Optimizing transition/movement smoothness for a 2D flash game.

Posted by Tom on Stack Overflow See other posts from Stack Overflow or by Tom
Published on 2009-08-16T17:52:04Z Indexed on 2010/03/14 22:55 UTC
Read the original article Hit count: 811

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.

© Stack Overflow or respective owner

Related posts about actionscript-3

Related posts about flash