如何让圆圈在画布中随机移动?

时间:2021-04-06 05:44:22

标签: javascript canvas

我需要在加载页面时让这个球随机移动,但只能以 15 x 15 的移动,例如,现在球在 (80,80) 它可以随机移动到 (95,80) , (80,95), (65,80) 或 (80,65),它需要每 15 px 保持移动,但不是那么快,它需要在那里停留片刻,然后再次移动

老实说我不知道​​该怎么办,我一直被卡住了,但还没有弄清楚如何去做,请帮我一个简单的方法

"use strict";
let ctx;


function setup() {
    let canvas = document.getElementById("myCanvas");
    ctx = canvas.getContext("2d");  
    ball(80,80);
}

function ball(x,y) {
  ctx.save();
  ctx.translate(x, y);
    ctx.fillStyle = "red";
    ctx.beginPath();
    ctx.arc(50, 50, 15, 0, 2 * Math.PI); // head
    ctx.fill();
}

function moveBall(){
  ball(x,y);
  
}

function moveRandom() {
 Math.floor(Math.random()*8)*30 + 15
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Run</title>
    <script src="script.js"></script>
  </head>
  <body onload="setup()">
    <h1>Run</h1>
    <canvas id="myCanvas" height="400" width="400" style="border: 1px solid black"></canvas>

</body>
</html>

2 个答案:

答案 0 :(得分:1)

它不是很清楚,你想要什么样的运动,但是下面的片段应该给出基本的想法,你可以进一步细化它以获得所需的输出:

"use strict";
let ctx, canvas;


function setup() {
    canvas = document.getElementById("myCanvas");
    ctx = canvas.getContext("2d");  
    ball(80,80);
}

function ball(x,y) {
  //first clear the canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.save();
  //ctx.translate(x, y);
    ctx.fillStyle = "red";
    ctx.beginPath();
    ctx.arc(x, y, 15, 0, 2 * Math.PI); // head
    ctx.fill();
}

function moveBall(){
  var x = moveRandom();
  var y = moveRandom();
  console.log("moving to: ", x,y);
  ball(x, y);
  
}

function moveRandom() {
 //canvas width and height is same in your case, so multiplied by one to get both x and y. This will give x, y values within canvas.
 return Math.floor(Math.random()*canvas.width);

}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Run</title>
    <script src="script.js"></script>
  </head>
  <body onload="setup()" onclick="moveBall()">
    <h1>Run</h1>
    <canvas id="myCanvas" height="400" width="400" style="border: 1px solid black"></canvas>

</body>
</html>

运动是身体的 onclick,但可以通过简单地从所需事件调用 moveBall 函数来改变,例如mousemove、canvas onclick 等。或者,如果您想要基于时间的移动,请使用 setIntervalrequestAnimationFramesetTimeout 来调用 moveBall

答案 1 :(得分:1)


向量类

让我们从创建一个 Vector 类开始。它会让事情变得更容易。我们当然是在 2D 中工作,因此我们的向量将是 2D。所以我们创建了一个带有 xy 的构造函数:

    class Vector {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
    }

除非我们有一些操作,否则这些向量并不是真正的向量。即加法和标量积。所以让我们添加它们:

        add(other) {
            return new Vector(this.x + other.x, this.y + other.y);
        }

如您所见,加法只是将向量成员相加。

对于 calar 乘积,我们只需将成员乘以一个因子:

        scaled(factor) {
            return new Vector(this.x * factor, this.y * factor);
        }

要进行减法,我们可以做a.add(b.sacled(-1)),但使用差分法很方便:

        diff(other) {
            return new Vector(this.x - other.x, this.y - other.y);
        }

我们想要一个向量的“范数”或长度,它只是毕达哥拉斯的:

        length() {
            return Math.sqrt(this.x * this.x + this.y * this.y);
        }

游戏循环

现在,我们有了 Vector 类,让我们谈谈游戏周期。 什么?你说这不是游戏?好吧,嘘,我们正在写它。

在网络中,我们希望将 requestAnimationFrame 用于您的游戏周期。我们将在初始化 (setup) 期间将其称为 1,并且我们将使其自身调用。我们将使用 performace.now() 来打发时间:

    function tick(newTime){
        // ...
        requestAnimationFrame(tick);
    }

    function setup() {
        // ...
        time = performance.now();
        tick(time);
    }

我们需要知道自上一帧过去了多长时间。因此,在外部作用域中声明 time,以跟踪最后一次,我们像这样计算 delta

    function tick(newTime){
        let delta = (newTime - time) / 1000.0;
        time = newTime;
        
        // ...

        requestAnimationFrame(tick);
    }

请注意,我除以 1000.0,这样我才能以秒而不是毫秒为单位获得值。

游戏周期将接受输入(在本例中为无)、更新状态(移动球)和输出(绘制到画布)。


更新状态

所以,我们需要一个状态。让我们用一个向量来表示球的位置。类似地,它要去的位置、速度、速度等......

事实上我们会有:

    let position = new Vector(80, 80); // Pixels
    let target = new Vector(80, 80); // Pixels
    let direction = new Vector(0, 0); // Pixels
    let speed = 15.0; // Pixels per second
    let step = 15.0; // Pixels

现在,球在 delta 秒内移动的距离是:

        let distanceToCover = delta * speed;

我们可以像这样更新位置:

        position = position.add(direction.scaled(distanceToCover));

这里我们假设 direction 是一个单位向量。将其缩放 distanceToCover 为我们提供了球在 delta 秒内移动的偏移量。我们将其添加到当前位置以获得更新的位置。

但是球向哪个方向移动?好吧,一旦球到达目标,我们就随机选择一个方向……啊,我们需要检查是否到达目标!

        if (position.diff(target).length() < distanceToCover) {
            // ...
        }

在那里我们计算从位置到目标的距离,看看它是否小于球将覆盖的距离。您可以将其视为检查我们是否即将超过目标。

请注意,我们不能完全依赖与目标匹配的位置。一方面,实际上球是离散地改变位置的(除非它每帧都这样做),所以它可能不会击中目标。另一方面,它无论如何也不会因为浮点错误。

好的,现在我们需要一个随机的方向。这很简单,我们使用 Math.random():

            let angle = (Math.random() * Math.PI * 2.0) - Math.PI;
            direction = new Vector(Math.cos(angle), Math.sin(angle));

在那里我们选择一个从 -PI 到 PI 弧度范围内的角度,然后我们使用三角法从中创建一个单位向量。

我们还需要更新目标位置:

            target = position.add(direction.scaled(step));

目标位置是当前位置加上我们计算的方向,按步长缩放。


我发现了一个错误。好古老的隧道。所以我将测量行进的距离而不是到目标的距离。所以我需要起始位置而不是目标。

这是固定代码:

        if (position.diff(start).length() + distanceToCover > step) {
            // ...
            start = position;
        }

哦,还有一件事,如果我将 start 初始化为与 position 相同的值,然后它永远不会选择另一个 start,那么该条件将是错误的。因此,start 必须初始化为远离 position

我知道还有其他事情需要改进以提高“正确性”。特别是将位置重置为计算的目标,并考虑我们超过多少以从运动中移除时间。

但是,请记住计算机图形学的第一定律:如果看起来正确,那就正确 -Flecher Dunn & Ian Parberry


输出

简单,清空画布,画球:

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        drawBall(position);

代码

这是工作代码:

"use strict";

class Vector {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    add(other) {
        return new Vector(this.x + other.x, this.y + other.y);
    }

    diff(other) {
        return new Vector(this.x - other.x, this.y - other.y);
    }

    scaled(factor) {
        return new Vector(this.x * factor, this.y * factor);
    }

    length() {
        return Math.sqrt(this.x * this.x + this.y * this.y);
    }
}

let ctx;
let canvas;
let time;
let position = new Vector(80, 80); // Pixels
let start = new Vector(Infinity, Infinity); // Pixels
let direction = new Vector(0, 0); // Pixels
let speed = 15.0; // Pixels per second
let step = 15.0; // Pixels

function tick(newTime){
    let delta = (newTime - time) / 1000.0;
    time = newTime;
    
    let distanceToCover = delta * speed;
    if (position.diff(start).length() + distanceToCover > step) {
        let angle = (Math.random() * Math.PI * 2.0) - Math.PI;
        direction = new Vector(Math.cos(angle), Math.sin(angle));
        start = position;
    }
    
    position = position.add(direction.scaled(distanceToCover));

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawBall(position);
    requestAnimationFrame(tick);
}

function setup() {
    canvas = document.getElementById("myCanvas");
    ctx = canvas.getContext("2d");
    time = performance.now();
    tick(time);
}

function drawBall(position) {
    ctx.save();
    {
        ctx.translate(position.x, position.y);
        ctx.fillStyle = "red";
        ctx.beginPath();
        ctx.arc(50, 50, 15, 0, 2 * Math.PI); // head
        ctx.fill();
    }
    ctx.restore();
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Run</title>
    <script src="script.js"></script>
  </head>
  <body onload="setup()">
    <h1>Run</h1>
    <canvas id="myCanvas" height="400" width="400" style="border: 1px solid black"></canvas>

</body>
</html>