首页 > 解决方案 > 如何在画布上用矩形或圆弧模拟重力?

问题描述

如何使用只有几个变量的画布对象来模拟重力。

我创建了基础画布、游戏、时间、分数、所有内容,甚至是游戏状态,但我被困在“将速度和重力因素添加到 Player Y 变量中”的部分。

我尝试将重力因子乘以指定值,然后将其添加到 yVel,然后将其添加到实际 Y 值,但我无法正确转换定位。

我想如果我想出如何“创造重力”来创造跳跃、向右移动和向左移动就不会太难了。

这是我用来查找平台的主要代码:

map.plates 表示一个充满数组的数组,每个数组包含一个板(平台板)的 4 个值 e 是 map.plates.Arrays。playY 基本上是玩家的确切 Y 高度,全部渲染到 fillRect();

function detectGravity() {
 map.plates.forEach(e => {
  if (playY > e[1] && playX <= e[0] && playX >= e[0] + e[2]) {
  } else {
   playY += 0; // Gravity Calculations here
  }
 });
}

我真的不知道我是否应该在此处包含其他任何内容,但是如果您想要整个项目,请参阅下面的代码段。

如果问题有什么问题,请告诉我,我已经快半年没来这里了。

完整的代码以防 codepen 死亡(建议在评论中):

"esversion: 6";

const can = document.querySelector(".block"),
 ctx = can.getContext("2d"),
 mScore = 100,
 map = {
  plates: [
   [25, 25, 25, 2],
   [75, 25, 25, 2],
   [125, 25, 25, 2],
   [175, 25, 25, 2],
   [225, 25, 25, 2],
   [25, 75, 25, 2],
   [75, 62, 25, 2],
   [125, 50, 25, 2],
   [175, 38, 25, 2],
   [25, 87, 25, 2],
   [75, 100, 25, 2]
  ],
  moneys: [
   [25, 25],
   [125, 25],
   [225, 25],
   [75, 62],
   [75, 100]
  ],
  player: [25, 25, 2, 2],
  badSpt: []
 };

let score = 0,
 time = 60,
 gameOn = 0;

let playX,
 playY,
 velX,
 velY,
 grav = 1.05;

can.addEventListener("click", startGame);

function startGame() {
 if (gameOn != 1) {
  gameOn = 1;
  init();
  gameTime = setInterval(() => {
   if (time != 0) {
    time -= 1;
   }
  }, 1000);
 }
}

function init() {
 can.width = 300;
 can.height = 300;
 drawEnviron();
 drawLevel();
 drawPlayer();
 drawGame();
 drawPixels();
 if (time == 0) {
  clearInterval(gameTime);
  time = 60;
  gameOn = 2;
 }
 window.requestAnimationFrame(init);
}

function drawEnviron() {
 with (ctx) {
  fillStyle = "#000000";
  fillRect(0, 0, can.width, can.height);
  fillStyle = "rgba(255, 255, 255, 0.5)";
  fillRect(0, 0, can.width, can.height);
  fillStyle = "#000000";
  fillRect(0, 0, can.width, can.height / 15);
  fillStyle = "#ffffff";
  font = can.height / 15 + "px Verdana";
  fillText("Score: " + score + "/" + mScore, 1, can.height / 19);
  fillText("Time: " + time, can.width / 1.5 + 6, can.height / 19);
 }
}

function drawLevel() {
 map.plates.forEach(e => {
  ctx.fillStyle = "#ffffff";
  ctx.fillRect(e[0], can.height / 15 + e[1], e[2], e[3]);
 });
 map.moneys.forEach(e => {
  ctx.beginPath();
  ctx.fillStyle = "#fcba03";
  ctx.arc(e[0] + 12.5, e[1] + 12.5, 4, 0, 2 * Math.PI);
  ctx.fill();
 });
}

function detectGravity() {
 map.plates.forEach(e => {
  if (playY > e[1] && playX <= e[0] && playX >= e[0] + e[2]) {
   
  } else {
   playY += 0;
  }
 });
}

function drawPlayer() {
 const a = map.player;
 if (gameOn == 0 || gameOn == 2) {
  playX = a[0];
  playY = a[1];
  velX = 0;
  velY = 0;
 }
 ctx.beginPath();
 ctx.fillStyle = "#ff0000";
 ctx.arc(playX + 12.5, playY + 12.5, 4, 0, 2 * Math.PI);
 ctx.fill();
}

function drawGame() {
 if (gameOn == 0) {
  can.style.animation = "none";
  with (ctx) {
   fillStyle = "rgba(0, 0, 0, 0.5)";
   fillRect(0, 0, can.width, can.height);
   strokeStyle = "#000000";
   lineWidth = 5;
   fillStyle = "#ffffff";
   textAlign = "center";
   strokeText("Click to Start", 150, can.height / 4);
   fillText("Click to Start", 150, can.height / 4);
  }
 } else if (gameOn == 2) {
  can.style.animation = "0.2s flash infinite";
  with (ctx) {
   fillStyle = "rgba(0, 0, 0, 0.5)";
   fillRect(0, 0, can.width, can.height);
   strokeStyle = "#000000";
   lineWidth = 5;
   fillStyle = "#ff0000";
   textAlign = "center";
   strokeText("-- Game Over --", 150, can.height / 4);
   fillText("-- Game Over --", 150, can.height / 4);
  }
 } else {
  can.style.animation = "none";
 }
}

