如何在画布上制作弹跳球的动画

时间:2015-12-09 18:11:39

标签: javascript html canvas draw setinterval

您好我刚刚开始编写Java和HTML等编码,所以我有时会挣扎。因此,我已经加入StackOverflow寻求帮助(请高兴我:))

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

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

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

我的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/

我被math.random帮助了,因为我不明白。有人可以帮我解决这个问题吗? 提前谢谢。

1 个答案:

答案 0 :(得分:1)

动画弹跳球

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

主循环。

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

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.
}

要启动动画,只需在完成所有设置后调用mainloop

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

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

弹球

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

它演示了如何通过在requestAnimationFramesetTimeout之间切换来调整主循环函数来更改帧速率。

它有一个绘制图像和添加运动模糊的示例(注意运动模糊是GPU(图形处理单元)密集型,并且在大量对象上无法正常工作。)

帧间移动

从曲面反射物体的正确方法。

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

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;
}

为何重要

下面是一个球在画布内弹跳的模拟。

为了说明球在帧之间移动,它已被运动模糊以显示其在帧之间的运动。请注意,这不是完美的解决方案,因为假设弹跳是在球处于线性运动时发生的,而实际上它是在自由落体和恒定加速度下。但它仍然节约能源。

在正确的测试中,球弹回的高度会随着时间的推移而保持不变。没有能量损失或获得。

右键单击以关闭帧间调整,您会注意到每帧的球开始减小其高度。这是因为在每次碰撞时球都会失去一点能量,因为在碰撞试验后定位时,不会考虑前一帧中的运动。当碰撞发生在精确的帧时间时,它将稳定在一个恒定的速率。什么时候提前很难确定。

左键单击以减慢模拟帧速率,再次单击鼠标左键以恢复正常。

下面的代码实际上并不是答案的一部分,它是为了证明在碰撞测试期间没有正确调整位置对模拟整体精度的影响。

&#13;
&#13;
// 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
&#13;
.canC { width:500px;  height:500px;}
&#13;
<canvas class="canC" id="canV" width=500 height=500></canvas>
&#13;
&#13;
&#13;