The Parallax Effect
Adding some flair to my game with graphics and a cool parallax effect!
Adding some flair to my game with graphics and a cool parallax effect!
Why on Earth did I start building a game?
A little journey back to the past and why I started this blog
In the previous post, I focused on testing with Matter.js and creating a simple car. In this post, I’m going to take it to the next level. Since my background is in UX design, I really wanted to add some graphics to this game to make it more fun to play. Matter.js has some simple methods to add textures to different objects. First, we’ll add some graphics to the car elements. You can add a sprite to any Matter.Bodies
element. It’s very simple:
render: {
sprite: {
texture: <url to texture.png>,
xScale: 1, // 1:1 scaling
yScale: 1 // 1:1 scaling
}
}
I wanted a game with some cool physics, and what better car to test that than a monster truck? It has big wheels and a big suspension. So, based on some real photos, I drew a monster truck.
So when I start the game it looks something like this
I've always been a big fan of the parallax effect. It's such a simple yet powerful effect that gives the user a sense of depth. Since I'm building a 2D game, this is perfect. Some other inspiration I had came from a website for the game Firewatch. I really love the vibes of that website, and when it launched, it was a trendsetter! So, I drew a couple of layers that I could put in the background of the game. Oh, and just a small tip: I figured this out myself, but you can actually make the canvas
element transparent! So, we're going to use some CSS and JavaScript to animate the different backgrounds. My HTML looks like this:
<div id="parallax-container">
<div id="background1" class="parallax-layer" style="background-image: url('/assets/img/background1.png');"></div>
<div id="background2" class="parallax-layer" style="background-image: url('/assets/img/background2.png');"></div>
<div id="background3" class="parallax-layer" style="background-image: url('/assets/img/background3.png');"></div>
<div id="background4" class="parallax-layer" style="background-image: url('/assets/img/background4.png');"></div>
<div id="background5" class="parallax-layer" style="background-image: url('/assets/img/background5.png');"></div>
<div id="canvas-container"></div>
</div>
Now we need to animate the layers based on the camera position. But as I'm writing this, I realize I never told you how to set up a camera in Matter.js. Here we go! The Matter engine has some callback events where we can add functionality. So inside:
Matter.Events.on(engine, 'beforeUpdate', function(event) {
// do stuff
});
I want to focus the camera on the car, so we get the x and y position of the car and use the lookAt
function of Matter. This is how my code looks:
const zoomFactor = config.zoomFactor;
let cameraX = car.carBody.position.x;
let cameraY = car.carBody.position.y;
Matter.Render.lookAt(render, {
min: { x: cameraX - render.options.width / (2 * zoomFactor), y: cameraY - render.options.height / (2 * zoomFactor) },
max: { x: cameraX + render.options.width / (2 * zoomFactor), y: cameraY + render.options.height / (2 * zoomFactor) }
});
To explain a bit what's happening here, the lookAt
function positions and sizes the viewport around the given object bounds. In this case, we need a top-left corner (min) and a bottom-right corner (max) to center the car. I'm using the zoomFactor
to zoom in or out as needed.
Back to the parallax effect. The trick of the parallax effect is that layers in the background move "slower" than layers in the foreground. This creates a sense of depth. For example, in the background, I want to have clouds or the sky, and in the foreground, some mountains or trees. To achieve this, we need to animate the background-position
of each individual layer at different speeds. To adjust this per layer, I added an attribute to each layer like this:
<div id="parallax-container">
<div id="background1" class="parallax-layer" data-speed="0" style="background-image: url('/assets/img/background1.png');"></div>
<div id="background2" class="parallax-layer" data-speed="0.01" style="background-image: url('/assets/img/background2.png');"></div>
<div id="background3" class="parallax-layer" data-speed="0.02" style="background-image: url('/assets/img/background3.png');"></div>
<div id="background4" class="parallax-layer" data-speed="0.05" style="background-image: url('/assets/img/background4.png');"></div>
<div id="background5" class="parallax-layer" data-speed="0.08" style="background-image: url('/assets/img/background5.png');"></div>
<div id="canvas-container"></div>
</div>
Next, we need to calculate the position for each layer, so let's create a function to go through each layer and update the background-position
.
export function setupParallax() {
const parallaxLayers = document.querySelectorAll('.parallax-layer'); // Array with all the layers
function updateParallax(carBody) {
parallaxLayers.forEach(layer => {
const speed = layer.dataset.speed; // Get the speed attribute
const offset = -carBody.position.x * speed; // Get the x position of the car and multiply it by the speed
layer.style.backgroundPosition = `${offset}px 0`; // Set the background position
});
}
return { updateParallax };
}
And that's it, we have a animated parallax effect, the game looks like this: