Node.js 制作实时多人游戏框架

2020-06-17 06:55:03易采站长站整理

使用 Chromium 的 setTimeout 实现,我们可以将 setTimeout(fn, 1) 的误差控制在 4ms 左右,而 setTimeout(fn, 48) 的误差可以控制在 1ms 左右。于是,我们的心中有了一幅新的蓝图,它让我们的代码看起来像是这样:


/* Get the max interval deviation */ 
var deviation = getMaxIntervalDeviation(bucketSize); // bucketSsize = 48, deviation = 2; 
function gameLoop() { 
  var now = Date.now(); 
  if (previousBucket + bucketSize <= now) { 
    previousBucket = now; 
    doLogic(); 
  } 
  if (previousBucket + bucketSize – Date.now() > deviation) { 
    // Wait 46ms. The actual delay is less than 48ms. 
    setTimeout(gameLoop, bucketSize – deviation); 
  } else { 
    // Busy waiting. Use setImmediate instead of process.nextTick because the former does not block IO events. 
    setImmediate(gameLoop); 
  } 

上面的代码让我们等待一个误差小于 bucket_size( bucket_size – deviation) 的时间而不是直接等于一个 bucket_size,46ms 的 delay 即便发生了最大的误差,根据上文的理论,实际间隔也是小于48ms的。剩下的时间我们使用忙等待的方法,确保我们的 gameLoop 在足够精确的 interval 下执行。

虽然我们利用 Chromium 在一定程度上解决了问题,但这显然不够优雅。

还记得我们最初的要求吗?我们的服务器端代码是应该可以脱离 Node-Webkit 客户端的,直接在一台有 Node.js 环境的电脑中运行。如果直接跑上面的代码,deviation 的值至少是16ms,也就是说在每一个48ms中,我们要忙等待16ms的时间。CPU使用率蹭蹭蹭就上去了。

意想不到的惊喜

真是气人啊,Node.js 里这么大的一个BUG,没有人注意到吗?答案真是让我们喜出望外。这个BUG在 v.0.11.3 版本里已经得到了修复。直接查看 libuv 代码的 master 分支也能看到修改后的结果。具体的做法是,在 poll 函数等待完成之后,把 loop 的当前时间,加上一个 timeout。这样即便 GetTickCount 没有反应过来,在经过poll的等待之后,我们还是加上了这段等待的时间。于是计时器就能够顺利地到期了。

也就是说,辛苦半天的问题,在 v.0.11.3 里已经得到了解决。不过,我们的努力不是白费的。因为即便消除了 GetTickCount 函数的影响,poll 函数本身也受到系统定时器的影响。解决方案之一,便是编写 Node.js 插件,更改系统定时器的间隔。

不过我们这次的游戏,初步设定是没有服务器的。客户端建立房间之后,就成为了一个服务器。服务器代码可以跑在 Node-WebKit 的环境中,所以 Windows 系统下计时器的问题的优先级并不是最高的。按照上文中我们给出的解决方案,结果已经足够让我们满意。