0

我想将一个球(带有图像)扔到一个二维场景中,并在它到达一定距离时检查它是否发生碰撞。但我不能让它正确地“飞”起来。似乎这个问题已经被问了一百万次了,但是我发现的越多,我就越困惑。现在我遵循了这个答案,但看起来,球的行为与我预期的非常不同。事实上,它移动到我的画布的左上角并且变得太小太快了——当然我可以通过将 vz 设置为 0.01 或类似的值来调整它,但是我根本看不到球……

这是我的对象(简化)/链接到感兴趣的完整来源。重要的部分是 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 的速度渲染画布时,哪些速度参数应该是合适的,以获得“自然”的飞行动画

如果有人能指出我正确的方向(当然还有解释逻辑的伪代码),我将不胜感激。

谢谢

4

2 回答 2

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 公里/小时,这并不多。

确定 60 m/s 或 216 km/h 的终端速度是合适的,正确的衰减因子将是 0,9972824054451614。

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

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

于 2012-11-17T14:45:30.613 回答
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();
于 2012-11-17T15:06:35.773 回答