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

2023-12-08

你好,我刚刚开始编写 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因为我不明白。有人可以帮助我如何做到这一点吗? 提前致谢。


制作弹跳球的动画

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

主循环。

要制作动画,您需要有一个主循环。每次您需要更新并将所需的所有内容绘制到画布上时,都会调用该函数。这种更新通常称为帧。那么每秒调用主循环的次数称为帧速率。 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.
}

要启动动画,只需在完成所有设置后调用主循环即可

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

请求动画帧会尽力保持均匀的帧速率。如果你的主循环花费很长时间,它也会减慢速度。

弹起一个球

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

它演示了如何通过切换来更改帧速率requestAnimationFrame and setTimeout调用主循环函数。

它有一个绘制图像并添加运动模糊的示例(注意运动模糊是 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;
}

为什么这很重要

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

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

在正确的测试中,球弹回的高度随着时间的推移保持大致相同。没有能量损失或获得。

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

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

下面的代码并不是答案的一部分,它是为了演示碰撞测试期间未正确调整位置对模拟整体精度的影响。

// 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>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在画布上制作弹跳球的动画 的相关文章

  • 如何按照编写的顺序迭代 javascript 对象属性

    我发现了代码中的一个错误 我希望通过最少的重构工作来解决该错误 此错误发生在 Chrome 和 Opera 浏览器中 问题 var obj 23 AA 12 BB iterating through obj s properties for
  • 动画进度元素值

    我有一个progress元素 该元素如下所示 div class container div div div
  • 如何将此 HTML 表格布局解决方案转换为浮动 div 解决方案?

    我经常需要列出各种尺寸的项目images在左边和text在右边 像这样 替代文本 http www deviantsart com upload 7s01l5 png http www deviantsart com upload 7s01
  • 是否可以使用 javascript 测试用户的浏览器/操作系统是否支持给定类型的链接?

    是否可以使用 javascript 或其他任何东西 测试用户的操作系统 浏览器是否支持给定的 url 方案 例如 大多数仅使用网络邮件的用户计算机上未设置 mailto 是否有可能以某种方式捕获单击 mailto 链接的尝试并弹出比浏览器错
  • 在为 RXJS 可观察量编写测试时,如何避免让调度程序通过我的业务逻辑?

    我发现使某些测试通过的唯一方法是显式地将调度程序传递给函数 为了便于说明 请考虑以下函数 function doStuff stream return stream delay 100 filter x gt x 2 0 map x gt
  • JavaScript 中数组的 HTML 数据列表值

    我有一个简单的程序 它必须从服务器上的文本文件中获取值 然后将数据列表填充为输入文本字段中的选择 为此 我想要采取的第一步是我想知道如何动态地将 JavaScript 数组用作数据列表选项 我的代码是
  • Typeahead.js substringMatcher 函数说明

    我只是在做一些研究Typeahead js这是一个非常酷的图书馆 感谢文档 我已经成功地获得了一个基本的示例 该文档也非常好 但是我试图弄清楚以下代码块实际上在做什么 var substringMatcher function strs r
  • 使用 JS 合并具有相同值的相邻 HTML 表格单元格

    我已经为此苦苦挣扎了一段时间 我有一个根据一些 JSON 数据自动生成的表 该数据可能会有所不同 我想合并第一列中具有相同值的相邻单元格 例如此表中的 鱼 和 鸟 table tr td fish td td salmon td tr tr
  • IE11不监听MSFullscreenChange事件

    我正在尝试使用 Bigscreen js 在 IE11 中使用全屏 但 IE11 不监听 MS FullscreenChange 事件 document addEventListener MSFullscreenChange functio
  • 将 javascript 整数转换为字节数组并返回

    function intFromBytes x var val 0 for var i 0 i lt x length i val x i if i lt x length 1 val val lt lt 8 return val func
  • Twitter 嵌入时间轴小部件

    我继续下载http platform twitter com widgets js http platform twitter com widgets js And the http platform twitter com embed t
  • Chartjs刻度标签位置

    尝试让 Y 轴刻度标签看起来像image https i stack imgur com XgoxX png 位于秤顶部且不旋转 缩放选项当前如下所示 scales yAxes id temp scaleLabel display true
  • 为什么“tbody”不设置表格的背景颜色?

    我在用 tbody 作为 CSS 选择器来设置background color在一个表中 我这样做是因为我有多个 tbody 表内的部分 它们具有不同的背景颜色 我的问题是 当使用border radius在细胞上 细胞不尊重backgro
  • 如何获取 UIWebView 中元素的位置?

    我在 iPad 程序中加载了 html 的 UIWebView 通过使用 webkit column width 我将 html 分为几列 padding 0px height 1024px webkit column gap 0px we
  • 数据表日期范围过滤器

    如何添加日期范围过滤器 like From To 我开始进行常规搜索和分页等工作 但我不知道如何制作日期范围过滤器 我正在使用数据表 1 10 11 版本 My code var oTable function callFilesTable
  • 如何在 javascript 正则表达式中匹配平衡分隔符?

    我原以为这个问题是不可能的 据我所知 Javascript 的正则表达式既没有递归插值 也没有漂亮的 NET 平衡组功能 但问题就在那里 如问题 12 所示正则表达式 alf nu http regex alf nu 匹配平衡对 lt an
  • 三级十进制有序列表 CSS

    我有一个 html 中的三级有序列表 我想为其提供如下样式 1 Item 1 1 1 Item 2 1 1 1 Item 3 下一个 plunker 中有一个 html 示例 http plnkr co edit DqhZ5pJILTUHG
  • 防止文本区域出现新行

    我正在开发聊天功能 使用 Vue 并使用文本区域作为输入 以便溢出换行 并且对于编写较长消息的用户来说更具可读性 不幸的是 当用户按下 Enter 键并提交时 光标会在提交之前移动到新行 从而使用户体验感觉不佳 关于如何使用普通 Javas
  • KeyboardAvoidingView - 隐藏键盘时重置高度

    我正在使用 React NativeKeyboardAvoidingView设置我的高度View当显示键盘时 但是当我关闭应用程序中的键盘时 视图的高度不会变回原来的值
  • 用于 C# XNA 的 Javascript(或类似)游戏脚本

    最近我准备用 XNA C 开发另一个游戏 上次我在 XNA C 中开发游戏时 遇到了必须向游戏中添加地图和可自定义数据的问题 每次我想添加新内容或更改游戏角色的某些值或其他内容时 我都必须重建整个游戏或其他内容 这可能需要相当长的时间 有没

随机推荐