Parallax layers and relative positions after screen resizing

Recommended Posts

I appear to have an issue where resizing my game causes my parallax backgrounds to move around, and am looking for some help to see what I may be doing wrong.


The game looks generally like this:




Onto the problem:

I have several layers of background that move at different speeds. The first layer of background (layer0 in the code) moves in unison with the foreground. I can line up the player's character with a background object in layer0 and then resize the screen and the player remains in the exact same relative position to the background object, which is great.


Where I run into trouble, is that I can't line up the player with objects in the other parallax layers (layer1 through layer4) and then resize the screen and keep the player lined up with background objects. Perhaps I'm missing a fundamental point about parallax layers, but I figured that resizing the screen would simply shrink or enlarge the entirety of the game view -- NOT cause the background layers to move around relative to everything else. So I assume I've made an error in how I treat these layers.


Here are pictures of the discrepancy:


The grey cloud adjacent the orange ship is in layer0. The red cloud is layer1 (parallax). The red cloud moves more slowly than the layer0 grey cloud when the ship moves around. I thought everything was working fine until I resized the window.



Upon changing the browser window's size, all of the images have resized nicely, and the orange ship remains in the exact same relative position to the cloud in layer0, however the red cloud from layer1 has moved. The top most gray cloud is in layer2 (not included in the code) and has moved even further.


To shorten the code, I'm going to remove layers2-4 as all of these layers are logistically the same as layer1; they are all parallax layers. The question becomes: What am I doing wrong with either resizing my game's view, or with my parallax layer1?

function Background() {	this.layer0 = new PIXI.DisplayObjectContainer()	this.layer1 = new PIXI.DisplayObjectContainer()	//skipping layer2, layer3, layer4}Background.prototype.initialize = function(stage) {	stage.addChild(this.layer0)	stage.addChild(this.layer1)		var cloud = new PIXI.Sprite(PIXI.Texture.fromFrame('compressed-cloud.png'))	cloud.anchor.x = cloud.anchor.y = 0.5	this.layer0.addChild(cloud)	var cloud1 = new PIXI.Sprite(PIXI.Texture.fromFrame('compressed-cloud.png'))	cloud1.anchor.x = cloud1.anchor.y = 0.5	cloud1.tint = 0xff0000	this.layer1.addChild(cloud1)}Background.prototype.moveBy = function(x, y) {	// mapped to foreground	this.layer0.x += x	this.layer0.y += y	// parallax	this.layer1.x += x * 0.8	this.layer1.y += y * 0.8}Background.prototype.setScale = function(scale) {	this.layer0.scale.x = this.layer0.scale.y = scale	this.layer1.scale.x = this.layer1.scale.y = scale}

To summarize Background, it is a plain non display object which creates two display objects, layer0 and layer1, which it adds to the stage. Some clouds are added to each of the layers as a test. When the background is moved, layer0 is moved by the same amount as the foreground layer, meanwhile layer0 is moved only 80% as much, creating a parallax effect. When the screen is resized, both layer0 and layer1 are rescaled.



Now my display and resizing logic:

function Display() {	// default, these values need updated if the window is ever resized	this.windowWidth = window.innerWidth	this.windowHeight = window.innerHeight	// a size for the game, applied to a PIXI renderer	// changing these values will zoom in/out the game 	this.gameWidth = 500	this.gameHeight = 500	// scaling ratio between the game and the browser window	this.scale = 1	this.recalcuateScale()}Display.prototype.resize = function() {	this.windowWidth = window.innerWidth	this.windowHeight = window.innerHeight	this.recalcuateScale()}Display.prototype.recalcuateScale = function() {	var xRatio = this.windowWidth / this.gameWidth	var yRatio = this.windowHeight / this.gameHeight	this.scale = (xRatio < yRatio) ? xRatio : yRatio}

Display keeps track of the actual browser window dimensions, and calculates a scale between them and the game dimensions. The scale calculation is basically to take the smaller of the width or the height ratio. I'll note that the game isn't actually ever a 500x500 pixel square, it'll instead be a 1920x1200 view (or any size)  where the graphics have been scaled up to a nice pixelly aesthetic. There's never any horizontal or vertical mashing, all the pixels stay crisp and square.


I can't include all the code, but here is where display resize is used:

	window.onresize = function() {		console.log('resizing!')		display.resize()		renderer.resize(display.windowWidth, display.windowHeight)		background.setScale(display.scale)		foreground.scale.x = display.scale		foreground.scale.y = display.scale		hud.resize()	}

And here is roughly where the actual movement of the foreground/background occurs:

var targetX = 0var targetY = 0var cameraX = 0var cameraY = 0function moveCamera(x, y) {	targetX = x	targetY = y}function updateCamera(delta) {	var dx = targetX - cameraX	var dy = targetY - cameraY	var moveX = dx * delta * 10	var moveY = dy * delta * 10	cameraX += moveX	cameraY += moveY	background.moveBy(moveX, moveY)	foreground.x = cameraX	foreground.y = cameraY}

It is a little messy, but I doubt the error is in the camera logic. Summary of camera logic: camera gets aimed at a position, and then each frame it moves towards it gently at the rate of delta * 10. The foreground and background move at the exact same rate (despite my having coded it awkwardly where one uses foreground.x = cameraX and the other uses layer0.x += moveX). The parallax effect occurs inside of moveBy(), where moveX is reduced for layers 1 through 4.


Anyone know what I've done wrong?


Thanks for reading!


Share this post

Link to post
Share on other sites

I would also like to know more about this. I've had similar troubles with my own game, and would love to solve them. Since I use Phaser, I can tween; what I did was tween the camera zoom (ie world scale, using the same technique as you) to zoom in and out in a sinusoidal manner. It turned out that the "deeper" layers moved around quite non-trivially (one might expect a linear motion, but I got something like an ellipsoid?)


Very interested in hearing other people's takes.

Share this post

Link to post
Share on other sites

I had a similar issue to this before, I think it was to do with scaling the containers. If it is then you need to place them inside a parent object container and use that for scaling instead, something like:

this.layer0 = new PIXI.DisplayObjectContainer();this.layer1 = new PIXI.DisplayObjectContainer();this.container = new PIXI.DisplayObjectContainer();this.container.addChild(this.layer0);this.container.addChild(this.layer1);stage.addChild(this.container);Background.prototype.setScale = function(scale) {   this.container.scale.x = scale;   this.container.scale.y = scale;}

Share this post

Link to post
Share on other sites

Using a container for the background layers certainly changed the movement a bit, but did not seem to fix the resizing, unless I'm missing something.


I was looking through the stock pixi.js examples, and I found an interesting one. The 'Run Pixie Run' example by Mat Groves has numerous parallax layers of a jungle scene as steve the pixie runs and jumps over spikey boxes and lava traps. An updated versions of this demo has a pause button ( http://www.goodboydigital.com/runpixierun/ ), which makes it real easy to analyze. If you'd like to reproduce the behavior, 1) start up Run Pixie Run 2) pause the game 3) note where the large trees are relative to the pixie 4) resize the the browser horizontally 5) recheck the positions of the trees relative to the pixie. At first it looked like this game has the same problem as my own -- the two large background trees move drastically whenever the browser window is resized. However it is only the front-most trees and the small vines with flowers which behave inconsistently. The other layers of grass and trees also use a parallax effect but stay perfectly in place at any window size. This latter behavior is exactly what I'm looking to achieve for my own backgrounds.


Reading through the source code for Run Pixie Run, I couldn't say for sure why some of the background layers can handle resizing without moving, while others can't. Luckily the developer has used a different pattern for the trees and vines that have the problem than from the other layers that work perfectly, so it seems like the answer has to be right in front of me. Any idea what it might be??? (see code below)


Here's the code for the elements that stay perfectly in place at any browser size:

GAME.BackgroundElement = function(texture, y, owner){	this.sprites = [];	this.spriteWidth = texture.width-1;	var amount = Math.ceil(940 / this.spriteWidth);	if(amount < 3)amount = 3;		for (var i=0; i < amount; i++) 	{		var sprite = new PIXI.Sprite(texture);		sprite.position.y = y;		owner.addChild(sprite);		this.sprites.push(sprite);	};					  	this.speed = 1;}GAME.BackgroundElement.prototype.setPosition = function(position){	var h = this.spriteWidth;		for (var i=0; i < this.sprites.length; i++) 	{		var pos = -position * this.speed;		pos += i *  h ;		pos %=  h * this.sprites.length ;		pos +=  h * 2;				this.sprites[i].position.x = Math.floor(pos) -GAME.xOffset		//this.sky[i].position.y = Math.round(this.sky[i].position.y);	};	}

As far as I can tell this code creates a series of the same sprite, enough to fill the screen horizontally, and then adds them to an array as well as to a container called owner. Owner, when specified, is the Background DisplayObjectContainer that wraps the whole background. The parallax movement occurs in setPosition, where the sprites in that BackgroundElement are moved by the speed variable (declared elsewhere, but for this demo the speed values fall roughly between 1.2/2.0 to 2.0/2.0). There's also something about an offset, but I'm going to ignore it because that part is the same for the later objects as well. Am I right about this? Or does something different happen here?



And here is the code for the the two larger trees (the ones that jump around when the window is resized):

GAME.Background.prototype.updateTransform = function(){	this.scrollPosition = GAME.camera.x + 4000// * GAME.time.DELTA_TIME;	var treePos = -this.scrollPosition * 1.5/2;	treePos %= this.width + 556;	treePos += this.width + 556;	treePos -= this.tree1.width/2;	this.tree1.position.x = treePos -GAME.xOffset;		var treePos2 = -(this.scrollPosition + this.width/2) * 1.5/2;	treePos2 %= this.width + 556;	treePos2 += this.width + 556;	treePos2 -= this.tree2.width/2;	this.tree2.position.x = treePos2 -GAME.xOffset;		//this.ground.setPosition(this.scrollPosition);	this.foggyTrees.setPosition(this.scrollPosition);	this.rearSilhouette.setPosition(this.scrollPosition);	this.rearCanopy.setPosition(this.scrollPosition);	this.farCanopy.setPosition(this.scrollPosition);	this.frontSilhouette.setPosition(this.scrollPosition);		this.roofLeaves.setPosition(this.scrollPosition);	//this.ground.setPosition(this.scrollPosition);		this.vines.setPosition(this.scrollPosition);			PIXI.DisplayObjectContainer.prototype.updateTransform.call( this );}

This is where the setPosition function is called for all the background elements, and the trees have their own logic placed directly here instead of also being made as background elements. The trees are regular PIXI.Sprite objects. Unfortunately, I can't tell why these two hardcoded trees would behave so differently from the BackgroundElements. All of the movement fundamentally boils down to:

var position = -this.scrollPosition * speed

They're also all PIXI.Sprites that exist in a container called Background (see full code here: http://www.goodboydigital.com/runpixierun/js/game/view/Background.js). Anyone have any idea why the BackgroundElements behave so differently from tree1 and tree2 on screen resize?



For further investigation here is the main engine class that has the creation, updating, and resizing of the Background wrapper: http://www.goodboydigital.com/runpixierun/js/game/view/RprView.js


If we can figure this out maybe I can write up up a formal blog/forum post about how to keep background parallax layers looking the same at multiple resolutions / after resizing.


Both my game and the Run Pixi Run demo are pre 2.0, if that matters.

Share this post

Link to post
Share on other sites

I've found a way to keep background layers consistent, and the trouble had to do with how my camera was moving.


Like most cameras, mine has a subtle smoothing as it follows the player's character... and it turns out this makes it entirely unfit as a reference point for parallax.


The game world and camera were basically the same thing. As the player moved, the camera slowly chased the player. The problem here is that the camera's movement is not the same thing as the players movement. My movement calculation would then pass on the amount the camera moved to the background layers, which would reduce the movement by some fraction and create a parallax effect. One immediate issue is that this conflates two positions, the camera position (which is slightly off from the player's real position) then becomes a factor in the parallax layers. A more drastic problem, is that when the screen is resized the camera now has to move to the new center point, and this movement would also offset the parallax layers. It also makes resolution a factor in the position of objects -- which I never noticed until I started testing on different displays.


In the end the solution was basically this:

var container = new PIXI.DisplayObjectContainer()stage.addChild(container)// core of the game simulationvar background = new Background() // all backgrounds and objects behind ships/entitiesvar world = new DisplayObjectContainer() // all the main entitiesvar foreground = new DisplayObjectContainer() // effects that need to appear on top of entitiesvar guiLayer = new DisplayObjectContainer() // gui / hud / menus etccontainer.addChild(background)container.addChild(world)container.addChild(foreground)// gui is separate because it doesn't move like the game simulation// and it also follows different scaling rulesstage.addChild(guilLayer) // skipping most of the Y component calculations for brevityfunction updateCamera(delta) {  // how much the player entity moved since last update  var deltaX = previousPlayerEntityX - currentPlayerEntityX   previousPlayerEntityX = currentPlayerEntityX  // coordinate that the camera is moving towards  var cameraTargetX = -currentPlayerX * display.ratio + window.innerWidth * 0.5  // move the camera (aka container) towards the camera target  var cameraDeltaX = cameraTargetX - container.x  container.x += cameraDeltaX * delta * cameraTrackingSpeed  // parallax ** moves by deltaX not cameraDeltaX  background.layer0.x += 0 // EDIT: changed to zero; this layer is just in the background, not in the distance  background.layer1.x += deltaX * 0.8  background.layer2.x += deltaX * 0.6  // etc}// resizing is as simple as:renderer.resize(window.innerWidth, window.innerHeight)container.scale.x = container.scale.y = display.ratio/* display ratio aka scale is same calculation as my first code sample */

The special part of the code is the distinction between deltaX and cameraDeltaX. DeltaX comes from the actual movements of the player entity in the game world, whereas cameraDeltaX is just the difference between where the camera is currently aimed and where it is going. The position of the layers moving in the  game world should be moved by deltaX, and then the whole game container can be moved by cameraDeltaX. This results in a game simulation which is not tied to the camera at all, and can be shrunk/zoomed without affecting the positions of objects. The camera could also be moved arbitrarily or hooked up to different entities.




Share this post

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.