调用javaScript物理引擎

时间:2016-08-19 15:32:50

标签: javascript game-physics

我编写了自己的HTML5画布 - 基于javaScript的物理引擎来模拟由弹簧连接的多个点。目前该计划的一般结构是

function init(){
// A bunch of event listeners
renderer();
physics();
}


var frameTime = 1;

function physics(){
  // iterate the physics
  parts.update();
  setTimeout(physics, frameTime);
}

// render loop
function renderer(){
  // draws a rectangle over the last frame
  drawBackground();
  // renders the objects themselves
  parts.draw();
  // update the timeout according to an onscreen slider
  frameTime = Math.ceil(101 - speed_r.value) / 2;
  setTimeout(renderer, 15);
}

两个不同循环背后的基本原理是人眼只需要看到60fps,但每秒做更多更新会产生更好的物理效果。

我自从做了更多研究后发现用javaScript渲染动画的标准方法是调用requestAnimationFrame(),据我所知,它具有在取消选择制表符时不渲染的优点,改善电池寿命。但是,由于双循环结构,物理将继续计算,并可能超过渲染器开销。

问题是:性能最佳的是什么,理想情况下最有效的方法是什么?

2 个答案:

答案 0 :(得分:5)

要将物理模拟与挂钟同步,并平滑渲染动画,您需要使用固定时间步插值。阅读this excellent article两个主题。

使用requestAnimationFrame是节省电池的好主意(如果大多数设备上的电池电量不足,它会降低帧速率)。您可以将它用于物理和渲染循环。 您需要做的是计算自上一帧以来经过的时间,然后使用零或多个固定步骤来保持物理循环与当前(挂钟)时间同步。这就是所有实时物理引擎的工作方式,包括Box2D和Bullet Physics。

根据上面提到的文章,我使用HTML5 Canvas和JavaScript创建了一个完整的JSFiddle,它实现了您的需求。请参阅下面的代码或open it on JSFiddle

integrate功能是您更新物理的地方。在代码中,它用于向前推进弹簧模拟。

var t = 0;
var dt = 0.01;

var currentTime;
var accumulator = 0;

var previousState = { x: 100, v: 0 };
var currentState = { x: 100, v: 0 };

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");

// start animation loop
requestAnimationFrame(animate);

function animate(newTime){
  requestAnimationFrame(animate);

  if (currentTime) {
    var frameTime = newTime - currentTime;
    if ( frameTime > 250 )
        frameTime = 250;
    accumulator += frameTime;

    while ( accumulator >= dt )
    {
        previousState = currentState;
        currentState = integrate( currentState, t, dt );
        t += dt;
        accumulator -= dt;
    }

    var alpha = accumulator / dt;
    var interpolatedPosition = currentState.x * alpha + previousState.x * (1 - alpha);

    render( interpolatedPosition );
  }

  currentTime = newTime;
}

// Move simulation forward
function integrate(state, time, fixedDeltaTime){
  var fixedDeltaTimeSeconds = fixedDeltaTime / 1000;
  var f = (200 - state.x) * 3;
  var v = state.v + f * fixedDeltaTimeSeconds;
  var x = state.x + v * fixedDeltaTimeSeconds;
  return { x: x, v: v };
}

// Render the scene
function render(position){
  // Clear
  ctx.fillStyle = 'white';
  ctx.fillRect(0,0,canvas.width,canvas.height);

  // Draw circle
  ctx.fillStyle = 'black';
  ctx.beginPath();
  ctx.arc(position,100,50,0,2*Math.PI);
  ctx.closePath();
  ctx.fill();
}

答案 1 :(得分:1)

我想我会考虑将物理部分放在web worker中,然后让它发布主UI线程的更新,这会在requestAnimationFrame回调上呈现它们。这允许物理代码不断运行(你甚至不需要setTimeout循环;虽然让它定期产生,所以它可以从前端访问消息 - 尤其是“停止”! - 会是一个好主意) ,同时只按实际需要更新显示。

2018更新:从ES2018开始,工作人员和主线程可以通过SharedArrayBufferAtomics对象的功能共享内存。它不是必须打破工作以便工作人员可以处理消息,而是可以只检查共享内存中的标志位置(例如,标记说它需要停止)。工作人员甚至可以在计算过程中暂停(即使在标准循环的中间,例如forwhile),然后通过Atomics.wait和{{ 1}}。