4
\$\begingroup\$

I've created a little 2D game engine with parallax scrolling and something has been bugging me for some time.

Layers / objects set at a different depths / z orders don't preserve their position relative to each other. The position of each layer after substaintial camera movement seems to decay and become inaccurate over time.

I have 3 layers / objects, each set at a different Z co-ordinate / layer depth. The higher the Z co-ordinate, the slower the layer moves; basically the more distant the layer is to the camera. Each layer being 1 level deeper than the next. For example:

layer1.z = 4;
layer2.z = 3;
layer3.z = 2;

Then, when rendering out all the layer positions every update. I use the following algorithm; let's use the first layer as an example:

layer1.x += (layer1.moveX -= cameraTarget.moveX) / layer1.moveZ;

moveX being the layer's / object's potentional movement modifier. So this should take into account not only the cameras movement, but the layers movement (if any) as well. This should allow dynamic bodies to work within the parallax scrolling paradigm.

For simplicities sake, even if I assume there are no dynamic bodies and just want to render background elements, I can simplify the above and do this instead:

layer1.x += (cameraTarget.moveX / layer1.moveZ) * -1;

I ASSUME this is the very core and basic math to doing parallax scrolling. Yet if I move the camera around enough, each layer seems to slightly offeset more and more. To test this, I even created a grid-like puzzle that fits together when all 3 layers match. It starts matching by default, and matches fine during the first initial movements, but after signifcant movements, it is unable to match perfectly again. Basically by moving the camera and tweaking it's position, I can't get the puzzle to match.

What am I doing wrong? I would think if everything passes through this algorithm, it should at least be consistant. Well, in saying that, to a casual observer it looks great, but on closer scrutiny it's flawed (I think). Is parallax scrolling natually decaying / desctructive to objects relational postioning to each other?

I am using Flash / ActionScript 3.0. One possibility I thought of was perhaps Flash's renderer could be doing something funky to my calculations when I assign these numbers to the x and y property of a DisplayObject. I've tried rounding too, in some naive attempt to make it more precise and perhaps have movement only to 1 sub-pixel.

UPDATE

My current setup is this, I have 3 layers, each of equal dimension layed out on the stage. So that at the start, the layers are where I want them, but they must just respond to camera movements.

layer1.z = 4;
layer1.x = 80;
layer1.y = 60;

layer2.z = 3;
layer2.x = 80;
layer2.y = 60;

layer2.z = 2;
layer2.x = 80;
layer2.y = 60;

// Place the player at the center of the screen, the camera directly follows / mimics the player's position
player.x = (stage.stageWidth / 2) - (player.width / 2);
player.y = (stage.stageHeight / 2) - (player.height / 2);

Then, in my render function that updates every frame:

// Capture camera movement
camera.x += cameraTarget.moveX; // cameraTarget being the player
camera.y += cameraTarget.moveY;

// Move all the layers
layer1.x = (camera.x + layer1.x) / layer1.moveZ;
layer1.y = (camera.y + layer1.y) / layer1.moveZ;

layer2.x = (camera.x + layer2.x) / layer2.moveZ;
layer2.y = (camera.y + layer2.y) / layer2.moveZ;

layer3.x = (camera.x + layer3.x) / layer3.moveZ;
layer3.y = (camera.y + layer3.y) / layer3.moveZ;

I've removed a few things my code has that would just confuse matters, but this is essentially what it does. For example, I have a loop that loops through an array of layers and then applies the transforms. Here, I'm only hardcoding it.

I also was using a Matrix to capture the rotation, scale etc... of the cameraTarget, which was nice.

\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

Whenever you do a float operation, some rounding errors are bound to occur. Because you are adding each specific layer's position change on every frame, eventually these errors add up and become noticeable.

Say you have a max error e in each multiply operation, then with your algorithm the frame 1 position is:

   layer1.x(1) += cameraTarget.moveX * layer1.z;
=> layer1.x(1) = layer1.x(0) + cameraTarget.moveX * layer1.z;
=> layer1.x(1) = layer1.x(0) + layer1.moveX + O(e);

Then on frame 2 you get (assuming the same camera movement):

   layer1.x(2) += cameraTarget.moveX * layer1.z;
=> layer1.x(2) = layer1.x(1) + layer1.moveX + O(e);
=> layer1.x(2) = layer1.x(0) + 2*layer1.moveX + O(2*e);

As you can see, after N frames you'll have O(N*e) error in each layer's position, so the differences between layers may become noticeable.

The best way to avoid that is to calculate all layer positions from a single parameter, for example, using the camera position instead of camera movement:

layer1.x = cameraTarget.x * layer1Scale;
layer2.x = cameraTarget.x * layer2Scale;
layer3.x = cameraTarget.x * layer3Scale;

This way any error from previous calculations is wiped-out on every frame - even if cameraTarget has an error, it will be the same constant error for all layers.

\$\endgroup\$
12
  • \$\begingroup\$ Isn't this essentially the same thing, since I am always moving everything according to cameraTarget every update? Every update consists of a loop that goes through every layer and updates the position. \$\endgroup\$ Mar 9, 2012 at 14:36
  • \$\begingroup\$ See update, I added an explanation of error propagation in your current algorithm. \$\endgroup\$
    – SkimFlux
    Mar 9, 2012 at 15:41
  • \$\begingroup\$ Could you elaborate on layer1Scale, layer2Scale etc..? I get what you're saying about error propergation, I assumed this was the case, but didn't know exactly how to avoid it. I've tried using camera position as you've recommended, and so far it seems to be working great. Required some rewriting, as before I didn't ever physically set a camera position, but now I am. But now objects are jumping to camera.x! I assume that's where layerScale comes to the rescue? \$\endgroup\$ Mar 9, 2012 at 17:25
  • 1
    \$\begingroup\$ @DanielCarvalho, Just add an additional variable then layer1.x = (cameraTarget.x + layer1Offset) * layer1Scale; \$\endgroup\$ Mar 9, 2012 at 21:18
  • 1
    \$\begingroup\$ @DanielCarvalho You need to use a new variable for the offset, not the position of the layer, like so: layer1.x = (camera.x + layer1Offset.x) / layer1.moveZ; \$\endgroup\$ Mar 10, 2012 at 23:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .