简体   繁体   English

如何在 canvas 中随机移动一个圆圈?

[英]How can I make a circle to move randomly in a canvas?

I need to make this ball move randomly when I load the page, but only in movements of 15 by 15, for example, now that the ball is in (80,80) it can move randomly to (95,80), (80,95), (65,80) or (80,65), and it needs to keep moving every 15 px, but not so fast, it needs to stay there fore a moment and then move again当我加载页面时,我需要让这个球随机移动,但只能在 15 x 15 的移动中,例如,现在球在 (80,80) 它可以随机移动到 (95,80), (80 ,95)、(65,80) 或 (80,65),它需要每 15 px 移动一次,但不要太快,它需要在那里停留片刻然后再次移动

Honestly I don't know what to do, I've been stucked but haven't figure out how to do it, please help me but a simple way老实说,我不知道该怎么做,我一直被卡住但不知道该怎么做,请帮助我,但一个简单的方法

 "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>

Its not very clear, what kind of desired movement you want, however the below snippet should give basic idea, you can refine it further to get the desired output:它不是很清楚,你想要什么样的运动,但是下面的片段应该给出基本的想法,你可以进一步改进它以获得所需的 output:

 "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>

The movement is onclick of body, but can be changed by simply calling your moveBall function from desired event eg mousemove , canvas onclick etc. Or if you want time based movement, use setInterval or requestAnimationFrame with setTimeout to call moveBall . The movement is onclick of body, but can be changed by simply calling your moveBall function from desired event eg mousemove , canvas onclick etc. Or if you want time based movement, use setInterval or requestAnimationFrame with setTimeout to call moveBall .


Vector class矢量 class

Let us start by creating a Vector class.让我们从创建向量 class 开始。 It will make things easier.它会让事情变得更容易。 We are, of course working in 2D, thus our vectors are going to be 2D.我们当然是在二维中工作,因此我们的向量将是二维的。 So We create a constructor that takes x and y :所以我们创建了一个带有xy的构造函数:

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

These vectors aren't really vectors unless we have some operations.除非我们有一些操作,否则这些向量并不是真正的向量。 Namely addition and scalar product.即加法和标量积。 So let us add them:所以让我们添加它们:

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

As you can see, addition is simply adding the vectors member-wise.如您所见,加法只是按成员添加向量。

For the calar product, we simply multiply the members by a factor:对于标量积,我们只需将成员乘以一个因子:

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

To make a subtraction, we could do a.add(b.sacled(-1)) , but it is convenient to have a difference method:做减法,我们可以做a.add(b.sacled(-1)) ,但是有一个不同的方法很方便:

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

And we want a "norm" or length of the vector, which is just Pythagoras':我们想要一个向量的“范数”或长度,这只是毕达哥拉斯的:

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

Game loop游戏循环

Now, that we have our Vector class, let us talk about the game cycle.现在,我们有了 Vector class,让我们谈谈游戏周期。 What?什么? you say this is not a game?你说这不是游戏? Well, shush, we are writing it like one.好吧,嘘,我们正在写它。

In the web we want to use requestAnimationFrame for your game cycle.在 web 中,我们希望为您的游戏周期使用requestAnimationFrame We will call it one during initialization ( setup ), and we will have it call itself.我们将在初始化( setup )期间调用它,并让它自己调用。 We will use performace.now() to pass the time:我们将使用performace.now()来打发时间:

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

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

We need to know how much time elapsed since the last frame.我们需要知道自上一帧以来经过了多少时间。 So declare time in the outer scope, to keep track of the last time, and we compute delta like this:因此,在外部 scope 中声明time ,以跟踪最后一次,我们计算delta如下:

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

        requestAnimationFrame(tick);
    }

Note that I divide by 1000.0 , this is so I get the value in seconds not milliseconds.请注意,我除以1000.0 ,所以我得到的值是秒而不是毫秒。

The game cycle will take input (none, in this case), update the state (move the ball), and output (draw to the canvas).游戏循环将接受输入(在本例中为无),更新 state(移动球)和 output(绘制到画布上)。


Update state更新 state

So, we need a state.所以,我们需要一个 state。 Let us represent the position of the ball with a vector.让我们用一个向量来表示球的 position。 Similarly the position were it is going, the velocity, speed, etc...同样,position 正在运行,速度,速度等......

In fact we will have:事实上,我们将拥有:

    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

Now, the distance the ball would have moved in delta seconds is:现在,球在delta秒内移动的距离是:

        let distanceToCover = delta * speed;

And we can update the position like this:我们可以像这样更新 position:

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

Here we are assuming that direction is a unit vector.这里我们假设direction是一个单位向量。 Scaling it by distanceToCover gives us the offset the ball would have moved in delta seconds.通过distanceToCover缩放它可以得到球在delta秒内移动的偏移量。 We add that to the current position to get the updated position.我们将其添加到当前的 position 以获得更新的 position。

But what direction does the ball move?但是球会朝哪个方向移动? Well, once the ball reached its target, we pick a direction at random… Ah, we need to check if we reached the target!好吧,一旦球到达目标,我们就随机选择一个方向……啊,我们需要检查我们是否到达目标!

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

There we compute the distance from the position to the target, and see if it is less than the distance the ball would cover.在那里我们计算从 position 到目标的距离,看看它是否小于球将覆盖的距离。 You can think of this as checking if we are about to overshoot the target.您可以将其视为检查我们是否即将超出目标。

Please note that we cannot rely on the position matching the target perfectly.请注意,我们不能依赖与目标完美匹配的 position。 On one hand, in practice the ball is changing position discretely (except it does so each frame), so it might not hit the target.一方面,在实践中,球正在离散地改变 position(除了它每帧都这样做),所以它可能不会击中目标。 On the other, it would not anyway because floating point errors.另一方面,它无论如何都不会因为浮点错误。

Ok, now we need a direction at random.好的,现在我们需要一个随机的方向。 That is easy, we use Math.random() :这很简单,我们使用Math.random()

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

There we pick an angle in the range from -PI to PI radians, and we create a unit vector from it using trigonometry.我们在 -PI 到 PI 弧度范围内选择一个角度,然后使用三角函数从它创建一个单位向量。

We also need to update the target position:我们还需要更新目标 position:

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

The target position is the current position plus the direction we computed, scaled by the step.目标 position 是当前 position 加上我们计算的方向,按步长缩放。


I found a bug.我发现了一个错误。 Good old tunneling.好旧的隧道。 So I'll measure distance traveled instead of distance to target.所以我会测量行进的距离而不是到目标的距离。 So I need the start position instead of the target.所以我需要开始 position 而不是目标。

This is the fixed code:这是固定代码:

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

Oh, one more thing, that conditional would be false if I initialize start to the same value than position , and then it never picks another start .哦,还有一件事,如果我将start初始化为与position相同的值,那么该条件将是错误的,然后它永远不会选择另一个start Thus, start must be initialized to be away from position .因此,必须将start初始化为远离position

I know there are other thing to improve for "correctness".我知道“正确性”还有其他需要改进的地方。 In particular resetting the position to the computed target, and taking into account by how much we overshot to remove time from the motion.特别是将 position 重置为计算的目标,并考虑到我们为从运动中移除时间的过冲量。

However, remember the first law of computer graphics: If it looks right, it is right -Flecher Dunn & Ian Parberry但是,请记住计算机图形学的第一定律:如果它看起来正确,那就是正确的——Flecher Dunn & Ian Parberry


Output Output

Simple, clear the canvas, draw the ball:简单,清除canvas,画球:

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

CODE代码

This is working code:这是工作代码:

 "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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM