2

更新 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

编码:

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 毫秒。在这些差异期间,令人震惊/扭曲/闪烁的运动已经在进行。还有1个232ms的峰值,此时出现了比较大的warp。然而,这不是最大的问题,最大的问题是在正常运动过程中连续的小翘曲。这是否给任何人一个线索?

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

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

我 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

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

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

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

请注意:是的,它实际上非常流畅。但绝对不够流畅。

4

8 回答 8

9

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.

于 2009-08-23T10:26:47.483 回答
2

你的代码对我来说运行流畅。没有任何尖峰。使用 updatePoller 函数末尾添加的以下代码对其进行了测试。

var shadow:Sprite = new Sprite();
shadow.graphics.beginFill(0xFFFFFF, 0.01);
shadow.graphics.lineStyle(1, 0xFFFFFF, 0.8);
shadow.graphics.drawRect(0, 0, 25, 50);
this.addChildAt(shadow, 0);
shadow.x = shipContainer.x;
shadow.y = shipContainer.y;
shadow.rotation = shipContainer.rotation;

100 fps 的版本往往会得到不均匀的图案,但这是正常的,因为根据您的计算,如果计算一帧需要超过 10 毫秒,则不可能在一秒钟内渲染 100 帧。所以,至少对我来说,最后一段代码运行流畅,速度为 30fps。

至于模糊部分,一个主要的问题,希望你不会因为我的问题而生气:模糊/模糊效果是否有可能是因为你的显示器?即使在 LCD 显示器上的响应时间为 10 毫秒,在静态黑色背景上快速移动的白色物体也会看起来很模糊。

于 2009-08-24T17:12:56.967 回答
1

我认为您问题的根源在于“垂直同步”与屏幕的不同。这与在例如 60hz 屏幕上观看 24fps 电影时出现的问题相同。更新不会完全匹配(在您的情况下为 100/60),当它们达到更大的跳跃时,它看起来就像运动中的小抖动。

这可以通过降低帧率来解决,任何高于屏幕的帧率都只是浪费处理能力。尽管用于 Flash 嵌入的较新 wmode可能是一种可能的解决方案,但它并不能完全避免。

于 2009-08-17T09:21:29.783 回答
0

我认为这几乎可以肯定是因为你的速度达到了 80fps。Flash 根本无法提供如此快的一致帧速率。降到 30fps 并继续测试。此外,尝试在实际背景前驾驶飞船,我想你会注意到这一点。

于 2009-08-23T19:14:58.880 回答
0

This is a pretty good question. I've scanned the code and I have a few suggestions, although my advice might not be that good.

I'm thinking you can do a lot to optimize the code. Obviously, not at this early stage. But you can have a test with that code and see fast it runs with the optimized code, then you'll know if it's worth continuing.

Here are my 'objections':

  • You use a lot of divisions. Division is more expensive than multiplication.

    var distance:Number = (newTimeStamp - movementTimeStamp) / 1000 * movementSpeed;

can be easily written as

var distance:Number = (newTimeStamp - movementTimeStamp) * .001 * movementSpeed;
  • You have a LOT of references to a lot of functions.

    Stuff like fixAngle() and so on can be inside the same function, without having calls running back and forth that often. This applies to references to external classes and Math.PI or Math.sin and so on, as fenomas and Allan pointed out.

I've tested this method for sine and cosine and it's bloody fast. Sure it makes the code dirty, but that's why you don't optimize this soon, until you got most of it working the way it needs to work, optimizing will only make you go nuts as the code will get harder to read. From my experience sin and cos are pretty expensive operations.

As the others already mentioned, you might be worring too much at this step. Keep in mind there are a lot of things you can gain speed on, until you got all your logic working properly, don't even think about optimizing.

于 2009-08-23T13:00:49.520 回答
0

我已经回答了与此问题有关的另一个问题,请阅读以下内容:

当我正在开发自己的游戏时,我感受到了你的痛苦。在默认设置下,无论您生成什么代码,Flash 渲染器都会产生可怕的屏幕撕裂/垂直同步问题。

这就是为什么我很高兴找到最简单、最优雅的答案,这不是重构代码(这没有一点帮助,问题是 Flash 播放器,而不是代码)。

只需在您的发布设置中启用硬​​件加速。有两种不同的选择:

1级:直接;和2 级:GPU

在官方文档中阅读更多相关信息:指定 SWF 文件的发布设置,并确定最适合您的游戏的选项。

目标市场确实在这里发挥了作用,如果它对游戏玩家来说是一款严肃的游戏,那么您无需担心可能出现的性能问题,因为大多数游戏玩家都有 GPU。

这篇文章并没有专门为我提供解决方案,但引导我朝着正确的方向前进。但是,如果您的游戏将在浏览器窗口中运行,您可能还必须使用相同的技术将 wmode 设置为 direct 或 gpu。

于 2010-03-14T22:49:23.623 回答
0

我可以建议你过早地担心吗?

如果您仔细查看您链接的“完美流畅”的 Flash 游戏,您会发现它正在用流畅运动的“幻觉”欺骗您。

汽车的移动速度并不快——可能每隔几帧就有一个像素。这是完成大部分动作的背景。仔细看看:有一点抖动和你试图修复的“难以聚焦”效果,但由于它是背景,这似乎是正常的。即使汽车确实显示出这些效果,背景和游戏玩法也会分散您的注意力,而不会注意到它们。

我认为您注意到了这种抖动,因为您现在所拥有的只是一艘在完全黑色背景上行驶的船。一旦游戏的其余部分到位,玩家可能不会有多余的注意力来注意到一点点抖动。至于“难以聚焦”的效果,它会随着你降低飞船的移动速度而消失。

你为什么不先处理游戏的其余部分?如果仍然存在问题,您可以稍后再回来调整动作。您在动画工件上花费了大量时间。游戏性不是更重要吗?

于 2009-08-18T20:47:36.503 回答
0

要下载的文件不存在(http://feedpostal.com/test/MovementTest.rar)。

于 2009-09-28T03:38:06.640 回答