通过鼠标点击进入场景,在2d js画布上模拟物理3d球投掷

时间:2012-11-17 12:39:58

标签: javascript animation canvas physics

我想把一个球(带有一个图像)扔到一个二维场景中,当它到达一定距离时检查它是否有碰撞。但我无法正确地“飞行”。好像这已被问过一百万次,但随着我发现的越多,我得到的就越混乱...... 现在我跟着this answer,但看起来,球的表现与我预期的非常不同。事实上,它移动到我的画布的左上方并变得太快太快 - 我可以通过将vz设置为0.01或类似来调整这个,但是然后我根本看不到一个球...

这是我感兴趣的对象(简称)/链接到full source。重要的部分是update()和render()

var ball = function(x,y) {

  this.x        = x;
  this.y        = y;
  this.z        = 0;
  this.r        = 0;
  this.src      = 'img/ball.png';
  this.gravity  = -0.097;

  this.scaleX   = 1;
  this.scaleY   = 1;

  this.vx       = 0;
  this.vy       = 3.0;
  this.vz       = 5.0;

  this.isLoaded = false;

  // update is called inside window.requestAnimationFrame game loop
  this.update = function() {
    if(this.isLoaded) {
      // ball should fly 'into' the scene
      this.x += this.vx;
      this.y += this.vy;
      this.z += this.vz;

      // do more stuff like removing it when hit the ground or check for collision
      //this.r += ?

      this.vz += this.gravity;
    }
  };

  // render is called inside window.requestAnimationFrame game loop after this.update()
  this.render = function() {
    if(this.isLoaded) {

      var x       = this.x / this.z;
      var y       = this.y / this.z;

      this.scaleX = this.scaleX / this.z;
      this.scaleY = this.scaleY / this.z;

      var width   = this.img.width * this.scaleX;
      var height  = this.img.height * this.scaleY;

      canvasContext.drawImage(this.img, x, y, width, height);

    }
  };

  // load image
  var self      = this;
  this.img      = new Image();
  this.img.onLoad = function() {
    self.isLoaded = true;
    // update offset to spawn the ball in the middle of the click
    self.x        = this.width/2;
    self.y        = this.height/2;
    // set radius for collision detection because the ball is round
    self.r        = this.x;
  };
  this.img.src = this.src;

} 

我也想知道,当使用requestAnimationFrame渲染~60fps的画布时,速度的哪些参数应该是合适的,以获得“自然”的飞行动画

我非常感激,如果有人能指出我正确的方向(也使用伪代码解释课程的逻辑)。

由于

2 个答案:

答案 0 :(得分:1)

我认为最好的方法是首先在公制系统中模拟情况。

speed = 30; // 30 meters per second or 108 km/hour -- quite fast ...
angle = 30 * pi/180;  // 30 degree angle, moved to radians.

speed_x = speed * cos(angle);
speed_y = speed * sin(angle);  // now you have initial direction vector

x_coord = 0;
y_coord = 0;  // assuming quadrant 1 of traditional cartesian coordinate system

time_step = 1.0/60.0;    // every frame...

// at most 100 meters and while not below ground
while (y_coord > 0 && x_coord < 100) {

   x_coord += speed_x * time_step;
   y_coord += speed_y * time_step;

   speed_y -= 9.81 * time_step;   // in one second the speed has changed 9.81m/s

   // Final stage: ball shape, mass and viscosity of air causes a counter force
   // that is proportional to the speed of the object. This is a funny part:
   // just multiply each speed component separately by a factor (< 1.0)
   // (You can calculate the actual factor by noticing that there is a limit for speed
   //  speed == (speed - 9.81 * time_step)*0.99, called _terminal velocity_
   // if you know or guesstimate that, you don't need to remember _rho_,
   // projected Area or any other terms for the counter force.

   speed_x *= 0.99; speed_y *=0.99;
}

现在你将有一个时间/位置系列,从0,0开始(你可以用Excel或OpenOffice Calc计算)

