更新6:
Fenomenas建议我尽可能简单地重新创造一切.我怀疑这会产生任何不同,因为算法保持不变,并且性能似乎不是问题.无论如何,这是我在这里得到的唯一建议:
30 FPS:http://www.feedpostal.com/test/simple/30/SimpleMovement.html
40 FPS:http://www.feedpostal.com/test/simple/40/SimpleMovement.html
60 FPS:http://www.feedpostal.com/test/simple/60/SimpleMovement.html
100 FPS:http://www.feedpostal.com/test/simple/100/SimpleMovement.html
代码:
package { import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.utils.getTimer; [SWF(, , 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 } } }
结果如我所料.绝对没有改善.我真的希望有人有另一个建议,因为这件事需要修复.另外,我怀疑这是我的系统,因为我有一个非常好的系统(8GB内存,Q9550 QuadCore英特尔,ATI Radeon 4870 512MB).此外,我到目前为止所问的其他人与我的客户有同样的问题.
更新5:光滑的Flash游戏的另一个例子,只是为了证明我的运动肯定是不同的!见http://www.spel.nl/game/bumpercraft.html
更新4:我追溯渲染前的时间(EVENT.RENDER)和渲染后的正确时间(EVENT.ENTER_FRAME),结果如下:
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
范围是12-16毫秒.在这些差异期间,震惊/翘曲/闪烁运动已经开始.还有1个232ms的峰值,此时有一个相对较大的扭曲.然而,这不是最大的问题,最大的问题是在正常运动期间连续的小扭曲.这会给任何人一个线索吗?
更新3:经过测试,我知道以下因素不会导致我的问题:
位图的质量 - >用photoshop更改为更加丑陋的8色优化图形,完全没有任何改进.
转动时图像不断旋转 - >禁用它,完全没有任何改善
浏览器渲染 - >试图单独使用flash播放器,根本没有任何改进
我100%确信问题出在我的代码或算法中.拜托,帮帮我吧.现在已经差不多两周了(我问过这个问题的一周),我仍然需要得到我的回答.
更新1:查看底部的完整flex项目源和现场演示,演示我的问题.
我正在开发一款2D游戏.玩家船只作为对象创建:
ships[id] = new GameShip();
当移动和旋转信息可用时,这将被引导至相应的船舶:
ships[id].setMovementMode(1); //move forward
现在,在此GameShip对象中,运动使用"Event.ENTER_FRAME"事件:
addEventListener(Event.ENTER_FRAME, movementHandler);
然后运行以下函数:
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]; }
当对象不再移动时,将删除事件侦听器.正在使用相同的方法进行旋转.一切都近乎完美.
我已将项目的目标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更新后,它们在屏幕上的显示方式会受到视频卡的严重影响.特别是你会发现剪切和vsync问题在一个环境中非常明显,而在另一个环境中则不存在.除了通常避免非常高的FPS动画并且尽可能降低整体处理器负担之外,开发人员无法解决这个问题.
编辑:有关框架更新时间"固有不均匀"的含义,请参阅此博客文章.屏幕更新之间的延迟在12-16ms之间变化不是你可以做任何事情; 这是因为操作系统和浏览器会影响Flash的计时工作方式.(这也是你在一部空白电影中都会看到的东西,这也就是为什么这个关于优化数学的线程中的许多评论都不会帮助你.)你无法避免这种变化,但是因为我如上所述,你可以定制视觉效果来唤起你想要的效果.无论哪种方式,我认为峰值更令人担忧.你所看到的变化是微妙的,并且在很多东西正在进行的游戏中很难注意到,但是尖峰是令人震惊的.
编辑2 你问:"你真的认为那些流畅的游戏使用与我相同的移动算法吗?"
答案是,我认为他们做的事情要简单得多.他们几乎肯定会做以下其中一项:
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 ); }
换句话说,要么每帧移动一个恒定的距离,要么每秒移动一个恒定的距离.这是您可以采用的两种最简单的方法,这两种选项将导致Flash中最流畅的外观.它们在恒定的帧速率下看起来相同,后一种方法在略微变化的帧速率下会看起来更平滑,原因类似于您链接的文章中提到的"时间混叠".但是这些方法之间的选择真的可以归结为,如果CPU出现峰值,在它结束后你是否希望这艘船继续移动?这真的是一个游戏设计问题.我过去做过的一件事就是使用第二种方法,同时dt
最多夹持理想帧(1/fps)持续时间的2到3倍.
正如您可能已经注意到的那样,我刚推荐的两种方法正是"修复您的时间步长!" 文章说不要做.这是因为那篇文章是关于数字集成的物理引擎,而这不是你正在做的事情.如果你开始实现弹簧和重力,那么是的,只要时间步长变大就会引入很多错误,因为对于那种模拟,过度简化事物,误差取决于时间步长的大小.在你正在做的事情中,它没有,所以偶尔的大时间步长不会影响模拟的正确性.
回复更新6
首先,我没有告诉你你的问题是表现,我具体说相反.我建议进行最小化的复制,因为我认为你的问题要么在项目的其他地方,要么是不可避免的,我仍然这样做.其次,我现在觉得很自在地说你做的事与任何其他Flash游戏一样,除了感知之外,你看到的任何问题都无法解决.在您发布的新链接中,如果我在独立的SWF播放器中查看它,动画看起来非常流畅,并且在浏览器的前后边缘处有微妙的闪烁(在Firefox中比在IE中更多).从技术上讲,我认为它不会改进(特别是当它在独立播放器中基本上完美时,暗示浏览器中的任何不稳定都会受到容器的影响.)
当然,感知的表现仍然可以改善.例如,如果船与背景没有如此明显的对比,那么闪烁就不那么明显了.此外,简单地使船舶移动得更慢会使运动看起来更平滑,并且可以与移动的背景结合以产生更快速的幻觉(正如您的一个例子所做的那样).
作为一个完整性检查,这是我在IDE中制作的类似的最小版本. http://www.fenomas.com/random/ship/ 我的机器上的性能与你的性能相当,正如我所说,我真的没有看到任何问题.(除了偶尔的尖峰,我现在只注意到它在Firefox中出现.)同样,特别是这两个版本在独立播放器中对我来说基本完美的事实进一步说服了我这里没有黄金算法.我知道这不是你想要的答案,但它是我得到的答案.