ActionScript 3, Timesteps, and the fixed time step

Started by
2 comments, last by Patrick B 10 years, 3 months ago

Hey everyone, I'm looking into creating a AS3 game and I am back to the place where things kind of fall apart
The Game Loop

This critical component has crippled me. I'm attempting to implement a fixed timestep game loop, but for some reason I have a very noticeable jitter/jerk moment

I have read this this article http://www.koonsolo.com/news/dewitters-gameloop/ and http://gafferongames.com/game-physic...your-timestep/

And a bunch of other things, but no matter what I unfortunately can't seem to get it 'working'.

On the above links, I have tried both approachs. It seems as if something has gone horribly wrong with my interpolation or set up of the loop.

Currently I'm am attempting to use Gafferon's approach again but...

My graphic ( a 32x32 square ) jitters constantly. It hiccups when moving and then twitchs in place when standing still. Even with interpolation

Can someone please point out wheat I'm doing wrong? biggrin.png


/* Above load in the image, set up other items, set the stage framerate to 60 */

public var fixedDelta:Number = 1 / 60;
public var minDelta:Number = 1 / 30;
public var accumulator:Number = 0.0;
public var ratio:Number;

//Called on ENTER FRAME
private function mainLoop(e:Event):void
{

	//Get the time and calc delta time in seconds
	now = getTimer();
	deltaTime = (now - last) * .001;
	if (deltaTime > minDelta)
		deltaTime = minDelta;
	last = now;

	//Add on to the accumulator
	accumulator += deltaTime;

	//While we need to update; Update
	while (accumulator >= fixedDelta)
	{
		//Update with out fixed DT
		update(fixedDelta);
		accumulator -= fixedDelta;
	}

	//Calc the interpolation ratio
	ratio = accumulator / fixedDelta;
	render(ratio);
}

//Main update function
public function update(dt:Number):void
{
	square.update(dt);
}

//Main render function
public function render(ratio:Number):void
{
	canvas.fillRect(canvas.rect, 0x000000FF);
	canvas.lock();
	
	square.draw(ratio);
	
	canvas.unlock();
}

//============== Square update and draw

//Square update
public function update(dt:Number):void
{
	prevPosition.x = position.x;
	prevPosition.y = position.y;
		
	if(Main.keyboard.checkKey(Main.keyboard.KB_RIGHT))
		position.x += 300 * dt;
		
	if(Main.keyboard.checkKey(Main.keyboard.KB_LEFT))
		position.x -= 300 * dt;
}

//Square draw function
public function draw(ratio:Number):void
{
	renderPosition.x = int(position.x * ratio + prevPosition.x * (1 - ratio));
	renderPosition.y = int(position.y * ratio + prevPosition.y * (1 - ratio));
	
	Main.canvas.copyPixels(image, image.rect, renderPosition);
}
Advertisement

Try removing the 'ratio' stuff and working only with the delta time

Doing something like

x += velocity.x * deltatime;

Should suffice

