优化 2D Flash 游戏的过渡/移动平滑度 [英] Optimizing transition/movement smoothness for a 2D flash game

查看:28
本文介绍了优化 2D Flash 游戏的过渡/移动平滑度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新 6:

Fenomenas 建议我尽可能简单地重新创建所有内容.我怀疑这会产生什么不同,因为算法保持不变,而且性能似乎不是问题.无论如何,这是我得到的唯一建议,所以这里是:

  1. 30 FPS:http://www.feedpostal.com/test/simple/30/SimpleMovement.html
  2. 40 FPS:http://www.feedpostal.com/test/simple/40/SimpleMovement.html
  3. 60 FPS:http://www.feedpostal.com/test/simple/60/SimpleMovement.html
  4. 100 FPS:http://www.feedpostal.com/test/simple/100/SimpleMovement.html

代码:

包{导入 flash.display.Sprite;导入 flash.events.Event;导入 flash.events.KeyboardEvent;导入 flash.utils.getTimer;[SWF(width="800", height="600", frameRate="40", backgroundColor="#000000")]公共类 SimpleMovement 扩展 Sprite{私有静态常量 TURNING_SPEED:uint = 180;私有静态常量 MOVEMENT_SPEED:uint = 400;private static const RADIAN_DIVIDE:Number = Math.PI/180;私有变量 playerObject:Sprite;私人 var shipContainer:Sprite;私有变量移动:Boolean = false;私人无功车削模式:uint = 0;私有变量移动时间戳:数字 = getTimer();私有变量变成时间戳:数字 = 移动时间戳;公共函数 SimpleMovement(){//第一步:创建玩家对象playerObject = new Sprite();playerObject.graphics.lineStyle(1, 0x000000);playerObject.graphics.beginFill(0x6D7B8D);playerObject.graphics.drawRect(0, 0, 25, 50);//让它围绕中心旋转playerObject.x = 0 - playerObject.width/2;playerObject.y = 0 - playerObject.height/2;shipContainer = new Sprite();shipContainer.addChild(playerObject);shipContainer.x = 100;shipContainer.y = 100;shipContainer.rotation = 180;addChild(shipContainer);//第2步:当舞台准备好时安装键盘钩子addEventListener(Event.ADDED_TO_STAGE, stageReady, false, 0, true);//第3步:安装渲染更新轮询addEventListener(Event.ENTER_FRAME, updatePoller, false, 0, true);}私有函数 updatePoller(event:Event):void{var newTime:Number = getTimer();//转向如果(车削模式!= 0){var TurningDeltaTime:Number = newTime - TurningTimestamp;转动时间戳 = 新时间;var rotation:Number = TURNING_SPEED * turnDeltaTime/1000;if (turningMode == 1) shipContainer.rotation -= 旋转;否则 shipContainer.rotation += 旋转;}//移动如果(移动){var moveDeltaTime:Number = newTime - movementTimestamp;运动时间戳 = 新时间;var distance:Number = MOVEMENT_SPEED * movementDeltaTime/1000;var rAngle:Number = shipContainer.rotation * RADIAN_DIVIDE;//将度数转换为弧度shipContainer.x += distance * Math.sin(rAngle);shipContainer.y -= 距离 * Math.cos(rAngle);}}私有函数 stageReady(event:Event):void{//安装键盘钩子stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown, false, 0, true);stage.addEventListener(KeyboardEvent.KEY_UP, keyUp, false, 0, true);}私有最终函数 keyDown(event:KeyboardEvent):void{if ((event.keyCode == 87) && (!moving))//87 = W{运动时间戳 = getTimer();移动 = 真;}if ((event.keyCode == 65) && (turningMode != 1))//65 = A{转动时间戳 = getTimer();车削模式 = 1;}else if ((event.keyCode == 68) && (turningMode != 2))//68 = D{转动时间戳 = getTimer();车削模式 = 2;}}私有最终函数 keyUp(event:KeyboardEvent):void{if ((event.keyCode == 87) && (moving))moving = false;//87 = Wif (((event.keyCode == 65) || (event.keyCode == 68)) && (turningMode != 0)) turningMode = 0;//65 = A, 68 = D}}}

结果如我所料.绝对没有改善.我真的希望有人有另一个建议,因为这件事需要修复.另外,我怀疑这是我的系统,因为我有一个很好的系统(8GB RAM、Q9550 QuadCore intel、ATI Radeon 4870 512MB).此外,到目前为止我问过的其他人都与我的客户有同样的问题.

更新 5: 另一个流畅的 Flash 游戏示例,只是为了证明我的动作绝对不同!参见 http://www.spel.nl/game/bumpercraft.html>

更新 4:我跟踪渲染前 (EVENT.RENDER) 和渲染后 (EVENT.ENTER_FRAME) 的时间,结果:

渲染耗时:14 ms渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:14 毫秒渲染耗时:14 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:24 毫秒渲染耗时:18 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:232 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:16 毫秒渲染耗时:12 毫秒渲染耗时:14 毫秒渲染耗时:12 毫秒

范围是 12-16 毫秒.在这些差异中,震撼/扭曲/闪烁的运动已经在进行.还有1个232ms的峰值,此时出现了比较大的翘曲.然而这还不是最大的问题,最大的问题是在正常运动过程中会出现连续的小扭曲.这是否会给任何人提供线索?

更新 3: 经过测试,我知道以下因素不会导致我的问题:

  • 位图的质量 -> 用 Photoshop 更改为更丑陋的 8 色优化图形,完全没有改进.
  • 旋转时图像不断旋转 -> 禁用它,根本没有改善
  • 浏览器渲染 -> 尝试单独使用 flash 播放器,没有任何改进

我 100% 确信问题出在我的代码或算法中.请帮帮我现在已经快两周了(我在 SO 上问了这个问题 1 周),我仍然需要得到我的黄金答案.

更新 1:请参阅底部的完整 flex 项目源代码和演示我的问题的现场演示.

我正在制作 2d Flash 游戏.玩家船被创建为一个对象:

ships[id] = new GameShip();

当移动和旋转信息可用时,这将被定向到相应的船:

ships[id].setMovementMode(1);//向前移动

现在,在这个 GameShip 对象移动中使用Event.ENTER_FRAME"事件:

addEventListener(Event.ENTER_FRAME,motionHandler);

然后正在运行以下函数:

私有最终函数movementHandler(event:Event):void{var newTimeStamp:uint = UtilLib.getTimeStamp();//设置当前时间戳var distance:Number = (newTimeStamp - MovementTimeStamp)/1000 * MovementSpeed;//速度=每1秒向前x个像素移动时间戳 = 新时间戳;//更新旧时间戳var diagonalChange:Array = getDiagonalChange(movementAngle, distance);//根据角度和距离更新对角线位置charX += diagonalChange[0];charY += diagonalChange[1];如果(船舶集装箱){//当容器准备好使用时shipContainer.x = charX;shipContainer.y = charY;}}私有最终函数 getDiagonalChange(angle:Number, distance:Number):Array{var rAngle:Number = 角度 * Math.PI/180;//将度数转换为弧度return [Math.sin(rAngle) * distance, (Math.cos(rAngle) * distance) * -1];}

当对象不再移动时,事件监听器将被移除.相同的方法用于旋转.一切都近乎完美.

我已将项目的目标 FPS 设置为 100 并创建了一个 FPS 计数器.根据FPS计数器,firefox的平均FPS在100左右,最高1000,最低22.我认为最低和最高FPS只发生在客户端初始化(启动)期间.

问题是这艘船看起来几乎完全光滑,而应该只是没有几乎"部分.就好像这艘船闪烁"得非常快,您实际上看不到它,但是当您的眼睛移动时,很难将注意力集中在物体上.此外,似乎时不时会出现一些帧率峰值,好像客户端跳过了几帧,然后您会看到它迅速扭曲.

很难解释真正的问题是什么,但总的来说,运动并不完全流畅.那么,对于如何让物体的移动或过渡完美平滑,您有什么建议吗?

更新 1:

我重新创建了客户端来演示我的问题.请检查一下.

客户: http://feedpostal.com/test/MovementTest.html

Actionscript 项目(完整源代码): http://feedpostal.com/test/MovementTest.rar

流畅的 Flash 游戏示例(不是我创建的):http://www.gamesforwork.com/games/swf/Mission%20Racing_august_10th_2009.swf

我花了很长时间重新创建这个客户端版本,我希望这有助于解决问题.

请注意:是的,它实际上非常流畅.但是肯定不够流畅.

解决方案

我不知道这里是否有最佳答案,但我确实有一些建议.

首先,我不会对优化 Math.PI/180 等问题进行任何调查.普遍较高的帧率应该表明简单的计算不会减慢任何速度.

其次,解决偶尔出现的显示延迟峰值:在我看来,这些非常类似于垃圾收集器非常频繁地运行.在非常简短地查看您的代码时,我没有看到频繁 GC 的任何明显原因,但我确实有两个建议.首先,如果您可以访问 Flash IDE,我会尝试在不使用 Flex 框架的情况下重新创建您的项目.Flash 项目除了您放入的内容外不包含任何代码,但 Flex 使用了许多自己的神秘之处,这些神秘之处可能并不总是很明显,并且您的代码和框架之间的某些交互可能会导致 GC.

如果这没有帮助,另一件要尝试的事情是制作一个大大简化的代码版本(如果可能,在 Flash 中),希望它足够简单,不会触发相同的峰值.我的意思是,例如,附加到图形的单个类,它只有一个关键事件侦听器和第二个帧(或计时器)事件侦听器,其中没有创建任何变量.如果最小版本没有显示这些峰值,那么应该可以在它和完整客户端之间进行三角测量,找出导致峰值的原因.

最后,关于一般的平滑度,我唯一的评论是 Flash 的屏幕更新本质上略有不均匀,实际上您只有两种方法可用.您可以根据帧更新移动演员,这会使他们的运动随着帧率的变化而略微不均匀,或者根据经过的时间移动它们,这会使他们的整体运动变得平滑(以每秒像素为单位)但它们的显示略微不均匀(以移动的像素为单位)每帧).这种差异在更高的 FPS 中被放大.

此外,请务必记住,在 Flash 进行更新后,它们在您屏幕上的显示方式在很大程度上受您的显卡的影响.特别是您会发现剪切和垂直同步问题在一种环境中非常明显,而在另一种环境中则不存在.开发人员没有真正的方法可以解决这个问题,除非通常避免非常高的 FPS 动画并尽可能降低整体处理器负担.

有关帧更新时间本质上不均匀"的含义的更多信息,请参阅这篇博文.屏幕更新之间的延迟在 12 到 16 毫秒之间变化,您对此无能为力;这是操作系统和浏览器影响 Flash 计时工作方式的结果.(这也是您即使在空电影中也会看到的东西,这就是为什么该线程中关于优化数学等的许多评论对您没有帮助.)您无法避免这种变化,但正如我上面说了,你可以定制你的视觉效果来唤起你想要的效果.无论哪种方式,我认为尖峰更值得担心.您看到的变化是微妙的,在有很多事情发生的游戏中很难注意到,但峰值非常惊人.

编辑 2你问:你真的认为那些流畅的游戏使用和我一样的运动算法吗?"

答案是,我认为他们正在做的事情要简单得多.他们几乎肯定会执行以下操作之一:

function onEnterFrame() {//每帧以恒定速度移动船角 += dtheta;ship.x += speed * Math.cos( ship.angle );ship.y += speed * Math.sin( ship.angle );}function onEnterFrame2() {//以每秒恒定的速度移动var dt:Number = getTimeSinceLastFrame();ship.angle +=anglePerSecond * dt/1000;var dist:Number = speedPerSecond * dt/1000;ship.x += dist * Math.cos( ship.angle );ship.y += dist * Math.sin( ship.angle );}

换句话说,要么每帧移动一个恒定距离,要么每秒移动一个恒定距离.这是您可以解决此问题的两种最简单方法,并且是两种可在 Flash 中获得最流畅外观的选项.它们在恒定帧速率下看起来相同,而后一种方法在稍微变化的帧速率下看起来更平滑,原因类似于您链接的文章中提到的时间混叠".但是这些方法之间的选择实际上归结为,如果发生 CPU 峰值,在它结束后您是否希望船继续移动?这真的是一个游戏设计问题.我过去做过的一件事是使用第二种方法,同时将 dt 限制为理想帧持续时间 (1/fps) 的 2 或 3 倍.

您可能已经注意到,我刚刚推荐的两种方法正是修复您的时间步长!"文章说不做.这是因为那篇文章是关于数值集成的物理引擎,而这不是你正在做的.如果您开始实施弹簧和重力,那么是的,每当时间步长变大时,它都会引入很多错误,因为对于这种模拟,为了过度简化事情,错误取决于时间步长的大小.在你正在做的事情中,它没有,所以偶尔的大时间步长不会影响模拟的正确性.

回复更新 6

首先,我没有告诉你你的问题是性能,我特意说反了.我建议进行最少的复制,因为我相信您的问题要么在您的项目中的其他地方,要么是不可避免的,我仍然这样做.其次,我现在很自在地说你在做任何其他 Flash 游戏一样的事情,你看到的任何问题都无法解决,只能在感知上解决.在您发布的新链接中,如果我在独立的 SWF 播放器中查看动画,动画看起来非常流畅,并且在浏览器的前后边缘有微妙的闪烁(在 Firefox 中比在 IE 中更是如此).从技术上讲,我不认为它可以改进(特别是当它在独立播放器中基本完美时,这意味着浏览器中的任何波动都会受到容器的影响.)

当然,感知性能仍然可以提高.例如,如果船与背景的对比不那么强烈,闪烁就会不那么明显.此外,简单地让船移动得更慢会使运动看起来更平滑,并且可以与移动的背景相结合,以提供速度更快的错觉(如您的一个示例所做的那样).

作为完整性检查,这是我在 IDE 中制作的一个类似的最小版本.http://www.fenomas.com/random/ship/性能和你在我机器上的性能相当,正如我所说,我真的没有看到任何问题.(除了偶尔出现的峰值,我现在注意到只有在 Firefox 中才会出现这种情况.)同样,尤其是在独立播放器中这两个版本对我来说基本完美的事实进一步让我相信这里没有黄金算法.我知道这不是你想要的答案,但这是我得到的答案.

Update 6:

Fenomenas suggested me to re-create everything as simple as possible. I had my doubts that this would make any difference as the algorithm remains the same, and performance did not seem to be the issue. Anyway, it was the only suggestion I got so here it is:

  1. 30 FPS: http://www.feedpostal.com/test/simple/30/SimpleMovement.html
  2. 40 FPS: http://www.feedpostal.com/test/simple/40/SimpleMovement.html
  3. 60 FPS: http://www.feedpostal.com/test/simple/60/SimpleMovement.html
  4. 100 FPS: http://www.feedpostal.com/test/simple/100/SimpleMovement.html

The code:

package {
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.utils.getTimer;

    [SWF(width="800", height="600", frameRate="40", backgroundColor="#000000")]

    public class SimpleMovement extends Sprite
    {
        private static const TURNING_SPEED:uint = 180;
        private static const MOVEMENT_SPEED:uint = 400;
        private static const RADIAN_DIVIDE:Number = Math.PI/180;
        private var playerObject:Sprite;
        private var shipContainer:Sprite;
        private var moving:Boolean = false;
        private var turningMode:uint = 0;
        private var movementTimestamp:Number = getTimer();
        private var turningTimestamp:Number = movementTimestamp;

        public function SimpleMovement()
        {
            //step 1: create player object
            playerObject = new Sprite();
            playerObject.graphics.lineStyle(1, 0x000000);
            playerObject.graphics.beginFill(0x6D7B8D);
            playerObject.graphics.drawRect(0, 0, 25, 50);
            //make it rotate around the center
            playerObject.x = 0 - playerObject.width / 2;
            playerObject.y = 0 - playerObject.height / 2;
            shipContainer = new Sprite();
            shipContainer.addChild(playerObject);
            shipContainer.x = 100;
            shipContainer.y = 100;
            shipContainer.rotation = 180;
            addChild(shipContainer);

            //step 2: install keyboard hook when stage is ready
            addEventListener(Event.ADDED_TO_STAGE, stageReady, false, 0, true);

            //step 3: install rendering update poll
            addEventListener(Event.ENTER_FRAME, updatePoller, false, 0, true);
        }

        private function updatePoller(event:Event):void
        {
            var newTime:Number = getTimer();

            //turning
            if (turningMode != 0)
            {

                var turningDeltaTime:Number = newTime - turningTimestamp;
                turningTimestamp = newTime;
                var rotation:Number = TURNING_SPEED * turningDeltaTime / 1000;
                if (turningMode == 1) shipContainer.rotation -= rotation;
                else shipContainer.rotation += rotation;
            }

            //movement
            if (moving)
            {
                var movementDeltaTime:Number = newTime - movementTimestamp;
                movementTimestamp = newTime;
                var distance:Number = MOVEMENT_SPEED * movementDeltaTime / 1000;
                var rAngle:Number = shipContainer.rotation * RADIAN_DIVIDE; //convert degrees to radian
                shipContainer.x += distance * Math.sin(rAngle);
                shipContainer.y -= distance * Math.cos(rAngle);
            }
        }

        private function stageReady(event:Event):void
        {
            //install keyboard hook
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown, false, 0, true);
            stage.addEventListener(KeyboardEvent.KEY_UP, keyUp, false, 0, true);
        }

        private final function keyDown(event:KeyboardEvent):void
        {
            if ((event.keyCode == 87) && (!moving))  //87 = W
            {
                movementTimestamp = getTimer();
                moving = true;
            }
            if ((event.keyCode == 65) && (turningMode != 1)) //65 = A
            {
                turningTimestamp = getTimer();
                turningMode = 1;
            }
            else if ((event.keyCode == 68) && (turningMode != 2)) //68 = D
            {
                turningTimestamp = getTimer();
                turningMode = 2;
            }
        }

        private final function keyUp(event:KeyboardEvent):void
        {
            if ((event.keyCode == 87) && (moving)) moving = false; //87 = W
            if (((event.keyCode == 65) || (event.keyCode == 68)) && (turningMode != 0)) turningMode = 0; //65 = A, 68 = D
        }
    }
}

The results were as I expected. Absolutely no improvement. I really hope that someone has another suggestion as this thing needs fixing. Also, I doubt it's my system as I have a pretty good one (8GB RAM, Q9550 QuadCore intel, ATI Radeon 4870 512MB). Also, everyone else I asked so far had the same issue with my client.

Update 5: another example of a smooth flash game just to demonstrate that my movement definitely is different! See http://www.spel.nl/game/bumpercraft.html

Update 4: I traced the time before rendering (EVENT.RENDER) and right after rendering (EVENT.ENTER_FRAME), the results:

rendering took: 14 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 14 ms
rendering took: 14 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 24 ms
rendering took: 18 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 232 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 16 ms
rendering took: 12 ms
rendering took: 14 ms
rendering took: 12 ms

The range is 12-16 ms. During these differences, the shocking/warping/flickering movement was already going on. There is also 1 peak of 232ms, at this time there was a relatively big warp. This is however not the biggest problme, the biggest problem are the continuous small warps during normal movement. Does this give anyone a clue?

Update 3: After testing, I know that the following factors are not causing my problem:

  • Bitmap's quality -> changed with photoshop to an uglier 8 colours optimized graphic, no improvement at all.
  • Constant rotation of image while turning -> disabled it, no improvement at all
  • Browser rendering -> tried to use the flash player standalone, no improvement at all

I am 100% convinced that the problem lies in either my code or in my algorithm. Please, help me out. It has been almost two weeks (1 week that I asked this question on SO) now and I still have to get my golden answer.

Update 1: see bottom for full flex project source and a live demo demonstrating my problem.

I'm working on a 2d flash game. Player ships are created as an object:

ships[id] = new GameShip();

When movement and rotation information is available, this is being directed to the corresponding ship:

ships[id].setMovementMode(1); //move forward

Now, within this GameShip object movement works using the "Event.ENTER_FRAME" event:

addEventListener(Event.ENTER_FRAME, movementHandler);

The following function is then being run:

private final function movementHandler(event:Event):void
        {
            var newTimeStamp:uint = UtilLib.getTimeStamp(); //set current timeStamp
            var distance:Number = (newTimeStamp - movementTimeStamp) / 1000 * movementSpeed; //speed = x pixels forward every 1 second
            movementTimeStamp = newTimeStamp; //update old timeStamp
            var diagonalChange:Array = getDiagonalChange(movementAngle, distance); //the diagonal position update based on angle and distance
            charX += diagonalChange[0];
            charY += diagonalChange[1];
            if (shipContainer)
            { //when the container is ready to be worked with
                shipContainer.x = charX;
                shipContainer.y = charY;
            }
        }

private final function getDiagonalChange(angle:Number, distance:Number):Array
        {
            var rAngle:Number = angle * Math.PI/180; //convert degrees to radian
            return [Math.sin(rAngle) * distance, (Math.cos(rAngle) * distance) * -1];
        }

When the object is no longer moving, the event listener will be removed. The same method is being used for rotation. Everything works almost perfect.

I've set the project's target FPS to 100 and created a FPS counter. According to the FPS counter, the average FPS in firefox is around 100, while the top is 1000 and the bottom is 22. I think that the bottom and top FPSs are only happening during the initialization of the client (startup).

The problem is that the ship appears to be almost perfectly smooth, while it should be just that without the "almost" part. It's almost as if the ship is "flickering" very very fast, you can't actually see it but it's hard to focus on the object while it's moving with your eyes. Also, every now and then, there seems to be a bit of a framerate spike, as if the client is skipping a couple of frames, you then see it quickly warp.

It is very difficult to explain what the real problem is, but in general it's that the movement is not perfectly smooth. So, do you have any suggestions on how to make the movement or transition of objects perfectly smooth?

Update 1:

I re-created the client to demonstrate my problem. Please check it out.

The client: http://feedpostal.com/test/MovementTest.html

The Actionscript Project (full source): http://feedpostal.com/test/MovementTest.rar

An example of a smooth flash game (not created by me): http://www.gamesforwork.com/games/swf/Mission%20Racing_august_10th_2009.swf

It took me a pretty long time to recreate this client side version, I hope this will help with solving the problem.

Please note: yes, it is actually pretty smooth. But it is definitely not smooth enough.

解决方案

I don't know if there's any golden answer here, but I do have a couple of suggestions.

First, I'd dispense with any inquiries into things like optimizing Math.PI/180, and so on. The generally high framerate should make it clear that simple calculations are not slowing anything down.

Second, to address the occasional spikes of display lag: These look to me very much like the garbage collector is running very frequently. On a very brief look through your code I didn't see any obvious causes of frequent GCs, but I do have two suggestions. First, if you have access to the Flash IDE, I'd try recreating your project without using the Flex framework. A Flash project doesn't include any code except what you put in, but Flex employs a lot of its own arcanery, which may not always be obvious, and some interaction between your code and the framework might be causing GCs.

If that doesn't help, the other thing to try would be to make a greatly simplified version of your code (in Flash if possible), which would hopefully be simple enough not to trigger the same spikes. I mean, for example, a single class attached to the graphic, which merely has one listener for key events and a second listener for frame (or timer) events, within which no variables are created. If a minimal version doesn't display these spikes, then it ought to be possible to triangulate between that and your full client, to figure out what's causing the spikes.

Finally, regarding the general smoothness, my only comment would be that Flash's screen updates are inherently slightly uneven, and realistically you only have two approaches available. Either you move your actors according to frame updates, which makes their movement slightly uneven as framerates vary, or you move them according to elapsed time, which makes their overall movement smooth (in pixels per second) but their display slightly uneven (in pixels moved per frame). The difference is magnified at higher FPS.

Also, it's important to remember that after Flash has made its updates, how they appear on your screen is heavily influenced by your video card. Especially you'll find that shearing and vsync issues can be highly noticeable in one environment and absent in another. There's no real way the developer can address this, except to generally avoid very high-FPS animations and keep the overall processor burden as low as possible.

edit: For more on what I mean about frame update timing being "inherently uneven", please see this blog post. The delay between screen updates varying between 12-16ms is not something you can do anything about; it's a result of the fact that the OS and the browser influence the way Flash's timing works. (It's also something you'll see even in an empty movie, which is why the many comments in this thread about optimizing math and so on are not going to help you.) You can't avoid this kind of variation, but as I said above, you can tailor you visual effects to evoke the effect you want. Either way, I think the spikes are much more worth worrying about. The variation you're looking at is subtle, and will be hard to notice in a game with lots of stuff going on, but the spikes are egregious.

edit 2 You ask: "Do you really think that those smooth games use the same movement algorithm as I do?"

The answer is, I think they're doing something much simpler. They're almost certainly doing one of the following:

function onEnterFrame() { // move at a constant speed per frame
    ship.angle += dtheta;
    ship.x += speed * Math.cos( ship.angle );
    ship.y += speed * Math.sin( ship.angle );
}

function onEnterFrame2() { // move at a constant speed per second
    var dt:Number = getTimeSinceLastFrame();
    ship.angle += anglePerSecond * dt/1000;
    var dist:Number = speedPerSecond * dt/1000;
    ship.x += dist * Math.cos( ship.angle );
    ship.y += dist * Math.sin( ship.angle );
}

In other words, either move a constant distance per frame, or a constant distance per second. Those are the two simplest ways you can approach this, and are the two options that are going to result in the smoothest appearance in Flash. They'll look identical at a constant framerate, and the latter method will look smoother at slightly varying framerates for reasons similar to the "temporal aliasing" mentioned in the article you linked. But the choice between these methods really comes down to, if a CPU spike occurs, after it's over do you want the ship to have kept moving or not? Which is really a game design question. One thing I've done in the past is to use the second method, while clamping dt to at most 2 or 3 times the duration of an ideal frame (1/fps).

As you've probably noticed, the two methods I just recommended are exactly what the "Fix your timestep!" article says not to do. This is because that article is about numerically integrated physics engines, and that's not what you're doing. If you start implementing springs and gravity, then yes, whenever the timesteps get large it will introduce a lot of error, because for that kind of simulation, to oversimplify things, the error depends on the size of the timestep. In what you're doing, it doesn't, so occasional large timesteps don't affect the correctness of the simulation.

Reply to update 6

First, I didn't tell you your problem was performance, I specifically said the opposite. I suggested a minimal reproduction because I believed your problem was either somewhere else in your project, or unavoidable, and I still do. Second, I now feel pretty comfortable saying that you're doing things the same as any other Flash game, and whatever problems you see cannot be solved other than in perception. In the new links you posted, the animation looks perfectly smooth if I view it in the standalone SWF player, and has subtle flickering at the front and back edges in browsers (moreso in Firefox than in IE). Technically, I don't think it can improve (especially when it's basically perfect in the standalone player, implying that any choppiness in browsers is influenced by the container.)

Of course, the perceived performance can still improve. If the ship didn't contrast so sharply with the background, for example, the flicker would be much less noticeable. Also, simply making the ship move more slowly would make the movement appear much smoother, and could be combined with a moving background to give an illusion of greater speed (as one of your examples did).

As a sanity check, here's a similar minimal version I made in the IDE. http://www.fenomas.com/random/ship/ The performance is comparable to yours on my machine, and as I said, I really don't see any problem. (Except the occasional spike, which I now notice only occurs for me in Firefox.) Again, especially the fact that both versions appear basically perfect to me in the standalone player further convinces me that there's no golden algorithm here. I know that's not the answer you want, but it's the one I got.

这篇关于优化 2D Flash 游戏的过渡/移动平滑度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