speed_x        speed_y       position_x     position_y    time 
25,9807687475  14,9999885096 0              0             0 
25,72096106    14,6881236245 0,4286826843   0,2448020604  1 / 60
25,4637514494  14,3793773883 0,8530785418   0,4844583502  2 / 60
25,2091139349  14,0737186144 1,2732304407   0,7190203271
...
5,9296028059   -9,0687933774 33,0844238036  0,0565651137  147 / 60
5,8703067779   -9,1399704437 33,1822622499 -0,0957677271  148 / 60

从该表中可以首先估计击球的距离和时间。 它们分别为33,08米和2.45秒(或148帧)。通过在excel中继续模拟,人们还注意到终端速度将达到~58 km / h,这并不多。

确定最终速度为60 m / s或216 km / h是正确的,正确的衰减系数为0,9972824054451614。

现在唯一剩下的任务是决定屏幕的长度(以米为单位),并将pos_x,pos_y与正确的比例因子相乘。如果1024像素的屏幕是32米,那么每个像素将对应于3.125厘米。根据应用,人们可能希望“改善”现实并使球更大。

编辑:另一件事是如何在3D上投影。我建议你将前一个算法(或excel)生成的路径作为一个可见对象(由线段组成),你可以旋转和放大。平移。

答案 1 :(得分:1)

您所看到的不良行为的起因是您使用的投影,以(0,0)为中心,而且通常过于简单,看起来不太好。
你需要一个更完整的投影,包括中心,比例,... 我使用那个添加一点3d:

     projectOnScreen : function(wx,wy,wz) {
            var screenX =   ... real X size of your canvas here ... ;
            var screenY =  ... real Y size of your canvas here ... ;
            var scale   = ... the scale you use between world / screen coordinates ...;
            var ZOffset=3000; // the bigger, the less z has effet
            var k =ZOffset;  // coeficient to have projected point = point for z=0
            var zScale =2.0; // the bigger, the more a change in Z will have effect

            var worldCenterX=screenX/(2*scale);
            var worldCenterY=screenY/(2*scale);


            var sizeAt = ig.system.scale*k/(ZOffset+zScale*wz);
            return {
                     x: screenX/2  +  sizeAt * (wx-worldCenterX) ,
                     y: screenY/2  +  sizeAt * (wy-worldCenterY) ,
                     sizeAt : sizeAt
                  }
        }

显然,你可以根据你的游戏进行优化。例如,如果分辨率和比例不变,您可以在该功能之外计算一次参数 sizeAt是您必须应用于图像的缩放系数(canvas.scale)。

编辑:对于你的更新/渲染代码,正如Aki Suihkonen的帖子所指出的,你需要使用'dt',两次更新之间的时间。因此,如果您稍后更改每秒帧数(fps)或者如果您在游戏中暂时减速,则可以更改dt并且所有内容的行为仍然相同。
等式变为x + = vx * dt / ... / vx + = gravity * dt;
无论屏幕大小如何,您都应该具有相对于屏幕高度计算的速度和重力,以获得相同的行为 我也会使用负z来开始。首先要有一个更大的球。 我也会分开关注:
- 分别处理图像的加载。您的游戏应在加载所有必要资产后开始。一些免费的小框架可以为您做很多事情。举个例子:crafty.js,但有很多好的 - 相对于点击位置的调整和图像大小应在渲染中完成,而x,y只是鼠标坐标。

var currWidth = this.width *scaleAt, currHeight= this.height*scaleAt;
canvasContext.drawImage(this.img, x-currWidth/2, y-currHeight/2, currWidth, currHeight);

或者您可以使用画布来进行缩放。奖金是你可以这样轻松旋转:

 ctx.save();
 ctx.translate(x,y);
 ctx.scale(scaleAt, scaleAt);  // or scaleAt * worldToScreenScale if you have 
                               // a scaling factor
 // ctx.rotate(someAngle);   // if you want...
 ctx.drawImage(this.img, x-this.width/2, x-this.height/2);
 ctx.restore();