我正在制作游戏(shmup),我开始质疑ActionScript中计时器的准确性。当然,当你想要在几秒或几秒的时间内完成时间时,它们是足够准确的,但是当你达到更精细的范围时它似乎表现得非常差。这使得像飞船每秒发射一百个激光器这样的事情非常困难。
在下面的示例中,我测试了间隔在1000个计时器刻度之间的时间长度(平均)为30ms。一次又一次,结果是~35-36ms。减少时间,我发现定时器延迟时间为~16-17ms。这给了我一个~60的最大fps,这在视觉上很棒,但也意味着我不可能每秒发射超过60个激光:-(。我在100和1000次循环中运行了几次这个测试,但对于两者而言30ms测试和1ms测试结果没有改变。我最后打印到textField,因为使用trace()并在调试模式下启动swf似乎会对测试产生负面影响。所以我想知道的是:
我知道这取决于getTimer()方法的准确性,但我在这个主题上发现的讨论通常围绕getTimer()在更大间隔内的准确性。
package {
import flash.display.Sprite; import flash.events.TimerEvent; import flash.text.TextField; import flash.utils.getTimer; import flash.utils.Timer;
public class testTimerClass extends Sprite
{
private var testTimer:Timer = new Timer(30, 1000);
private var testTimes:Array = new Array();
private var testSum:int = 0;
private var testAvg:Number;
private var lastTime:int;
private var thisTime:int;
public function testTimerClass()
{
testTimer.addEventListener(TimerEvent.TIMER, step);
testTimer.addEventListener(TimerEvent.TIMER_COMPLETE, printResults);
lastTime = getTimer();
testTimer.start();
}
private function step(event:TimerEvent):void
{
thisTime = getTimer();
testTimes.push(thisTime - lastTime);
lastTime = thisTime;
}
private function printResults(event:TimerEvent):void
{
while (testTimes.length > 0)
{
testSum += testTimes.pop();
}
testAvg = testSum / Number(testTimer.repeatCount);
var txtTestResults:TextField = new TextField();
txtTestResults.text = testAvg.toString();
this.addChild(txtTestResults);
}
}
}
我想最好的选择是在不同位置的同一帧中绘制多个激光,避免使用多个Timer对象。
编辑:我使用stage.frameRate更改渲染帧速率并在几个帧速率上运行测试,但没有变化。
答案 0 :(得分:4)
Tinic Uro(Flash Player工程师)不久前写了关于这个问题的interesting blog帖子。
答案 1 :(得分:2)
完全使用计时器类的Intead,我会将更新代码放入并输入frame eventlistener。
然后使用getTimer(),找出自上次更新以来已经过了多少时间,然后用一点点数学来计算运动,激光“产生”等等。这样游戏应该执行相同的操作,无论是否你得到60FPS或10FPS。
如果FPS暂时掉落,或游戏在慢速机器上运行,这也可以防止出现奇怪的游戏行为。
在大多数情况下,数学很简单,如果不是,你通常可以“偷工减料”并且仍能获得足够好的游戏结果。 对于简单的移动,您可以简单地将移动偏移与自上次更新后的时间相乘。对于加速度,您需要二阶方程,但可以简化再次乘法。
回应下面的jorelli评论:
碰撞检测问题可以通过更好的代码来解决,这不仅可以检查当前状态,还可以检查先前状态和当前状态之间发生的情况。 我从未遇到过键盘问题。也许你应该尝试这个漂亮的小班: http://www.bigroom.co.uk/blog/polling-the-keyboard-in-actionscript-3
答案 2 :(得分:1)
我只是试着运行你的示例代码,除了作为一个框架脚本,我正坐在那里Timer正如你期望的那样工作。使用30ms定时器,平均出现在33-34左右,而3ms定时器出现在3.4或3.5左右。使用1 ms计时器,我在一千次试验中获得1.4到1.6之间的计时器。它在Flash和浏览器中以这种方式工作。
至于准确性,请参阅Tinic在Luke的答案中的博客文章。但是对于频率的上限,如果你的事件没有超过16毫秒,只有你发布的示例代码,或者某些东西很奇怪,或者这可能是浏览器提供Flash定时消息的速度的上限。如果您在实际游戏中获得这些结果,我认为您只是拥有阻止计时器事件的同步代码。
还有一件事 - 我知道这不是你问的问题,但在ENTER_FRAME处理程序中处理你的游戏逻辑会更明智。产生激光的代码是每隔30ms还是每3ms运行一次并不重要,屏幕每帧只重绘一次(除非你强迫它更频繁地更新,你可能不应该这样)。因此,屏幕刷新之间发生的任何事情只是开销会降低整体帧率,因为有一点聪明,它应该可以实现与从更频繁执行的计时器获得的完全相同的结果。例如,不是每3ms产生一次激光,而是每30ms产生10个激光,并沿着它的飞行路径将每个激光移动一段距离,就像它早先创建3或6或9ms一样。
运行逻辑关闭帧事件的另一种自然方法是制作一个“理论上”每T毫秒运行一次的游戏循环。然后,在ENTER_FRAME
处理程序中,简单地迭代该循环F / T次,其中F是自上一帧事件以来经过的ms数。因此,如果你希望游戏理论上每5ms更新一次,并且你希望屏幕以30FPS更新,你只需要以30FPS发布,并在你的ENTER_FRAME
处理程序中调用你的主游戏循环5-7连续时间,取决于自上一帧以来的时间长度。屏幕上,这将给你相同的结果,如果你使用了5毫秒计时器事件,它将免除大部分开销。
答案 3 :(得分:1)
BitmapData.scroll和BitmapData.copyPixels是目前在Flash中渲染的最快方式,尽管在少数系统上,使用具有gpu加速的动画片段稍快一些。每个四边形在OpenGL中呈现为单独的纹理,因此它不是您对普通GPU加速应用程序的期望。我知道,因为我在游戏中描述了两种方式。您可以做的一件事是改善响应时间是从输入事件处理程序调用您的游戏更新。这样,即使您的游戏帧速率较低,您也会听到正确的定时声音,并且应用程序会感觉更具响应性。
答案 4 :(得分:0)
这给了我最大fps~60,这在视觉上很棒,但也意味着我不可能每秒发射超过60个激光: - (
我会说你很幸运能够获得那种FPS,并且你的大多数用户(假设你的观众是整个互联网)很可能不会达到那种帧率。
我同意闪存播放器的功能可能不足以满足您的目标。