function drawPixels() {
 var fw = (can.width / 2) | 0,
  fh = (can.height / 2) | 0;
 ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.msImageSmoothingEnabled = ctx.webkitImageSmoothingEnabled = false;
 ctx.drawImage(can, 0, 0, fw, fh);
 ctx.drawImage(can, 0, 0, fw, fh, 0, 0, can.width, can.height);
}

init();
* {
 box-sizing: border-box;
 overflow: hidden;
}

.block {
 border: 2px solid black;
}

@keyframes flash {
 0%, 100% {
  border: 2px solid black;
 }
 50% {
  border: 2px solid red;
 }
}
<canvas class="block"></canvas>

标签: javascriptcanvashtml5-canvasgame-physics

解决方案


简单的基于步骤的重力。

重力

重力表现为速度随时间的变化(加速度)。它有一个方向和大小(一个向量)

我们定义沿着画布向下的重力矢量

const gravity = {x: 0, y: 1};

通常我们以秒为单位施加重力。这不是一个方便的动画单元。在这种情况下,我们可以将其定义为每帧像素。一帧是1/60秒。因此,上面定义的重力大小为每刻度平方 1 个像素。所以在一秒钟内,一个物体将以每刻 60 像素或每秒 3600 像素的速度移动。

这对于大多数动画来说有点太快了,所以我们可以稍微放慢它

const gravity = {x: 0, y: 0.1};

物体

对象具有位置(坐标)和速度(矢量),具有方向和大小。

const object = {
    pos: {x: 0, y: 0}, // position
    vel: {x, 0, y: 0}, // velocity
}

为了模拟这个物体的重力,我们可以添加一个函数形式的行为。在这种情况下,我们可以调用它update。在update函数中,我们通过将gravity向量添加到速度向量 ( object.vel) 来加速对象。然后我们通过将速度向量添加object.vel到位置坐标来更新位置object.pos

const gravity = {x: 0, y: 0.1};
const object = {
    pos: {x: 0, y: 0}, // position
    vel: {x, 0, y: 0}, // velocity
    update() {
        this.vel.x += gravity.x;
        this.vel.y += gravity.y;
        this.pos.x += this.vel.x;
        this.pos.y += this.vel.y;
    }
}

世界

这个物体本身将永远坠落,所以我们需要让它与世界互动。我们可以定义一条地线。最基本的一条线位于画布上的任意位置。

const ground = ctx.canvas.height;  // ground at bottom of canvas.

要进行交互,我们需要添加到 objectsupdate函数。在此,我们检查物体相对于地面的位置。如果位置低于地面,我们将位置从地面向上移动到与地面相同的距离,并反转速度(反弹)。

我们可以将地面的弹性定义为速度的一小部分。

我们还需要给对象一个大小。

这样我们就得到了。

const gravity = {x: 0, y: 0.1};
const ground = ctx.canvas.height;  // ground at bottom of canvas.
const bounce = 0.5;
const object = {
    pos: {x: 0, y: 0}, // position
    vel: {x, 0, y: 0}, // velocity
    size: {w: 10, h: 10},
    update() {
        this.vel.x += gravity.x;
        this.vel.y += gravity.y;
        this.pos.x += this.vel.x;
        this.pos.y += this.vel.y;
        const g = ground - this.size.h; // adjust for size
        if(this.pos.y >= g) {  
            this.pos.y = g - (this.pos.y - g); // 
            this.vel.y = -Math.abs(this.vel.y) * bounce;  // change velocity to moving away.
        }
    }
}

然后所需要的就是调用更新每一帧并在正确的位置绘制对象。

演示

付诸实践。

一个名为的简单盒子object从画布顶部落下并撞击地面(画布底部)弹跳了一下并停止。(点击重置)

更新:我忘了检查对象是否处于静止状态。

如果我们不添加一点额外的代码到update.

当它的弹跳小于重力时,盒子现在看起来会完全停止。见评论// check for rest.

const ctx = canvas.getContext("2d");
canvas.width = innerWidth-4;
canvas.height = innerHeight-4;
requestAnimationFrame(mainLoop); // starts the animation

const gravity = {x: 0, y: 0.1};
const ground = ctx.canvas.height;  // ground at bottom of canvas.
const bounce = 0.9; // very bouncy
const object = {
    pos: {x: ctx.canvas.width / 2, y: 0}, // position halfway on canvas
    vel: {x: 0, y: 0}, // velocity
    size: {w: 10, h: 10},
    update() {
        this.vel.x += gravity.x;
        this.vel.y += gravity.y;
        this.pos.x += this.vel.x;
        this.pos.y += this.vel.y;
        const g = ground - this.size.h; // adjust for size
        if(this.pos.y >= g) {
            this.pos.y = g - (this.pos.y - g); // 
            this.vel.y = -Math.abs(this.vel.y) * bounce;  
            if (this.vel.y >= -gravity.y) {  // check for rest.
                this.vel.y = 0;
                this.pos.y = g - gravity.y;
            }
        }
    },
    draw() { ctx.fillRect(this.pos.x, this.pos.y, this.size.w, this.size.h) },
    reset() { this.pos.y = this.vel.y = this.vel.x = 0 },
}
function mainLoop() {
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    object.update(); // move object
    object.draw();
    requestAnimationFrame(mainLoop);
}
canvas.addEventListener("click", object.reset.bind(object));
body {
  margin: 0px;
  padding: 0px;
}

canvas {
  position: absolute;
  top: 0px;
  left: 0px;
  border: 1px solid black;
}
<canvas id="canvas"></canvas>


推荐阅读