更新6 :
Fenomenas建议我尽可能简单地重新创建所有内容。我对此表示怀疑,因为算法保持不变,并且性能似乎不是问题,这会产生什么不同。无论如何,这是我得到的唯一建议,所以这里是:
代码:
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
}
}
}
结果符合我的预期。绝对没有改善。我真的希望有人提出其他建议,因为这个问题需要解决。另外,我怀疑这是我的系统,因为我有一个不错的系统(8GB RAM,Q9550 QuadCore intel,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毫秒。在这些差异期间,令人震惊/翘曲/闪烁的运动已经在进行。还有一个232ms的峰值,这时出现了一个较大的翘曲。然而,这不是最大的问题,最大的问题是正常运动期间连续的小翘曲。这会给任何人一个线索吗?
更新3:经过测试,我知道以下因素并未引起我的问题:
我100%确信问题出在我的代码或算法中。请帮帮我。现在已经快两个星期了(我在SO上问了这个问题大约1个星期),我仍然必须得到我的黄金答案。
更新1:请参见底部以获取完整的flex项目源以及一个演示我的问题的现场演示。
我正在开发2D Flash游戏。玩家飞船被创建为一个对象:
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
The Actionscript Project(完整源代码): 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的屏幕更新本质上有些不均匀,实际上,您只有两种方法可用。您可以根据帧更新来移动actor,这会使它们的移动随帧率的变化而稍微不均匀,或者根据经过的时间对其进行移动,这会使它们的整体移动变得平滑(以每秒像素为单位),但它们的显示会稍微不均匀(以移动像素为单位)每帧)。较高的FPS会放大差异。
另外,请务必记住,Flash进行更新后,它们在屏幕上的显示方式会受到视频卡的严重影响。尤其是您会发现,在一种环境中,剪切和垂直同步问题可能非常明显,而在另一种环境中却没有。开发人员没有真正的解决办法,除非通常避免播放非常高FPS的动画,并尽可能降低总体处理器负担。
编辑:有关帧更新定时“天生不平衡”的更多信息,请参阅this blog post。屏幕更新之间的延迟介于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中。)同样,尤其是两个版本在独立播放器中对我来说看起来基本完美的事实进一步使我确信,这里没有黄金算法。我知道这不是您想要的答案,但这是我得到的答案。
关于actionscript-3 - 优化2D Flash游戏的过渡/运动平滑度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1284886/