如何在 canvas 上为带有背景图像的球制作动画?

How can I animate a ball with a background image on the canvas?

我正在创建一个 canvas 游戏,其中的球会相互反弹。 我希望球有自己的皮肤,其中背景图像放在弧形元素上。

当球不弹跳时,图像剪辑得很好并且是圆形的,就像弧线一样。但是,当我开始为球设置动画时,它根本就不会移动,因为剪辑功能不允许重绘图像或弧线。

那时我发现 canvas 中的保存和恢复功能允许在制作动画时使用剪辑方法。

问题是图像的一部分没有正确剪裁。只有一半的动画是圆形的,另一半是矩形的。我试过调整图像的位置,但这并没有达到预期的效果。

我不确定为什么代码会这样,也不知道如何修复它,使它只是一个带有图像背景的动画球。

如果有人对此有所了解并且可能有解决方案,将不胜感激。

下面是代码和片段:

  const x = document.getElementById('canvas');
  const ctx = x.getContext('2d');
  let slide = 0;
  class Balls {
    constructor(xPos, yPos, radius) {
      this.xPos = xPos;
      this.yPos = yPos;
      this.radius = radius;
      this.imgX = this.xPos - this.radius;
    }
  }
  const img = document.createElement('img');
  img.src = 'https://geology.com/google-earth/google-earth.jpg';
  Balls.prototype.render = function() {
    ctx.save();
    ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2);
    ctx.clip();
    ctx.drawImage(img, this.imgX, this.yPos - this.radius, this.radius * 2, this.radius * 2);

  };
  Balls.prototype.motion = function() {
    this.imgX = this.imgX + 1;
    this.xPos = this.xPos + 1;
  }
  let object = new Balls(100, 100, 25);
  const animate = () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    object.render();
    object.motion();
    ctx.restore();
  }
  setInterval(animate, 50);
body {
  background-color: grey;
}

#canvas {
  background-color: white;
}

#mod {
  border-radius: 100%
}
<!DOCTYPE html>
<html>
<head>
  <title>Page Title</title>
  <script src='practice.js'></script>
  <link rel="stylesheet" type="text/css" href="practice.css">
</head>
<body>
  <canvas id="canvas" height="200" width="800" />
</body>
</html>

您需要调用ctx.beginPath(),否则,对arc()的每次调用都会添加到相同且唯一的子路径。这意味着最后,您正在剪裁由许多弧线组成的奇怪路径:5 帧后路径的 ASCII 表示:((((( )

const x = document.getElementById('canvas');
const ctx = x.getContext('2d');
let slide = 0;
class Balls {
  constructor(xPos, yPos, radius) {
    this.xPos = xPos;
    this.yPos = yPos;
    this.radius = radius;
    this.imgX = this.xPos - this.radius;
  }
}
const img = document.createElement('img');
img.src = 'https://geology.com/google-earth/google-earth.jpg';
Balls.prototype.render = function() {
  ctx.save();
  // begin a  new sub-path
  ctx.beginPath();
  ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2);
  ctx.clip();
  ctx.drawImage(img, this.imgX, this.yPos - this.radius, this.radius * 2, this.radius * 2);

};
Balls.prototype.motion = function() {
  this.imgX = this.imgX + 1;
  this.xPos = this.xPos + 1;
}
let object = new Balls(100, 100, 25);
const animate = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  object.render();
  object.motion();
  ctx.restore();
  requestAnimationFrame(animate);
}
animate();
body {
  background-color: grey;
}

#canvas {
  background-color: white;
}

#mod {
  border-radius: 100%
}
<canvas id="canvas" height="200" width="800" />

另请注意,对于圆形遮罩,您最好使用合成而不是剪裁,合成处理更好的抗锯齿,并且不需要昂贵的 save/restore。

const x = document.getElementById('canvas');
const ctx = x.getContext('2d');
let slide = 0;
class Balls {
  constructor(xPos, yPos, radius) {
    this.xPos = xPos;
    this.yPos = yPos;
    this.radius = radius;
    this.imgX = this.xPos - this.radius;
  }
}
const img = document.createElement('img');
img.src = 'https://geology.com/google-earth/google-earth.jpg';
Balls.prototype.render = function() {
  // begin a  new sub-path
  ctx.beginPath();
  ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2);
  ctx.fill();
  ctx.globalCompositeOperation = 'source-in';
  ctx.drawImage(img, this.imgX, this.yPos - this.radius, this.radius * 2, this.radius * 2);
  ctx.globalCompositeOperation = 'source-over';
};
Balls.prototype.motion = function() {
  this.imgX = this.imgX + 1;
  this.xPos = this.xPos + 1;
}
let object = new Balls(100, 100, 25);
const animate = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  object.render();
  object.motion();
  requestAnimationFrame(animate);
}
animate();
body {
  background-color: grey;
}

#canvas {
  background-color: white;
}

#mod {
  border-radius: 100%
}
<canvas id="canvas" height="200" width="800" />