我正在尝试编写我的第一个html5游戏。但是,游戏循环导致我的浏览器无响应(最终被浏览器关闭)。我创建了一个状态机:
while(state != State.EXIT){
switch(state){
case State.SPLASH:
break;
case State.HOW_TO:
break;
case State.PLAY:
oldTime=Date.now();
state=gameLoop();
break;
case State.GAME_OVER:
break;
default:
state=State.EXIT;
}
}
这似乎工作正常。那么,这就是游戏循环:
function gameLoop(){
var newTime=Date.now();
var delta=newTime-oldTime;
update(delta/1000);
render();
oldTime=newTime;
return state;
}
这是发生崩溃的地方。如果我取出return语句,它将返回null或javascript返回的任何内容。而且,那没关系。它运行一次并退出。但是,如果我把它放在那里,这就是浏览器占用的地方。更新功能使我的角色能够移动,渲染功能将一个图像绘制到屏幕上。非常简单的东西。
注意:如果重要的话,这将写在canvas元素中。
解!我创建了一个stateSelector()函数,它包含上面的switch语句(没有while)。但是,我使用的是interval = setInterval(gameLoop,1),而不是state = gameLoop。然后,当我想要停止时,我使用clearInterval(interval),然后立即使用stateSelector()。显然,如果我想改变状态,我会在调用stateSelector函数之前这样做。我可能会接受一个包含我想要进入的状态的参数,但这是我稍后可以评估的一个小变化。我只想宣布我的解决方案,以防其他人遇到此问题。
答案 0 :(得分:3)
JavaScript是单线程的,并且在所有常见浏览器环境中的GUI线程中运行(实际上)。当您使用JavaScript时,在JavaScript完成运行之前,浏览器的UI不会更新。
您正在使用永远不会完成的while
循环,因此UI永远不会更新。要解决这个问题,你需要重新构建一个:渲染一个框架,然后告诉浏览器你想尽快渲染另一个框架;它可以更新UI并执行其他浏览操作,然后它可以返回给您渲染另一个框架。
有一个名为requestAnimationFrame
的实验性新功能可以做到这一点。由于它仍然是实验性的,要使用它,您需要检查它的浏览器特定版本,或者如果它根本不可用,则提供后备。以下是浏览器特定版本的一些名称:
mozRequestAnimationFrame
适用于Gecko(火狐)webkitRequestAnimationFrame
适用于WebKit(Chrome和Safari)msRequestAnimationFrame
for Trident(Internet Explorer)因此,如果有一个没有前缀的requestAnimationFrame
可用,请使用它。如果没有,但是前缀是,请使用它。如果这些都不起作用,您可以使用后备:
function fallbackRequestAnimationFrame(func) {
setTimeout(func, 10); // Schedule func to be run in 10 milliseconds.
}
以下是代码found on MDN的略微修改版本:
var myRequestAnimationFrame =
window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame
|| fallbackRequestAnimationFrame;
一旦你弄清楚你可以使用哪个requestAnimationFrame
函数,就可以改变你的游戏循环(这似乎不是gameLoop
函数,它没有循环,而是{{ 1}} loop)看起来像这样:
while
然后开始:
function runFrame() {
switch(state) {
// state handling code
}
if(state != State.EXIT) {
myRequestAnimationFrame(runFrame);
}
}
答案 1 :(得分:0)
我认为你可能需要某种暂停,如果你循环没有暂停,它将消耗所有CPU反复处理循环,阻止页面呈现。
答案 2 :(得分:0)
通常情况下,程序员通过添加sleep(s)
或yield()
调用来使循环播放变得很好,但由于javascript的事件驱动模型缺少这些调用,因此您可以使用可调用的setInterval()
替换循环一个函数,包含每个指定间隔的循环体,比如每33毫秒一次,体验30 fps。
答案 3 :(得分:0)
JavaScript在浏览器用于呈现页面的同一个线程上运行,因此如果您编写无限循环,浏览器将永远无法控制回刷新页面。 (现代浏览器会检测“长时间运行”循环并为用户提供中止它们的机会,但这对您的游戏没有帮助。)
您需要使用setTimeout()
或setInterval()
以及以下内容的一些变体:
function gameLoop() {
// do calculations
// render
// etc
if (!gameOver)
setTimeout(gameLoop, 30);
}
gameLoop();
(注意:你的原始gameLoop()
函数实际上并没有循环 - 你的循环是在函数之外控制的 - 而我刚刚显示的函数循环。)
setTimeout()
函数将函数排队等待稍后运行,然后立即继续下一行代码。当前代码完成执行浏览器然后获得控制权以更新显示等。然后在(大约)指定的间隔(以毫秒为单位)后执行排队的函数。
上面的效果类似于一个函数直接调用自身的递归调用,除非使用setTimeout()
在此期间将控制权交还给浏览器。
外部 gameLoop()
函数,您可以为键和/或鼠标事件定义事件处理程序,并拥有gameLoop()
将用于决定如何的更新变量,例如,移动玩家的角色,就像我在this answer to another question中说的那样。