如何创建一个带有图像的 canvas 圆圈?

How do I create a canvas circle with a image on it?

所以,我按照教程制作了一个游戏。显然,这不是学习编码的最佳方式,所以我开始修改它。目前游戏中有缓慢向玩家移动的敌人,但这些敌人只是彩色圆圈而已。我想添加一张可以放在敌人身上的图片,但我不知道怎么做。下面是一些您可能想知道的代码:

敌人class(每帧调用更新):

class Enemy {
    constructor(x, y, radius, color, velocity) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.color = color;
        this.velocity = velocity;
    }

    draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
        ctx.fillStyle = this.color;
        ctx.fill();
    }

    update() {
        this.draw();
        this.x = this.x + this.velocity.x;
        this.y = this.y + this.velocity.y;
    }
}

创建敌人的功能:

function spawnEnemies() {
    setInterval(() => {
        const radius = Math.random() * (30 - 4) + 4;
        let x;
        let y;
        if (Math.random() < 0.5) {
            x = Math.random() < 0.5 ? 0 - radius : canvas.width + radius;
            y = Math.random() * canvas.height;
        } else {
            y = Math.random() < 0.5 ? 0 - radius : canvas.height + radius;
            x = Math.random() * canvas.width;
        }
        const color = `hsl(${Math.random() * 360}, 50%, 50%)`;
        const angle = Math.atan2(canvas.height / 2 - y, canvas.width / 2 - x);
    const velocity = {
        x: Math.cos(angle),
        y: Math.sin(angle)
    }
        enemies.push(new Enemy(x, y, radius, color, velocity));
    }, 1000)
}

动画函数中的这段代码是运行:

enemies.forEach((enemy, index) => {
        enemy.update();
        const dist = Math.hypot(player.x - enemy.x, player.y - enemy.y);
        
        if (isHacking) {
            if (dist - enemy.radius - player.radius < 11) {
                setTimeout(() => {
                for (let i = 0; i < enemy.radius * 2; i++) {
                    particles.push(new Particle(enemy.x, enemy.y, Math.random() * 2, enemy.color, {
                        x: (Math.random() - 0.5) * (Math.random() * 8),
                        y: (Math.random() - 0.5) * (Math.random() * 8)}
                    ));
                }}, 0)
                score += 25;
                scoreEl.innerHTML = score;
                setTimeout(() => {
                    enemies.splice(index, 1);
                    projectiles.splice(proIndex, 1);
                }, 0)
            }         
        } else if (dist - enemy.radius - player.radius < 1) {
            cancelAnimationFrame(animationId);
            modal.style.display = 'flex';
            modalScore.innerHTML = score;
        }

此外,这是我第一次在堆栈溢出上发帖,所以如果有什么我应该做但没有做的,反之亦然,请告诉我!

如果您的用例支持,我建议使用具有 pre-cropped 循环透明度的 png 来提高性能并避免对此进行编码。

但让我们继续回答您的问题 . I felt this was different enough from Canvas clip image with two quadraticCurves to add a new answer, but that thread shows the general approach: use context.clip(),在路径 context.arc 之后,然后以 context.drawImage.

结束

因为你还画了其他东西,所以用 context.save()context.restore() 包裹你的剪辑,以防止你的剪辑影响你之后画的所有东西。

这是一个最小的例子:

const canvas = document.createElement("canvas");
canvas.height = canvas.width = 100;
const {width: w, height: h} = canvas;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = function () {
  ctx.fillRect(10, 0, 20, 20); // normal drawing before save()  
  ctx.save();
  ctx.beginPath();
  ctx.arc(w / 2, h / 2, w / 2, 0, Math.PI * 2);
  ctx.clip();
  ctx.drawImage(this, 0, 0);
  ctx.restore();
  ctx.fillRect(0, 10, 20, 20); // back to normal drawing after restore()
};
img.src = `http://placekitten.com/${w}/${h}`;

如果您有多张图片,我建议使用 中描述的 promises。

有很多方法可以做你想做的事。带图片的圆圈。

从你的问题和评论中我可以猜到你想要一个显示加载图像的动态圆圈。

使用图像创建图案。

const imgPat = ctx.createPattern(image, "no-repeat");

使用图像大小来计算您可以在不超出图像范围内的最大半径。

const minRadius = Math.min(image.width, image.height) / 2;

要以任意半径绘制圆,您需要使用 2D 上下文 setTransform 函数。以下函数将执行此操作

function drawImageCircle(imgPat, minRadius, x, y, radius) {

     // get scale of circle image
     const scale = radius / minRadius;

     // transform to put origin at top left of bounding rectangle scaling to fit image pattern
     ctx.setTransform(scale, 0, 0, scale, x - radius, y - radius);
     ctx.fillStyle = imgPat;
     ctx.beginPath();
     ctx.arc(minRadius, minRadius, minRadius, 0, Math.PI * 2);  
     ctx.fill();

     // reset the default transform 
     ctx.setTransform(1, 0, 0, 1, 0, 0);

}

演示

演示加载图像。然后获取它的大小,从中创建一个图案并对其进行动画处理,改变半径并在其周围放置一个 4 像素的轮廓。

const image = new Image;
image.src = "https://i.stack.imgur.com/C7qq2.png?s=256&g=1";
image.addEventListener("load", imageReady);
const ctx = canvas.getContext("2d");
var w = canvas.width, h = canvas.height, cw = w / 2, ch = h / 2;
var imgPat, minRadius;

function imageReady() {
    imgPat = ctx.createPattern(image, "no-repeat");
    minRadius = Math.min(image.width, image.height) / 2;
    requestAnimationFrame(renderLoop);
}

function drawImageCircle(imgPat, minRadius, x, y, radius) {
     const scale = radius / minRadius;
     ctx.setTransform(scale, 0, 0, scale, x - radius, y - radius);
     ctx.strokeStyle = "#F00";
     ctx.lineWidth = 8 / scale;
     ctx.fillStyle = imgPat;
     ctx.beginPath();
     ctx.arc(minRadius, minRadius, minRadius, 0, Math.PI * 2); 
     ctx.stroke();
     ctx.fill();
     ctx.setTransform(1, 0, 0, 1, 0, 0);
}

function renderLoop(time) {
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0, 0, w, h);
    var x = Math.cos(time / 500) * (cw - 80) + cw;
    var y = Math.sin(time / 600) * (ch - 80) + ch;
    var rad = Math.sin(time / 333) * 10 + 30;
    drawImageCircle(imgPat, minRadius, x, y, rad);
    requestAnimationFrame(renderLoop);
}
canvas {
   background: #888;
   border: 2px solid black;
}
<canvas id="canvas" width="256" height="256"></canvas>