I just tried removing the interpolation, but unfortunatly while it stops the jittering when standing still. It does not solve the spikes of jitters while moving :(

First of all, I think your issue may be the type casting that you're doing:

[source]

//Square draw function
public function draw(ratio:Number):void
{
renderPosition.x = int(position.x * ratio + prevPosition.x * (1 - ratio));
renderPosition.y = int(position.y * ratio + prevPosition.y * (1 - ratio));
Main.canvas.copyPixels(image, image.rect, renderPosition);
}
[/source]
In Flash, because you can do sub-pixel rendering, decimal values are used for x/y positioning. I have a feeling that the jumping you're seeing is because of the blind rounding that happens when casting to an int from a real. Maybe this will help?

[source]

//Square draw function
public function draw(ratio:Number):void
{
renderPosition.x = Number(position.x * ratio + prevPosition.x * (1 - ratio));
renderPosition.y = Number(position.y * ratio + prevPosition.y * (1 - ratio));
Main.canvas.copyPixels(image, image.rect, renderPosition);
}
[/source]
I also note that you're copying the square graphic to what I'm guessing is a mx.containers.Canvas instance. I don't want to presume anything but from the code I don't see any reason to be doing this. If it's a vector or bitmap graphic, you can use a Sprite or maybe even a Bitmap instance. It'll make your code cleaner and it should work faster than any custom blitting or pixel copy system.
For example:

[source]

var myGraphic:Sprite=new Sprite();

myGraphic.addchild(squareGraphic); //or draw or load it - this is very generic

[/source]

Subsequently you won't need the render function since the above has already rendered the graphic. So your new update function would be:

[source]

//Square draw function
public function draw(ratio:Number):void
{
myGraphic.x = Number(position.x * ratio + prevPosition.x * (1 - ratio));
myGraphic.y = Number(position.y * ratio + prevPosition.y * (1 - ratio));
}
[/source]

That all being said, you would probably save yourself some time and frustration by using a tweening system (Flash's or something like Greensock, for example). You tell it which object to move, the start position, end position, and interval -- it takes care of moving the graphic. All of the graphics properties should always be available so you can always calculate collisions (or whetever) on every motion increment, and of course you can stop, reset, and rewind any animation. You can also just get the tweener to dispatch events instead of affecting the graphic (you then update the graphic position based on where the tweener tells you to).

Have a look here: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/fl/transitions/Tween.html

Unless there's a compelling reason to move graphics around like you're doing, the solution that you've implemented is WAY too complicated and error-prone, not to mention limiting (tweens also do accelerations/decelerations and other nice effects).

Without seeing your ultimate application, here's how I would tackle graphic motion:

[source]

import flash.display.*;

import fl.transitions.Tween;

import fl.transitions.tweening.Regular; //Regular tweening equations; others include Strong, Elastic, Bounce, etc.

import fl.transitions.TweenEvent;

private var motionXTween:Tween;

private var motionYTween:Tween;

public function createSquare():Sprite {

//only need to create the square once!

var returnSquare:Sprite=new Sprite();

returnSquare.graphics.lineStyle(1,0x000000); //Black line, 1 pixel thick

returnSquare.graphics.moveTo(0,0);

returnSquare.graphics.beginFill(0xFF0000,1); //Red fill, 100% (1) opacity

returnSquare.graphics.lineTo(0,100); //Draw the 100x100 square...

returnSquare.graphics.lineTo(100,100);

returnSquare.graphics.lineTo(100,0);

returnSquare.graphics.lineTo(0,0);

returnSquare.graphics.endFill();

return (returnSquare);

}

public function onSquareMove(eventObj:TweenEvent):void {

if (eventObj.target== this.motionXTween) {

trace ("Square is now at X position: "+eventObj.position);

}

if (eventObj.target== this.motionYTween) {

trace ("Square is now at Y position: "+eventObj.position);

}

}

public function moveSquareTo(targetXPos:Number, targetYPos:Number):void {

var moveSquare:Sprite=this.createSquare();

this.stage.addChild(moveSquare);

this.motionXTween=new Tween(moveSquare, "x", Regular, moveSquare.x, targetXPos, 1, true); //Move "moveSquare", on "x" axis, using Regular tweening, from "moveSquare.x", to tagetXPos, using a duration of 1, in seconds

this.motionXTween.addEventListener(TweenEvent.MOTION_CHANGE, this.onSquareMove); //Call the onSquareMove function on every motion update -- completely optional!

this.motionXTween.start();

//Do the same thing for the Y axis...

this.motionYTween=new Tween(moveSquare, "y", Regular, moveSquare.y, targetYPos, 1, true);

this.motionYTween.addEventListener(TweenEvent.MOTION_CHANGE, this.onSquareMove);

this.motionYTween.start();

}

[/source]

You could probably shrink this code down a bit more but this is the essence of both creating the graphic and then moving it using a tween. You can also get rid of the onSquareMove event listener since it doesn't do anything except tell you where the square is while moving.

As I said, I'm making assumptions here; if you're loading the square then there are an extra couple of steps, and if you really do need to move the square around at the pixel level then it's best to combine your copyPixels call with the Tween event (use the event listener to assign the X/Y values instead of calculating them).

Hope this helps, and Merry Christmas!

This topic is closed to new replies.

Advertisement