如何在画布上制作弹跳球动画 [英] how to animate a bouncing ball on canvas

查看:125
本文介绍了如何在画布上制作弹跳球动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

你好我刚刚开始编写Java和HTML等,所以我倾向于有时挣扎。因此,我已经加入了StackOverflow的帮助(对我好,请:) :)

我试图使用 setInterval() code>并绘制方法来创建一个弹跳球,它在每个帧中移动多个像素。当它碰到边缘时,它应该通过反转相关的水平或垂直像素速度的方向来反弹。

到目前为止,我的HTML代码是:

 <!DOCTYPE html> 
< html lang =en>
< head>
< title>帆布范例< / title>
< script type =text / javascriptsrc ='myanim.js'defer>< / script>
< style type = text / css>
#mycanvas {border:1px solid#000000}
< / style>
< / head>
< body>
< canvas id = mycanvas width = 600 height = 400>不支持画布
< / canvas>
< / body>
< / html>

我的JavaScript:

  var canvas = document.getElementById('mycanvas'); 
var ctx = canvas.getContext('2d');
var timer;
var fillColour ='#FF0000';
var strokeColour ='#FFFFFF';
var x = 0; var y = 0;
函数frameRate(fps){
timer = window.setInterval(updateCanvas,1000 / fps);
}
函数noLoop(){
window.clearInterval(timer);

function updateCanvas(){
if(Math.random()> 0.5)x + = 2; else x- = 2;
if(Math.random()> 0.5)y + = 2;否则y- = 2;
ctx.fillRect(0,0,canvas.width,canvas.height);
draw();
}
函数draw(){
ctx.beginPath();
ctx.arc(canvas.width / 2 + x,canvas.height / 2 + y,100,0,2 * Math.PI);
ctx.stroke();
}
ctx.strokeStyle = strokeColour;
ctx.fillStyle = fillColour;
frameRate(50);

http://jsfiddle.net/6EFqk/265/



我得到了数学的帮助。随机,因为我不明白。有人可以帮我解决这个问题吗?
提前致谢

解决方案

动画弹跳球



下面是一个简单的弹跳球的例子。这个答案部分来自另一个答案,但你的问题标题更适合被发现。



主循环。



要制作动画,您需要有一个主循环。这是每次需要更新并在画布上绘制所需的所有内容时调用的函数。此更新通常称为框架。然后主循环被称为秒的次数称为帧速率。 HTML的典型帧速率是每秒60帧或更低。 HTML5提供了一个特殊的事件调用requestAnimationFrame,它可以在您运行主循环的最佳时间为动画和火灾设计。

 函数mainLoop(){
//清除画布
//更新动画中所有东西的位置

//然后请求下一个动画帧
window.requestAnimationFrame(mainLoop); //将它作为主循环的名称
//主循环完成。退出让浏览器知道您已完成并
//它将重绘的画布呈现给屏幕。

要启动动画,只需在设置好所有内容后调用mainloop, p>

  //开始动画
mainLoop(); //调用主循环

请求动画帧将尽最大努力保持均匀的帧速率。如果您的主循环需要很长时间,它也会变慢。

弹跳



<下面是如何模拟单个弹跳球并正确处理与画布边平行的表面的简单碰撞的示例。



它演示了如何更改框架通过在 requestAnimationFrame setTimeout 之间切换来调用主循环函数。

它有一个绘制图像的示例,并添加了运动模糊(注:运动模糊是GPU(图形处理单元)密集型,并且不适用于大量物体。)



帧间运动

反射对象的正确方法一个表面。

您必须考虑到球在帧之间移动,并且碰撞可能发生在前一帧期间的任何时间。碰撞后,球与墙壁的距离取决于在前一帧撞击墙壁时的时间。这是非常重要的,如果球缓慢或快速移动。

  var dx = 10; //像素的delta x速度像素
var wx = 10; //以像素为单位的对象宽度
var px = 90; //物体的像素位置
var wallX = 105; //墙的位置


px + = dx; //移动球。它的位置是100.
//它的右边是px + wx = 110.
//测试它是否有墙壁
if(px + wx> wallX){
dx = -dx; //反射增量x
//物体是5个像素到墙上。
//在最后一帧期间对象有一段时间撞墙了
//我们需要调整位置,因为球可能是
//远离墙壁一段时间在最后一帧。
var dist =(px + wx)-wallX; //获得到墙的距离
px - = dist * 2; //物体撞击位置95处的墙,并且从那以后一直是
//远处移动,所以很容易只需
//减去球进入墙壁的距离的2倍
/ /以上两行可以在
// px - =((px + wx)-wallX)* 2中完成;
}

重要性



下面是对画布内弹跳球的模拟。为了说明球在帧之间移动,运动模糊以显示帧之间的运动。请注意,这不是一个完美的解决方案,因为当球处于线性运动时,假定发生了反弹,而实际上它是在自由下落和恒定加速下发生的。但它仍然节省能源。

在正确的测试中,球弹回的高度在一段时间内保持不变。没有能量损失或获得。

右键关闭帧间调整,您会注意到球每帧开始降低其高度。这是因为在每次碰撞时球会失去一点点能量,因为在碰撞测试之后定位它时,在前一帧期间的运动没有被考虑。恰好在帧时间发生碰撞时,它会稳定在恒定的速率。当这将是很难提前确定。

左键单击以减慢模拟帧速率,再次左键单击以恢复正常。 下面的代码实际上并不是答案的一部分,它是为了证明在碰撞测试期间未正确调整位置对仿真的整体精度的影响。

//帮助函数。不是answervar的一部分canvas = document.getElementById(canV); var ctx = canvas.getContext(2d); var mouseButton = 0; canvas.addEventListener('mousedown',function(event){mouseButton = event.which;}); canvas.addEventListener('mouseup',function() {mouseButton = 0;}); canvas.addEventListener(contextmenu,function(e){e.preventDefault();},false); var currentSurface = ctx; var createImage = function(w,h){// create大小为w,h和附加上下文的canvas图像2d var image = document.createElement(canvas); image.width = w; image.height = h!== undefined?h:w; currentSurface = image.ctx = image.getContext(2d); var setColour = function(fillC,strokeC,lineW){currentSurface.fillStyle = fillC!== undefined? fillC:currentSurface.fillStyle; currentSurface.strokeStyle = strokeC!==未定义? strokeC:currentSurface.strokeStyle; currentSurface.lineWidth = lineW!==未定义? lineW:currentSurface.lineWidth;} var circle = function(x,y,r,how){currentSurface.beginPath(); currentSurface.arc(X,Y,R,0,Math.PI * 2); how = how.toLowerCase()。replace(/ [os] / g,l); //如何绘制开关(如何){casef:// fill currentSurface.fill();打破; casel:currentSurface.stroke();打破; caself:currentSurface.stroke(); currentSurface.fill();打破; casefl:currentSurface.fill(); currentSurface.stroke();打破; }} function createGradImage(size,col1,col2){var image = createImage(size); var g = currentSurface.createLinearGradient(0,0,0,currentSurface.canvas.height); g.addColorStop(0中,col1); g.addColorStop(1,COL2); currentSurface.fillStyle = g; currentSurface.fillRect(0,0,currentSurface.canvas.width,currentSurface.canvas.height); return image;}函数createColouredBall(ballR,col){var ball = createImage(ballR * 2); var unit = ballR / 100; setColour( 黑色);圆(ballR,ballR,ballR, F); setColour( HSL( + COL + 100%,30%));圆(ballR单元* 3,ballR单元* 3,ballR单元* 7, F); setColour( HSL( + COL + 100%,50%));圆(ballR单元* 10,ballR单元* 10,ballR单元* 16, F); setColour( 白);圆(ballR单元* 50,ballR单元* 50,单元* 16, F); return ball;} // =================================== // _ // / _\ _ _ ____ __ _____ _ _ // / _ \ | '\(_-< V V / -_)'_ | // / _ / \ __ _ || _ / __ / \_ / \_ / \ ___ | _ | // // ================================== //答案代码//延迟编码器variablesvar w = canvas .width; var h = canvas.height; //模拟球5cm var pixSize = 0.24; //以毫米为单位进行仿真//重力为9.8 ms ^ 2,因此转换为每帧像素的平方//假设每秒恒定60帧。 ()var gravity = 9800 * pixSize / 60;重力* = 0.101; //因为地球的引力是愚蠢的大让我们移动到冥王星//球5cm var ballR =(25 / pixSize)/ 2; //对于5cm直径球体,半径为2.5cm ballX = w / 2; //获取canvasvar的中心ballY = ballR + 3; //从topvar开始ballDX =(Math.random() - 0.5)* 15; //从随机开始x speedballDX + = ballDX< 0? -5:5; //确保它不太慢。ballDY = 0; // ball没有向下的速度; var ballLastX = ballX; var ballLastY = ballY; //创建Ballvar的图像ball = createColouredBall(ballR,Math.floor(Math.random()* 360)); //创建球的图像//创建背景。图像很小,因为它在itvar中没有太多细节background = createGradImage(16,#5af,#08C); //时间运行//功能绘制没有运动模糊的球//绘制球行动被弄脏。 //图像是要绘制的图像// px和py是绘制球体的x和y位置drawImage = function(image,px,py){ctx.drawImage(image,px,py);} //绘制球运动模糊。这引入了额外的复杂度drawMotionBlur = function(image,px,py,dx,dy,steps){var i,sx,sy; sx = dx / steps; sy = dy / steps; px - = dx; //回到开始位置py - = dy; ctx.globalAlpha = 1 /(steps * 0.8); //为每个步骤设置α略高于(i = 0; i

.canC {width: 500像素; height:500px;}

< canvas class =canC id =canVwidth = 500 height = 500>< / canvas>

Hi I've just started coding Java and HTML, etc, so I tend to struggle at times. Hence I have joined StackOverflow for your help (Be nice to me please :) )

I'm trying to animate using setInterval() and draw method to create a bouncing ball which moves by a number of pixels in each frame. When it hits an edge it should bounce back by reversing the direction of the relevant horizontal or vertical pixel velocity.

Here is what has been done so far, my HTML code:

<!DOCTYPE html>
  <html lang="en">
  <head>
  <title>Canvas Example</title>
  <script type="text/javascript" src='myanim.js' defer></script>
  <style type=text/css>
    #mycanvas {border:1px solid #000000}
  </style>
  </head>
  <body>
  <canvas id=mycanvas width=600 height=400>Canvas Not Supported
  </canvas>
  </body>
  </html>

My JavaScript:

var canvas=document.getElementById('mycanvas');
var ctx=canvas.getContext('2d');
var timer;
var fillColour = '#FF0000';
var strokeColour = '#FFFFFF';
var x=0; var y=0;
function frameRate(fps) {
 timer = window.setInterval(updateCanvas,1000/fps);
}
function noLoop() {
 window.clearInterval(timer);
}
function updateCanvas(){
if (Math.random()>0.5)x+=2; else x-=2;
if (Math.random()>0.5)y+=2; else y-=2;
ctx.fillRect(0,0,canvas.width,canvas.height);
draw();
}
function draw(){
ctx.beginPath();
ctx.arc(canvas.width/2+x,canvas.height/2+y,100,0,2*Math.PI);
ctx.stroke();
}
ctx.strokeStyle=strokeColour;
ctx.fillStyle=fillColour;
frameRate(50);

http://jsfiddle.net/6EFqk/265/

I've been helped out by the math.random as I don't understand it. Can someone help me out on how to do this? Thanks in advance.

解决方案

Animating a bouncing ball

Below is an example of a simple bouncing ball. This answer is partly from another answer, but your question title is better suited to being found.

The Main Loop.

To animate you need to have a main loop. This is the function that is called once every time you need to update and draw everything you need onto the canvas. This update is normally called a frame. Then number of times the main loop is called a second is called the frame rate. The typical frame rate of HTML is 60 frames per second or lower. HTML5 provides a special event call requestAnimationFrame that is designed for animations and fires at the best time for you to run the main loop.

function mainLoop(){
    // clear the canvas
    // update the position of all the stuff in the animation

    // then request the next animation frame
    window.requestAnimationFrame( mainLoop ); // pase it the name of your main loop
    // the main loop is done. Exiting lets the browser know yo are done and 
    // it presents the redrawn canvas to the screen.
}

To start the animation just call the mainloop after you have everything set up

// starting the animation
mainLoop(); // call main loop

Request animation frame will do its best to maintain an even frame rate. It will also slow down if your mainloop takes to long.

Bouncing a ball

Below is an example of how to simulate a single bouncing ball and to correctly handle simple collisions will surfaces parallel to the canvas sides.

It demonstrates how to change the frame rate by switching between requestAnimationFrame and setTimeout to call the main loop function.

It has an example of drawing an image, and adding motion blur (NOTE motion blur is GPU (graphics processing unit) intensive and will not work well on large numbers of objects.)

Inter frame movement

The correct way to reflect an object from a surface.

You must take into account that the ball is moving between frames and that the collision may have happened at any time during the previous frame. The ball's distance from the wall after the collision is dependent on when during the previous frame it hit the wall. This is important if the ball moves slowly or quickly.

var dx = 10; // delta x velocity of object in pixels
var wx = 10; // width of object in pixels
var px = 90;  // position of object in pixels
var wallX = 105; // position of wall


px += dx;  // move the ball. Its position is now  100.
           // its right side is at px + wx = 110.
// test if it has it the wall
if(px+wx > wallX){
    dx = -dx; // reflect delta x
    // The object is 5 pixel into the wall.
    // The object has hit the wall some time during the last frame
    // We need to adjust the position as the ball may have been
    // traveling away from the wall for some time during the last frame.
    var dist = (px+wx)-wallX; // get the distance into the wall
    px -= dist*2; // the object hit the wall at position 95 and has been 
                  // traveling away since then so it is easy to just 
                  // subtract 2 times the distance the ball entered the wall
    // the above two lines can be done in one
    // px -= ((px+wx)-wallX)*2;
}

Why it matters

Below is a simulation of a ball bouncing inside the canvas.

To illustrate that the ball is moving between frames it has been motion blurred to show its motion between frames. Please note this is not the perfect solution as the bounce is assumed to occur while the ball is in linear motion while infact it is in freefall and under constant acceleration. But it still conserves energy.

In the correct test the height the ball bounces back to, stays around the same over time. No energy is lost or gained.

Right click to turn off the inter frame adjustment and you will notice that the ball begins to decrease its height each frame. This is because at each collision the ball loses a little energy because it motion during the previous frame is not taken into account when positioning it after the collision test. It will settle down to a constant rate when the collision occurres at precisely the frame time. When that will be is very hard to determine in advance.

Left click to slow the simulation frame rate, left click again to return to normal.

The code below is not really part of the answer, it is there to demonstrate the effect of not correctly adjusting the position during collision test on the overall accuracy of the simulation.

// helper functions. NOT part of the answer
var canvas = document.getElementById("canV"); 
var ctx = canvas.getContext("2d");
var mouseButton = 0;
canvas.addEventListener('mousedown',function(event){mouseButton = event.which;});
canvas.addEventListener('mouseup'  ,function(){mouseButton = 0;});
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);
var currentSurface = ctx;
var createImage = function (w, h) {// create an canvas image of size w,h and attach context 2d
    var image = document.createElement("canvas");  
    image.width = w;
    image.height = h !== undefined?h:w; 
    currentSurface = image.ctx = image.getContext("2d"); 
    return image;
}  
var setColour = function (fillC, strokeC, lineW) { 
    currentSurface.fillStyle = fillC !== undefined ? fillC : currentSurface.fillStyle;
    currentSurface.strokeStyle = strokeC !== undefined ? strokeC : currentSurface.strokeStyle;
    currentSurface.lineWidth = lineW !== undefined ? lineW : currentSurface.lineWidth;
}
var circle = function(x,y,r,how){
    currentSurface.beginPath();
    currentSurface.arc(x,y,r,0,Math.PI*2);
    how = how.toLowerCase().replace(/[os]/g,"l"); // how to draw
    switch(how){
        case "f":  // fill
            currentSurface.fill();
            break;
        case "l":
            currentSurface.stroke();
            break;
        case "lf":
            currentSurface.stroke();
            currentSurface.fill();
            break;
        case "fl":
            currentSurface.fill();
            currentSurface.stroke();
            break;
    }
}
function createGradImage(size,col1,col2){
    var image = createImage(size);
    var g = currentSurface.createLinearGradient(0,0,0,currentSurface.canvas.height);
    g.addColorStop(0,col1);
    g.addColorStop(1,col2);
    currentSurface.fillStyle = g;
    currentSurface.fillRect(0,0,currentSurface.canvas.width,currentSurface.canvas.height);    
    return image;
}
function createColouredBall (ballR,col) {
    var ball = createImage(ballR*2);
    var unit = ballR/100;
    setColour("black");
    circle(ballR,ballR,ballR,"f");
    setColour("hsl("+col+",100%,30%)");
    circle(ballR-unit*3,ballR-unit*3,ballR-unit*7,"f");
    setColour("hsl("+col+",100%,50%)");
    circle(ballR-unit*10,ballR-unit*10,ballR-unit*16,"f");
    setColour("White");
    circle(ballR-unit*50,ballR-unit*50,unit*16,"f");
    
    return ball;
}
//===================================    
//    _                          
//   /_\  _ _  ____ __ _____ _ _ 
//  / _ \| ' \(_-< V  V / -_) '_|
// /_/ \_\_||_/__/\_/\_/\___|_|  
//                              
// ==================================
// Answer code

// lazy coder variables
var w = canvas.width;
var h = canvas.height;

// ball is simulated 5cm 
var pixSize = 0.24; // in millimeters for simulation

// Gravity is 9.8 ms^2 so convert to pixels per frame squared
// Assuming constant 60 frames per second. ()
var gravity = 9800*pixSize/60; 
gravity *= 0.101; // because Earth's gravity is stupidly large let's move to Pluto

// ball 5cm 
var ballR = (25/pixSize)/2;          // radius is 2.5cm for 5cm diamiter ball
var ballX = w/2;                     // get center of canvas
var ballY = ballR+3;                 // start at the top
var ballDX = (Math.random()-0.5)*15; // start with random x speed
ballDX += ballDX < 0 ? -5 : 5;       // make sure it's not too slow
var ballDY = 0;                      // star with no downward speed;
var ballLastX = ballX;
var ballLastY = ballY;

//create an image of the Ball
var ball = createColouredBall(ballR,Math.floor(Math.random()*360)); // create an image of ball

// create a background. Image is small as it does not have much detail in it
var background = createGradImage(16,"#5af","#08C");
// time to run for


// Function to draw ball without motion blur
// draws the ball with out motion blurred. 
// image is the image to draw
// px and py are the x and y position to draw the ball
var drawImage = function(image , px, py){
    ctx.drawImage(image, px, py);
}


// draws the ball motion blurred. This introduces extra complexity
var drawMotionBlur = function(image, px, py, dx, dy, steps){
    var i, sx, sy;
    sx = dx / steps;
    sy = dy / steps;
    px -= dx; // move back to start position
    py -= dy; 
    ctx.globalAlpha = 1 / (steps * 0.8); // set alpha to slightly higher for each step
    for(i = 0; i < steps; i+= 1){
        ctx.drawImage(image, px + i * sx, py + i * sy);
    }
    ctx.globalAlpha = 1; // reset alpha
    
}
// style for text
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.textAlign = "center";
ctx.lineJoin = "round"; // stop some letters getting ears.
ctx.lineWidth = 3;
ctx.textBaseline = "bottom";
var textCenterX = w/2;
var maxHeight = Infinity;
var lastMaxHeight = ballY;
var slowMotion = false;  // slow motion flag
var frameTravel = true;  // use frame travel in collision test 
const bSteps = 10;  // the fixed motion blur steps
var update = function(){
    var str, blurSteps;
    blurSteps = 10;  // motion blur ball render steps. This varies depending on the the collision inter frame time. 
     
    if(mouseButton === 1){
        slowMotion = ! slowMotion;
        mouseButton = 0;
    }
    if(mouseButton === 3){
        frameTravel = ! frameTravel;
        ballX = w / 2;                     // get center of canvas
        ballY = ballR + 3;                 // start at the top
        ballDY = 0;                      // start at 0 y speed
        mouseButton = 0;
    }
    // clear the canvas with background canvas image
    ctx.drawImage(background, 0, 0, w, h);
    
    ballDY += gravity; // acceleration due to grav
    // add deltas to ball position
    ballX += ballDX; 
    ballY += ballDY;
    // test for collision on left and right walls. Need to 
    // adjust for motion blur
    if (ballX < ballR) {
        ballDX = -ballDX; // refect delta x
        if (frameTravel) { // if using frame travel time
            // blur the outward traveling ball only for the time it has been traveling away
            blurSteps = Math.ceil(10 * ((ballX - ballR) / -ballDX));
            // get position it should have traveled since
            ballX -= (ballX - ballR) * 2;
        }else{
            ballX = ballR; // move ball to touching wall
            blurSteps = 1; // there is no outward motion
        }
    } else
    if (ballX > w - ballR) {
        ballDX = -ballDX;
        if (frameTravel) { // if using frame travel time
            // blur the outward traveling ball only for the time it has been traveling away
            blurSteps = Math.ceil(10 * ((ballX - (w - ballR)) / -ballDX));
            ballX -= (ballX - (w - ballR)) * 2;
        }else{
            ballX = w - ballR; // move ball to touching wall
            blurSteps = 1; // there is no outward motion
        }
    }

    // Test ball hit ground
    if (ballY > h - ballR) {
        ballDY = -ballDY;
        // to show max height
        lastMaxHeight = maxHeight;
        maxHeight = Infinity;
        if (frameTravel) { // if using frame travel time
            // blur the outward traveling ball only for the time it has been traveling away
            blurSteps = Math.ceil(10 * ((ballY - (h - ballR)) / -ballDY));
            ballY -= (ballY - (h - ballR)) * 2;
        }else{
            ballY = h - ballR; // move ball to touching wall
            blurSteps = 1; // there is no outward motion
        }
    }     
   
    // draw the ball motion blured
    drawMotionBlur(
        ball,                    // image to draw
        ballX - ballR,             // offset radius
        ballY - ballR,
        ballDX * (blurSteps / bSteps),  // speed and adjust for bounced
        ballDY * (blurSteps / bSteps),
        blurSteps                // number of blurs
    );

    // show max height. Yes it is min but everything is upside down.
    maxHeight = Math.min(maxHeight,ballY);
    lastMaxHeight = Math.min(ballY,lastMaxHeight);

    // show max height
    ctx.font = "12px arial black";
    ctx.beginPath();
    ctx.moveTo(0, lastMaxHeight - ballR);
    ctx.lineTo(w, lastMaxHeight - ballR);
    ctx.stroke();
    ctx.fillText("Max height.", 40, lastMaxHeight - ballR + 6);


    str = ""; // display status string
    if(slowMotion){   // show left click help
        str += "10fps."
        ctx.fillText("click for 60fps.", textCenterX, 43);
    }else{
        str += "60fps."
        ctx.fillText("click for 10fps.", textCenterX, 43);
    }

    if(frameTravel){ // show mode and right click help
        str += " Mid frame collision.";
        ctx.fillText("Right click for Simple collision", textCenterX,55);
    }else{
        str += " Simple collision.";
        ctx.fillText("Right click for mid frame collision", textCenterX,55);
    }

    // display help text
    ctx.font = "18px arial black";  
    ctx.strokeText(str, textCenterX, 30);
    ctx.fillText(str, textCenterX, 28);

    if(slowMotion){
        setTimeout(update, 100); // show in slow motion
    }else{
        requestAnimationFrame(update); // request next frame (1/60) seconds from now
    }

    // all done
}
update(); // to start the ball rolling

.canC { width:500px;  height:500px;}

<canvas class="canC" id="canV" width=500 height=500></canvas>

这篇关于如何在画布上制作弹跳球动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