making games - leanpubsamples.leanpub.com/making-games-sample.pdf · introduction...

48

Upload: others

Post on 16-Aug-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple
Page 2: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Making Games

Christopher Pitt

This book is for sale at http://leanpub.com/making-games

This version was published on 2015-09-14

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishingprocess. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools andmany iterations to get reader feedback, pivot until you have the right book and build traction onceyou do.

©2015 Christopher Pitt

Page 3: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Contents

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

The Game Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2Setting the stage for our game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2Creating sprites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4The Game Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

Player Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8Making modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8Detecting input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9Natural player movement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

Collision Detection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14Creating boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14Detecting circle collisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17Detecting rectangle collisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Gravity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21Cleaning up our existing code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21Adding gravity to the world . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28Allowing players to jump . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

Ladders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34Creating our first ladder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34Allowing players to climb ladders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37Allowing players to stand on ladders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

Page 4: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

IntroductionI’m a gamer. I’ve been a gamer since before I was a programmer. And yet I’ve never tried to builda game…until now. A few weeks ago, a coworker sketched a simple, beautiful platform game. Themoment I saw it, I knew I wanted to build it.

Knowing where to begin is always hard. Many books and tutorials about making games are oftenwritten in Java, C++ or some other language you haven’t learned yet. So the complexity of makinggames is increased with the need to learn new languages.

Instead I’m going to use modern JavaScript to make games for web browsers. They’re effortless touse and they work everywhere. If you’ve ever wanted to make a game, join me. We’ll start withnothing and build fun games, in no time at all.

If you have questions, feel free to ask me on Twitter¹.

¹https://twitter.com/assertchris

Page 5: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

The Game LoopGame loops are an essential part of every game. In this post we’re going to set the stage for ourgame, by creating a solid workflow and environment. We’ll see a few helpful libraries, and renderour first game character. This is gonna be fun!

..

You can find this code at https://github.com/assertchris/making-games/tree/the-game-loop

Setting the stage for our game

There is an lot of stuff to think about, when creating a browser-based game. We’re going to learntechniques that work in many platforms and languages.

We’re also going to use a 2D rendering engine called Pixi.js². It’s not a game engine or a physicsengine. We’re still going to have to learn how physics and gameplay work, but at least we won’t bewasting time on browser inconsistencies. We can pick those apart when we have nothing better todo. For now, we’re going to create an environment for Pixi to operate in:

1 {

2 "scripts": {

3 "build": "browserify main.js -t babelify --outfile main.dist.js",

4 "watch": "watchify main.js -t babelify --outfile main.dist.js"

5 },

6 "dependencies": {

7 "pixi.js": "^3.0"

8 },

9 "devDependencies": {

10 "babel": "^5.4",

11 "babelify": "^6.1",

12 "browserify": "^10.2",

13 "watchify": "^3.2"

14 }

15 }

²http://www.pixijs.com

Page 6: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

The Game Loop 3

..

This is from package.json

I want to be able to use ES6 features. At the time of writing, ES6 isn’t supported in any stable browser.So we’re going to cross-compile ES6 code to ES5 code that browsers can understand. The tools we’reusing for this are Babel³, Browserify⁴, and Watchify⁵.

Babel will cross-compile ES6 to ES5 for us. Browserify will connect all our ES6 modules (JavaScriptfiles) together, using import syntax. Watchify will trigger Babel/Browserify builds when our fileschange.

We’re also including the 3.0+ version of Pixi and defining two scripts. These NPM scripts⁶are just shorthand for commands we would otherwise enter in a terminal. Instead of typingbrowserify...main.dist.js we type npm run build.

1 <!doctype html>

2 <html>

3 <head>

4 <style>

5 html, body {

6 margin: 0;

7 padding: 0;

8 cursor: default;

9 background: #000;

10 overflow: hidden;

11 }

12 </style>

13 </head>

14 <body>

15 <script src="node_modules/pixi.js/bin/pixi.js"></script>

16 <script src="main.dist.js"></script>

17 </body>

18 </html>

..

This is from index.html

³https://babeljs.io/⁴http://browserify.org/⁵https://github.com/substack/watchify⁶https://docs.npmjs.com/misc/scripts

Page 7: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

The Game Loop 4

You might be wondering if we will need to include every JavaScript file we make. We won’t.Browserify connects together all the JavaScript files imported into main.js. It then runs a Babeltransformation on them, resulting in a single file. We only need to import that file.

You may also be wondering what elements we’re going to render our game to. The code we writeto render our game will embed itself in the document body.

..

You can serve these files by going to their folder, in terminal, and running php -S 127.0.0.1:8000.This spins up a PHP test server, at a URL you can put into your browser. You’ll need that beforeyou can load in sprite images, so try it now…

Creating sprites

Sprites are a common name for visual objects in a game. We can move them around (though oftenthey are responsible for moving themselves around) on the screen. We can interact with them

Mario is a sprite, and the platforms he walks on are sprites, and the clouds in the background aresprites. Think of sprites as slices of a design file, which we paint over abstract data structures. Thoseabstract data structures have a position, and sometimes a velocity. Those abstract data structuresare what we apply game rules to. They are our power-ups and enemies.

So how do we make them? Let’s start by creating a small PNG image. We’re using PNG because itallows us to make parts of the sprite texture transparent. You could use JPEG images for your sprites,if you want to.

Then we need to create a renderer, a stage, and a sprite:

1 var renderer = new PIXI.autoDetectRenderer(

2 window.innerWidth,

3 window.innerHeight,

4 {

5 "antialias": true,

6 "autoResize": true,

7 "transparent": true,

8 "resolution": 2

9 }

10 );

11

12 document.body.appendChild(renderer.view);

13

14 var sprite = new PIXI.Sprite.fromImage("player.png");

15 sprite.x = window.innerWidth / 2;

Page 8: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

The Game Loop 5

16 sprite.y = window.innerHeight / 2;

17

18 var stage = new PIXI.Container();

19 stage.addChild(sprite);

20

21 animate();

22

23 function animate() {

24 requestAnimationFrame(animate);

25

26 renderer.render(stage);

27 }

..

This is from main.js

Ok, there’s a lot going on here! Let’s look at this code in steps:

1. We create a renderer. Renderers are what convert Pixi abstractions (sprites, textures etc.) intocanvas or WebGL graphics. We don’t have to interact with them, but we always have to havea renderer for our Pixi stuff to show up.

We tell the renderer to take up the full width and height of the browser window. We alsotell it to anti-alias our graphics, and bump them up to retina resolution. We tell it to have atransparent background, and to resize all graphics to fit on the screen.

2. Then we create a new instance of PIXI.Sprite, using the PNG image we created earlier. Bydefault, it has a position of x = 0 and y = 0. We can position it in the centre of the screeninstead.

We have to create a root sprite container, often called a stage, and append the sprite to thestage. It’s less complicated than it sounds. Think of HTML documents. They have a root htmlelement which we add all other elements to. This is the same kind of thing.

3. Finally we create an animate function and make sure that the renderer renders our stage andsprite.

We’re using the requestAnimationFrame() function as a way rendering without blockingthe JavaScript thread. There’s a long discussion we could have about that. For now, it’s onlyimportant to know that requestAnimationFrame() happens many times a second.

The goal is for our game is to render between 30 and 60 frames per second, while doing all sorts ofplayer and world calculations in the background. This is the function we need to use for that all togo smoothly.

Page 9: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

The Game Loop 6

The Game Loop

The game loop will control the flow of our game. It’s a repetitive process of reading input, calculatingchanges in state and rendering output to the screen.

So far we’ve set up a lot of scaffolding and all we’re doing is rendering a static image. Let’s move itaround a bit! To begin with, we’re going to create a Player class, so we can track the position of theplayer:

1 class Player {

2 constructor(sprite, x, y) {

3 this.sprite = sprite;

4 this.x = x;

5 this.y = y;

6

7 this.sprite.x = this.x;

8 this.sprite.y = this.y;

9 }

10

11 animate(state) {

12 this.x += 5;

13

14 if (this.x > window.innerWidth) {

15 this.x = 0;

16 }

17

18 this.sprite.x = this.x;

19 this.sprite.y = this.y;

20 }

21 }

..

This is from main.js

This is what JavaScript classes look like, in ES6. The Player class has a constructor, which we useto store a reference to the sprite, and the initial x and y position.

It also has an animate() method, which we will call many times a second. We can use the methodto figure out if the player needs to change position or do something else. In this case, we incrementthe sprite x position a bit. If the player/sprite moves off the right-hand side of the screen, we moveit back to the left-hand side of the screen.

We also have to change how we create the sprite:

Page 10: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

The Game Loop 7

1 var sprite = new PIXI.Sprite.fromImage("player.png");

2

3 var player = new Player(

4 sprite,

5 window.innerWidth / 2,

6 window.innerHeight / 2

7 );

8

9 stage.addChild(sprite);

10

11 var state = {

12 "renderer": renderer,

13 "stage": stage

14 };

15

16 animate();

17

18 function animate() {

19 requestAnimationFrame(animate);

20

21 player.animate(state);

22

23 renderer.render(stage);

24 }

..

This is from main.js

Now we’re beginning to see the loop in our game. Many times a second, we’re telling our playerto animate. The player takes into account some internal logic, and adjusts its sprite. The rendererrenders the output, and what we see is a smooth animation.

..

You can see a video of this code at https://vimeo.com/137681095.

The only thing we’re lacking here is input, but we’ll get to that soon!

Page 11: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Player InputWhat’s the difference between a movie and a game? Player input! In fact, it’s such a critical pieceof game design that games are often defined by how they take player input.

Racing games rely on constant, subtle input. If you lift your fingers from the keyboard, or lift yourfoot off the pedal, you lose momentum. If you don’t enter the right combination of keys, your flightsimulation will crash.

Platform games (like the one we’re building) need input from both hands. Often the right-hand sideof the keyboard is for movement. Often the left-hand side of the keyboard is for player actions, likeshooting or opening doors.

Perhaps you want to design a platform game like Terraria⁷, which uses the mouse for player actionsand projective aim. Perhaps you want to use WASD to move your player around the screen. Let’sget to work!

..

You can find this code at https://github.com/assertchris/making-games/tree/player-input

Making modules

Last time we create the Player class, and made it responsible for the movement of the player. We leftit in main JavaScript file, but that’ll become messy once we have more classes. Let’s make a moduleof it:

1 class Player {

2 constructor(sprite, x, y) {

3 this.sprite = sprite;

4 this.x = x;

5 this.y = y;

6

7 this.sprite.x = this.x;

8 this.sprite.y = this.y;

9 }

10

11 animate(state) {

⁷https://terraria.org

Page 12: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Player Input 9

12 this.x += 5;

13

14 if (this.x > window.innerWidth) {

15 this.x = 0;

16 }

17

18 this.sprite.x = this.x;

19 this.sprite.y = this.y;

20 }

21 }

22

23 export default Player;

..

This is from player.js

The Player class is identical to the one we created before. The important bit is export default

Player. This makes the player class importable in the following way:

1 import Player from "./player";

2

3 var player = new Player(

4 sprite,

5 window.innerWidth / 2,

6 window.innerHeight / 2

7 );

This is a common pattern for splitting code (and often single classes) into their own files. It keepsthings lean, and we gain the ability to reuse code without copy and paste.

..

Remember to check that the sprite still moves across the screen. As before, you can start a testingserver with php -S 127.0.0.1:8000.

Detecting input

When I first tried detecting input, I tried to add event listeners to everything. I tried adding eventlisteners to the player and event listeners to the stage. Aaaand things got out of hand.

Page 13: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Player Input 10

The trick is to detect events at the highest level, and save details of them to a game state object.Last time we created this kind of state object, and passed it to the player’s animate() method. Let’sexpand on that state:

1 var state = {

2 "renderer": renderer,

3 "stage": stage,

4 "keys": {},

5 "clicks": {},

6 "mouse": {}

7 };

8

9 window.addEventListener("keydown", function(event) {

10 state.keys[event.keyCode] = true;

11 });

12

13 window.addEventListener("keyup", function(event) {

14 state.keys[event.keyCode] = false;

15 });

16

17 window.addEventListener("mousedown", function(event) {

18 state.clicks[event.which] = {

19 "clientX": event.clientX,

20 "clientY": event.clientY

21 };

22 });

23

24 window.addEventListener("mouseup", function(event) {

25 state.clicks[event.which] = false;

26 });

27

28 window.addEventListener("mousemove", function(event) {

29 state.mouse.clientX = event.clientX;

30 state.mouse.clientY = event.clientY;

31 });

32

33 animate();

34

35 function animate() {

36 requestAnimationFrame(animate);

37

38 player.animate(state);

39

40 renderer.render(stage);

41 }

Page 14: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Player Input 11

..

This is from main.js

We began by adding the renderer and state to the game state object. Now we’ve added keys, clicks,and mouse properties which track buttons and movement.

..

For this next part, I had to adjust the CSS of index.html so that the canvas is absolutely positioned.This made it possible to track the mouse movement more accurately. Check out the source code tosee the change.

As we add more objects to the game, we’ll pass the game state object to each of them. They canuse it to work out what they should do when the player interacts with the game. Let’s remove thecurrent player movement and add input-driven movement:

1 animate(state) {

2 if (state.keys[37]) { // left

3 this.x = Math.max(

4 0, this.x - 5

5 );

6 }

7

8 if (state.keys[39]) { // right

9 this.x = Math.min(

10 window.innerWidth - 64, this.x + 5

11 );

12 }

13

14 if (state.clicks[1]) { // left click

15 this.x = state.clicks[1].clientX;

16 }

17

18 this.sprite.x = this.x;

19 this.sprite.y = this.y;

20 }

..

This is from player.js

Page 15: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Player Input 12

Little actually changes in the animate() method. We check for two arrow keys, and move if theplayer and pressed one of them. We we also check if the player has clicked, and immediately movethe player if so.

..

Pay no attention to the Math functions and magic number 64. These are just there to prevent theplayer sprite from going off-screen. We’ll remove these things when we start building walls…

Natural player movement

The player can now move a bit. It doesn’t feel great though. The moment we let go of an arrow key,it just grinds to a halt. And it has only one speed: slow.

What we need is some acceleration, to allow the player to speed up in a direction. We could also usesome friction, to slow the player down when they are no longer accelerating. Let’s add these:

1 class Player {

2 constructor(sprite, x, y) {

3 this.sprite = sprite;

4 this.x = x;

5 this.y = y;

6 this.velocityX = 0;

7 this.maximumVelocityX = 12;

8 this.accelerationX = 2;

9 this.frictionX = 0.9;

10

11 this.sprite.x = this.x;

12 this.sprite.y = this.y;

13 }

14

15 animate(state) {

16 if (state.keys[37]) { // left

17 this.velocityX = Math.max(

18 this.velocityX - this.accelerationX,

19 this.maximumVelocityX * -1

20 );

21 }

22

23 if (state.keys[39]) { // right

24 this.velocityX = Math.min(

25 this.velocityX + this.accelerationX,

26 this.maximumVelocityX

Page 16: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Player Input 13

27 );

28 }

29

30 this.velocityX *= this.frictionX;

31

32 this.x += this.velocityX;

33

34 this.sprite.x = this.x;

35 this.sprite.y = this.y;

36 }

37 }

..

This is from player.js

Woah there, tiger! Let’s review this in steps:

1. We’ve created a handful of properties: velocity, maximum velocity, acceleration, and friction.We’ve set them to reasonable defaults. Feel free to experiment with their values until youappreciate what each of them do.

2. We can begin to track the acceleration of our player, in either direction. If we’re pressing theleft key, we start to accelerate the player in that direction. There’s a maximum accelerationthat the player can have, so they don’t continue to gain speed.

3. Without some kind of counter-force, the player will continue to move in the same direction.This is like what happens in space, where there is no air resistance or gravity to counteractmomentum.

We define this counter-force as friction, and multiply the velocity by it. Without acceleration,this means the momentum tends towards zero. It gives the appearance that the player hasstopped.

We define this counter-force as friction, and multiply the velocity by it. Without acceleration, thismeans the momentum tends towards zero. It gives the appearance that the player has stopped.

..

You can see a video of this code at https://vimeo.com/137681201.

Page 17: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Collision DetectionIt’s time for us to talk about collision detection. It affects obvious parts of our game, like walls wecan’t walk through. Like floors we can’t fall through. It also affects obscure parts of our game likeweapon projectiles, and checkpoints.

We’re not going to get to gravity yet. Nor are we going to look at player health or respawning. Floors,projectiles and checkpoints are interesting, but they deserve sections of their own. We’re going tocreate impassable objects. We’re going to learn ways of knowing whether two object occupy thesame space.

I’ve spent a bit of time researching this topic. It seems there are many ways of working out whethertwo things occupy the same space. Some of them are easy to explain and implement. We’ll look atthose. Other ways are not easy. They’re still cool though.

..

You can find this code at https://github.com/assertchris/making-games/tree/collision-detection

Creating boxes

The player is only one of many objects that will exist on the screen at one time. It’s a platform game,so we can expect at least one platform on screen, at a time.

Platforms have some interesting characteristics though. Sometimes they allow players to fall downthrough them. Like when you’re standing on a platform and you hold down and press jump (at thesame time). Some games take that sequence to mean that you want to fall through the platform.

Some games allow players to jump through the bottom of platforms. This enables vertical movementwithout gaps in overhead platforms.

Sometimes platforms move!

Platforms are so special that we’ll spend a few sections just implementing their different behaviours.Now we’re going to focus on another common object. The generic box.

Think of this box as an ancestor to the platform. It may share some platform functionality, but themain reason it exists to for things to collide with it. Especially things like the player.

The boxes we’re going tomakemight not even look like boxes.Whenwe get to implementing gravity,we’ll need a wide, thin box to keep the player from falling out of the world. We’ll need tall, thin

Page 18: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Collision Detection 15

boxes to stop the player from running off the side of the floor. We’ll make walls out of them. Wemay even make boxes out of the boxes. Big, wooden, “jump on me to get to higher things” boxes.

Ok, enough talking.

1 class Box {

2 constructor(sprite, x, y, width, height) {

3 this.sprite = sprite;

4 this.x = x;

5 this.y = y;

6 this.width = width;

7 this.height = height;

8

9 this.sprite.x = this.x;

10 this.sprite.y = this.y;

11 }

12

13 animate(state) {

14 this.sprite.x = this.x;

15 this.sprite.y = this.y;

16 }

17 }

18

19 export default Box;

..

This is from box.js

To make this class, I copy and pasted the Player class and deleted a bunch of stuff. I did have to adda width and height property to it. We’ll get to that in a bit.

Next, we need to add two of these boxes to the stage:

1 var player_sprite = new PIXI.Sprite.fromImage(

2 "player.png"

3 );

4

5 var player = new Player(

6 player_sprite,

7 window.innerWidth * 2/4,

8 200,

9 64,

10 64

Page 19: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Collision Detection 16

11 );

12

13 var barrel_sprite1 = new PIXI.Sprite.fromImage(

14 "barrel.png"

15 );

16

17 var barrel1 = new Box(

18 barrel_sprite1,

19 window.innerWidth * 1/4,

20 152,

21 64,

22 64

23 );

24

25 var barrel_sprite2 = new PIXI.Sprite.fromImage(

26 "barrel.png"

27 );

28

29 var barrel2 = new Box(

30 barrel_sprite2,

31 window.innerWidth * 3/4,

32 248,

33 64,

34 64

35 );

36

37 stage.addChild(player_sprite);

38 stage.addChild(barrel_sprite1);

39 stage.addChild(barrel_sprite2);

40

41 var state = {

42 "renderer": renderer,

43 "stage": stage,

44 "keys": {},

45 "clicks": {},

46 "mouse": {},

47 "objects": [

48 player,

49 barrel1,

50 barrel2

51 ]

52 };

Page 20: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Collision Detection 17

..

This is from main.js

That’s strange! I’ve created two new Box instances, and called them barrel. That’s because we’reabout to look at…

Detecting circle collisions

I want you to create circles for these first few boxes. The type of collision detection we’re going todo first, is with circles. It’s ok that the box has a width and height, instead of a radius. You won’tuse this kind of collision detection often, unless your platform game has a lot of circles in it.

Let’s see how this kind of detection works:

1 class Player {

2 constructor(sprite, x, y, width, height) {

3 this.sprite = sprite;

4 this.x = x;

5 this.y = y;

6 this.width = width;

7 this.height = height;

8 this.velocityX = 0;

9 this.maximumVelocityX = 12;

10 this.accelerationX = 2;

11 this.frictionX = 0.9;

12

13 this.sprite.x = this.x;

14 this.sprite.y = this.y;

15 }

16

17 animate(state) {

18 if (state.keys[37]) { // left

19 this.velocityX = Math.max(

20 this.velocityX - this.accelerationX,

21 this.maximumVelocityX * -1

22 );

23 }

24

25 if (state.keys[39]) { // right

26 this.velocityX = Math.min(

27 this.velocityX + this.accelerationX,

28 this.maximumVelocityX

Page 21: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Collision Detection 18

29 );

30 }

31

32 this.velocityX *= this.frictionX;

33

34 var move = true;

35

36 state.objects.forEach((object) => {

37 if (object === this) {

38 return;

39 }

40

41 var deltaX = this.x - object.x;

42 var deltaY = this.y - object.y;

43

44 var distance = Math.sqrt(

45 deltaX * deltaX + deltaY * deltaY

46 );

47

48 if (distance < this.width / 2 + object.width / 2) {

49 if (this.velocityX < 0 && object.x <= this.x) {

50 move = false;

51 }

52

53 if (this.velocityX > 0 && object.x >= this.x) {

54 move = false;

55 }

56 }

57 });

58

59 if (move) {

60 this.x += this.velocityX;

61 }

62

63 this.sprite.x = this.x;

64 this.sprite.y = this.y;

65 }

66 }

67

68 export default Player;

..

This is from player.js

Page 22: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Collision Detection 19

The first thing we need to do is define a width and height. While we’re pretending that our playersand boxes are circles, we only need half a width as a radius…

Next we check each object in the state. We can ignore any the player object because we don’t needto know when something collides with itself. We do check everything else though.

Circles collide when the distance between their origin is less than their combined radii. Their middlepoints are so close that their lines have to be overlapping.

We do a quick check to see whether the direction the player is moving in is where the box is. If that’sthe case then we prevent the player from moving in that direction.

Give it a go. Its pretty fun to see how non-squares block each other. Of course they all have to beperfect circles for this simple algorithm to work.

Detecting rectangle collisions

Detecting collisions of rectangles is almost as easy as circles. Go ahead and swap the barrel imagefor a box image. You can even adjust the bootstrap code to reflect the squareness of your boxes.

This time round, we’ll treat the player like a rectangle. Instead of radii, we need to check if there aregaps between the player rectangle and either box. We call this axis-aligned bounding box collisiondetection (or AABB for short).

If there is no gap, and the player wants to move in the direction of the box, then we prevent thatfrom happening:

1 var move = true;

2

3 state.objects.forEach((object) => {

4 if (object === this) {

5 return;

6 }

7

8 if (this.x < object.x + object.width &&

9 this.x + this.width > object.x &&

10 this.y < object.y + object.height &&

11 this.y + this.height > object.y) {

12

13 if (this.velocityX < 0 && object.x <= this.x) {

14 move = false;

15 }

16

17 if (this.velocityX > 0 && object.x >= this.x) {

18 move = false;

19 }

Page 23: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Collision Detection 20

20 }

21 });

22

23 if (move) {

24 this.x += this.velocityX;

25 }

..

This is from player.js

These are simple methods for detecting collisions, but there are others. There’s one that usesprojection-based vector mathematics⁸ to determine overlap. There’s one that checks each line, ina couple of polygons, to see if any lines intersect⁹. It’s crazy.

You could even experiment with groups of circles colliding together. That might be fun. I’m goingto run this little green circle into these little red boxes for a bit…

..

You can see a video of this code at https://vimeo.com/137681307.

⁸http://www.sevenson.com.au/actionscript/sat⁹http://stackoverflow.com/questions/9043805/test-if-two-lines-intersect-javascript-function

Page 24: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

GravityThis time we’re going to work on our code structure, and add gravity to our game. We’ve alreadydone most of the work for gravity.

..

You can find this code at https://github.com/assertchris/making-games/tree/gravity

Cleaning up our existing code

We need to clean up a few things! First, let’s swap out x, y, width, and height (both in Box andPlayer) with PIXI.Rectangle. They have these properties, but they also interact with the rest ofPIXI in interesting ways.

1 class Box {

2 constructor(sprite, rectangle) {

3 this.sprite = sprite;

4 this.rectangle = rectangle;

5 }

6

7 animate(state) {

8 if (this.sprite) {

9 this.sprite.x = this.rectangle.x;

10 this.sprite.y = this.rectangle.y;

11 }

12 }

13 }

14

15 export default Box;

..

This is from box.js

Page 25: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 22

1 class Player {

2 constructor(sprite, rectangle) {

3 this.sprite = sprite;

4 this.rectangle = rectangle;

5

6 this.velocityX = 0;

7 this.maximumVelocityX = 12;

8 this.accelerationX = 2;

9 this.frictionX = 0.9;

10 }

11

12 animate(state) {

13 if (state.keys[37]) {

14 this.velocityX = Math.max(

15 this.velocityX - this.accelerationX,

16 this.maximumVelocityX * -1

17 );

18 }

19

20 if (state.keys[39]) {

21 this.velocityX = Math.min(

22 this.velocityX + this.accelerationX,

23 this.maximumVelocityX

24 );

25 }

26

27 this.velocityX *= this.frictionX;

28

29 var move = true;

30

31 state.objects.forEach((object) => {

32 if (object === this) {

33 return;

34 }

35

36 var me = this.rectangle;

37 var you = object.rectangle;

38

39 if (me.x < you.x + you.width &&

40 me.x + me.width > you.x &&

41 me.y < you.y + you.height &&

42 me.height + me.y > you.y) {

43

44 if (this.velocityX < 0 && you.x <= me.x) {

45 move = false;

Page 26: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 23

46 }

47

48 if (this.velocityX > 0 && you.x >= me.x) {

49 move = false;

50 }

51 }

52 });

53

54 if (move) {

55 this.rectangle.x += this.velocityX;

56 }

57

58 this.sprite.x = this.rectangle.x;

59 this.sprite.y = this.rectangle.y;

60 }

61 }

62

63 export default Player;

..

This is from player.js

Notice how much code we can delete? The comparisons get a little verbose, but nothing a localvariable or two can’t fix. I also realised that we canmove the initial x and y set operations to animate.

Next, I want to encapsulate the event, renderer and stage logic into a Game class:

1 class Game {

2 constructor() {

3 this.state = {

4 "keys": {},

5 "clicks": {},

6 "mouse": {},

7 "objects": []

8 };

9 }

10

11 get stage() {

12 if (!this._stage) {

13 this._stage = this.newStage();

14 }

15

16 return this._stage;

Page 27: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 24

17 }

18

19 set stage(stage) {

20 this._stage = stage;

21 }

22

23 newStage() {

24 return new PIXI.Container();

25 }

26

27 get renderer() {

28 if (!this._renderer) {

29 this._renderer = this.newRenderer();

30 }

31

32 return this._renderer;

33 }

34

35 set renderer(renderer) {

36 this._renderer = renderer;

37 }

38

39 newRenderer() {

40 return new PIXI.autoDetectRenderer(

41 window.innerWidth,

42 window.innerHeight,

43 this.newRendererOptions()

44 );

45 }

46

47 newRendererOptions() {

48 return {

49 "antialias": true,

50 "autoResize": true,

51 "transparent": true,

52 "resolution": 2

53 };

54 }

55

56 animate() {

57 var caller = () => {

58 requestAnimationFrame(caller);

59

60 this.state.renderer = this.renderer;

61 this.state.stage = this.stage;

Page 28: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 25

62

63 this.state.objects.forEach((object) => {

64 object.animate(this.state);

65 })

66

67 this.renderer.render(this.stage);

68 };

69

70 caller();

71

72 return this;

73 }

74

75 addEventListenerToElement(element) {

76 element.addEventListener("keydown", (event) => {

77 this.state.keys[event.keyCode] = true;

78 });

79

80 element.addEventListener("keyup", (event) => {

81 this.state.keys[event.keyCode] = false;

82 });

83

84 element.addEventListener("mousedown", (event) => {

85 this.state.clicks[event.which] = {

86 "clientX": event.clientX,

87 "clientY": event.clientY

88 };

89 });

90

91 element.addEventListener("mouseup", (event) => {

92 this.state.clicks[event.which] = false;

93 });

94

95 element.addEventListener("mousemove", (event) => {

96 this.state.mouse.clientX = event.clientX;

97 this.state.mouse.clientY = event.clientY;

98 });

99

100 return this;

101 }

102

103 addRendererToElement(element) {

104 element.appendChild(this.renderer.view);

105

106 return this;

Page 29: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 26

107 }

108

109 addObject(object) {

110 this.state.objects.push(object);

111

112 if (object.sprite) {

113 this.stage.addChild(object.sprite);

114 }

115

116 return this;

117 }

118 }

119

120 export default Game;

..

This is from game.js

Notice the ES6 class getters and setters. They’re useful for filling optional dependencies as needed.We can override renderer and stage if we need to, but they have sensible defaults too.

This is mostly just moving the code that was in main.js to game.js. The only notable difference isthat we no longer require sprites to be added to the stage separately to adding objects to the state.

I’m in two minds about this. On the one hand, it’s much clearer what’s going on if we add themto the stage by hand. On the other, will we be adding sprites (joined to objects) without adding theobjects? I don’t think so.

Perhaps we’ll come back and change that. For now, it makes things a little cleaner. So what doesmain.js look like now?

1 import Game from "./game";

2 import Player from "./player";

3 import Box from "./box";

4

5 var game = new Game();

6

7 game.addObject(

8 new Box(

9 new PIXI.Sprite.fromImage(

10 "box.png"

11 ),

12 new PIXI.Rectangle(

13 window.innerWidth * 1/4, 152, 64, 64

Page 30: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 27

14 )

15 )

16 );

17

18 game.addObject(

19 new Player(

20 new PIXI.Sprite.fromImage(

21 "player.png"

22 ),

23 new PIXI.Rectangle(

24 window.innerWidth * 2/4, 200, 64, 64

25 )

26 )

27 );

28

29 game.addObject(

30 new Box(

31 new PIXI.Sprite.fromImage(

32 "box.png"

33 ),

34 new PIXI.Rectangle(

35 window.innerWidth * 3/4, 248, 64, 64

36 )

37 )

38 );

39

40 game.addEventListenerToElement(window);

41 game.addRendererToElement(document.body);

42 game.animate();

..

This is from main.js

That looks much better! We have enough control to set starting points for each game object. Butafter the first frame, the animate methods take over. Things like gravity and collisions will start tocontrol how the game progresses. It was always going to be like that, so this file now feels like it too.

We could confine the events and renderer to a smaller set of elements. We could add any numberof objects to the game from here. Everything else game-related is inside the game class. Everythingelse player or box related is inside those classes. It’s neat!

Page 31: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 28

Adding gravity to the world

One of the things that make platform games fun is a moderate amount of physics are at play. Let’sadd walls and a floor:

1 var game = new Game();

2

3 game.addObject(

4 new Box(

5 null,

6 new PIXI.Rectangle(

7 -10, 0, 10, window.innerHeight

8 )

9 )

10 );

11

12 game.addObject(

13 new Box(

14 null,

15 new PIXI.Rectangle(

16 0, window.innerHeight, window.innerWidth, 10

17 )

18 )

19 );

20

21 game.addObject(

22 new Box(

23 null,

24 new PIXI.Rectangle(

25 window.innerWidth, 0, 10, window.innerHeight

26 )

27 )

28 );

29

30 game.addObject(

31 new Player(

32 new PIXI.Sprite.fromImage(

33 "player.png"

34 ),

35 new PIXI.Rectangle(

36 window.innerWidth * 2/4, 200, 64, 64

37 )

38 )

39 );

Page 32: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 29

..

This is from main.js

Instead of two red squares, we have three thin rectangles. They collide in the same way. Now, let’ssee about adding that gravity…

1 class Player {

2 constructor(sprite, rectangle) {

3 this.sprite = sprite;

4 this.rectangle = rectangle;

5

6 this.velocityX = 0;

7 this.maximumVelocityX = 12;

8 this.accelerationX = 2;

9 this.frictionX = 0.9;

10

11 this.velocityY = 0;

12 this.maximumVelocityY = 30;

13 this.accelerationY = 10;

14 this.jumpVelocity = -50;

15 }

16

17 animate(state) {

18 if (state.keys[37]) {

19 this.velocityX = Math.max(

20 this.velocityX - this.accelerationX,

21 this.maximumVelocityX * -1

22 );

23 }

24

25 if (state.keys[39]) {

26 this.velocityX = Math.min(

27 this.velocityX + this.accelerationX,

28 this.maximumVelocityX

29 );

30 }

31

32 this.velocityX *= this.frictionX;

33

34 this.velocityY = Math.min(

35 this.velocityY + this.accelerationY,

36 this.maximumVelocityY

37 );

Page 33: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 30

38

39 state.objects.forEach((object) => {

40 if (object === this) {

41 return;

42 }

43

44 var me = this.rectangle;

45 var you = object.rectangle;

46

47 if (me.x < you.x + you.width &&

48 me.x + me.width > you.x &&

49 me.y < you.y + you.height &&

50 me.y + me.height > you.y) {

51

52 if (this.velocityY > 0 && you.y >= me.y) {

53 this.velocityY = 0;

54 return;

55 }

56

57 if (this.velocityY < 0 && you.y <= me.y) {

58 this.velocityY = this.accelerationY;

59 return;

60 }

61

62 if (this.velocityX < 0 && you.x <= me.x) {

63 this.velocityX = 0;

64 return;

65 }

66

67 if (this.velocityX > 0 && you.x >= me.x) {

68 this.velocityX = 0;

69 return;

70 }

71 }

72 });

73

74 this.rectangle.x += this.velocityX;

75 this.rectangle.y += this.velocityY;

76

77 this.sprite.x = this.rectangle.x;

78 this.sprite.y = this.rectangle.y;

79 }

80 }

81

82 export default Player;

Page 34: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 31

..

This is from player.js

We begin by creating a set of properties to match the ones we made to track horizontal movement.We don’t need vertical friction, since that level of detail is often omitted from platform games.

We also have to track vertical and horizontal collisions. When the collision is with the player and aplatform/floor then we stop the downward velocity. When it’s against a ceiling, we replace upwardsvelocity with the force of gravity.

Allowing players to jump

Jumping is simply reversing gravity for a short time:

1 animate(state) {

2 if (state.keys[37]) {

3 this.velocityX = Math.max(

4 this.velocityX - this.accelerationX,

5 this.maximumVelocityX * -1

6 );

7 }

8

9 if (state.keys[39]) {

10 this.velocityX = Math.min(

11 this.velocityX + this.accelerationX,

12 this.maximumVelocityX

13 );

14 }

15

16 this.velocityX *= this.frictionX;

17

18 this.velocityY = Math.min(

19 this.velocityY + this.accelerationY,

20 this.maximumVelocityY

21 );

22

23 state.objects.forEach((object) => {

24 if (object === this) {

25 return;

26 }

27

28 var me = this.rectangle;

Page 35: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 32

29 var you = object.rectangle;

30

31 if (me.x < you.x + you.width &&

32 me.x + me.width > you.x &&

33 me.y < you.y + you.height &&

34 me.y + me.height > you.y) {

35

36 if (this.velocityY > 0 && you.y >= me.y) {

37 this.velocityY = 0;

38 this.grounded = true;

39 this.jumping = false;

40 return;

41 }

42

43 if (this.velocityY < 0 && you.y <= me.y) {

44 this.velocityY = this.accelerationY;

45 return;

46 }

47

48 if (this.velocityX < 0 && you.x <= me.x) {

49 this.velocityX = 0;

50 return;

51 }

52

53 if (this.velocityX > 0 && you.x >= me.x) {

54 this.velocityX = 0;

55 return;

56 }

57 }

58 });

59

60 if (state.keys[32] && !this.jumping && this.grounded) {

61 this.velocityY = this.jumpVelocity;

62 this.jumping = true;

63 this.grounded = false;

64 }

65

66 this.rectangle.x += this.velocityX;

67 this.rectangle.y += this.velocityY;

68

69 this.sprite.x = this.rectangle.x;

70 this.sprite.y = this.rectangle.y;

71 }

Page 36: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Gravity 33

..

This is from player.js

With this, we’ve mapped the space key to jump. We add the keyboard check after the collision checkbecause we only want our player to jump if they are standing on a platform or floor.

Now it’s possible to create levels, out of boxes. To give some of them visible textures, and to jumparound them. Spend some time making a level and jumping around in it!

..

You can see a video of this code at https://vimeo.com/137466509.

Page 37: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

LaddersIt would be pretty limiting if players could onlymove upwards by jumping through gaps, or on boxes.Those aren’t the only options though. We still have to learn about elevators, stairs, and ladders. Solet’s build a ladder!

..

You can find this code at https://github.com/assertchris/making-games/tree/ladders

Creating our first ladder

Let’s begin our ladder by copying the Box class:

1 class Ladder {

2 constructor(sprite, rectangle) {

3 this.sprite = sprite;

4 this.rectangle = rectangle;

5 }

6

7 animate(state) {

8 if (this.sprite) {

9 this.sprite.x = this.rectangle.x;

10 this.sprite.y = this.rectangle.y;

11 }

12 }

13 }

14

15 export default Ladder;

..

This is from ladder.js

Page 38: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 35

1 import Ladder from "./ladder";

2

3 game.addObject(

4 new Ladder(

5 new PIXI.Sprite.fromImage(

6 "ladder.png"

7 ),

8 new PIXI.Rectangle(

9 200, window.innerHeight - 192, 64, 192

10 )

11 )

12 );

13

14 game.addObject(

15 new Box(

16 new PIXI.Sprite.fromImage(

17 "box.png"

18 ),

19 new PIXI.Rectangle(

20 136, window.innerHeight - 191, 64, 64

21 )

22 )

23 );

..

This is from main.js

You’ll need to make a ladder.png image. I chose to use a simple 64 by 192 sprite. Be sure to add thisto the game before adding the player, or the ladder sprite will be in front of the player sprite.

You’ll notice that the player bumps into the ladder, as if it was a box. We’ll need to add a few gettersto our boxes and ladders to that the collision detection can decide whether they still collide witheach other…

Page 39: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 36

1 if (me.x < you.x + you.width &&

2 me.x + me.width > you.x &&

3 me.y < you.y + you.height &&

4 me.y + me.height > you.y) {

5

6 if (object.collides && this.velocityY > 0 && you.y >= me.y) {

7 this.velocityY = 0;

8 this.grounded = true;

9 this.jumping = false;

10 return;

11 }

12

13 if (object.collides && this.velocityY < 0 && you.y <= me.y) {

14 this.velocityY = this.accelerationY;

15 return;

16 }

17

18 if (object.collides && this.velocityX < 0 && you.x <= me.x) {

19 this.velocityX = 0;

20 return;

21 }

22

23 if (object.collides && this.velocityX > 0 && you.x >= me.x) {

24 this.velocityX = 0;

25 return;

26 }

27 }

..

This is from player.js

We give Box and Ladder a collides property so that Player.animate and ignore collisions withobjects that players shouldn’t collide with. If we were going to allow multiple players in the samegame/level thenwewould also add a collides property to Player. That is unless wewantedmultipleplayers to collide with each other.

..

You can see a video of this code at https://vimeo.com/137673699.

Page 40: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 37

Allowing players to climb ladders

For players to climb ladders, we have to be able to tell if they’re trying to climb one. We also thenhave to suspend gravity and side motion so they down fall off or slide off:

1 class Player {

2 constructor(sprite, rectangle) {

3 this.sprite = sprite;

4 this.rectangle = rectangle;

5

6 this.velocityX = 0;

7 this.maximumVelocityX = 12;

8 this.accelerationX = 2;

9 this.frictionX = 0.9;

10

11 this.velocityY = 0;

12 this.maximumVelocityY = 20;

13 this.accelerationY = 5;

14 this.jumpVelocity = -40;

15

16 this.climbingSpeed = 10;

17 }

18

19 animate(state) {

20 if (state.keys[37]) {

21 this.velocityX = Math.max(

22 this.velocityX - this.accelerationX,

23 this.maximumVelocityX * -1

24 );

25 }

26

27 if (state.keys[39]) {

28 this.velocityX = Math.min(

29 this.velocityX + this.accelerationX,

30 this.maximumVelocityX

31 );

32 }

33

34 this.velocityX *= this.frictionX;

35

36 this.velocityY = Math.min(

37 this.velocityY + this.accelerationY,

38 this.maximumVelocityY

39 );

40

Page 41: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 38

41 var onLadder = false;

42

43 state.objects.forEach((object) => {

44 if (object === this) {

45 return;

46 }

47

48 var me = this.rectangle;

49 var you = object.rectangle;

50

51 if (me.x < you.x + you.width &&

52 me.x + me.width > you.x &&

53 me.y < you.y + you.height &&

54 me.y + me.height > you.y) {

55

56 if (object.collides && this.velocityY > 0 && you.y >= me.y) {

57 this.velocityY = 0;

58 this.grounded = true;

59 this.jumping = false;

60 return;

61 }

62

63 if (object.collides && this.velocityY < 0 && you.y <= me.y) {

64 this.velocityY = this.accelerationY;

65 return;

66 }

67

68 if (object.collides && this.velocityX < 0 && you.x <= me.x) {

69 this.velocityX = 0;

70 return;

71 }

72

73 if (object.collides && this.velocityX > 0 && you.x >= me.x) {

74 this.velocityX = 0;

75 return;

76 }

77

78 if (object.constructor.name === "Ladder") {

79 onLadder = true;

80

81 if (state.keys[38] || state.keys[40]) {

82 this.grounded = false;

83 this.jumping = false;

84 this.climbing = true;

85 this.velocityY = 0;

Page 42: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 39

86 this.velocityX = 0;

87 }

88

89 if (state.keys[38]) {

90 this.rectangle.y -= this.climbingSpeed;

91 }

92

93 if (state.keys[40] && me.y + me.height < you.y + you.height) {

94 this.rectangle.y += this.climbingSpeed;

95 }

96 }

97 }

98 });

99

100 if (!onLadder) {

101 this.climbing = false;

102 }

103

104 if (state.keys[38] && this.grounded) {

105 this.velocityY = this.jumpVelocity;

106 this.jumping = true;

107 this.grounded = false;

108 }

109

110 this.rectangle.x += this.velocityX;

111

112 if (!this.climbing) {

113 this.rectangle.y += this.velocityY;

114 }

115

116 this.sprite.x = this.rectangle.x;

117 this.sprite.y = this.rectangle.y;

118 }

119 }

120

121 export default Player;

..

This is from player.js

We begin by adding a new property, called climbing. This will be true when the player is busyclimbing the ladder. We also create a local onLadder variable, so we can tell if they’re standing stillon a ladder.

Page 43: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 40

During the usual collision detection, we note whether the object the player is colliding with is aladder or not. If so, and they are pressing the up arrow, we start them climbing. this.climbing isonly set if they are pressing the up arrow, which is why we need that local variable.

We reset player velocity, and the properties related to jumping. We also directly alter the playerrectangle. If the up arrow is being pressed, we move the player up. If down is pressed then we onlymove the player down until the player’s bottom is lower than the ladder’s bottom. This preventsfloor-level ladders from allowing the player to ignore the floor collision.

..

You can see a video of this code at https://vimeo.com/137674792.

Allowing players to stand on ladders

There’s still a problem with our ladders. The moment we reach the top, we fall down again. We’vedisabled collisions for ladders, so we need a way of standing at the top of them so we can jump/moveto the next platform. Let’s move the collision logic into boxes and ladders:

1 class Box {

2 collides(state) {

3 var collides = false;

4

5 state.objects.forEach((object) => {

6 if (object.constructor.name !== "Player") {

7 return;

8 }

9

10 let edges = this.getEdges(this, object);

11

12 if (!(

13 edges.boxTop > edges.playerBottom ||

14 edges.boxRight < edges.playerLeft ||

15 edges.boxBottom < edges.playerTop ||

16 edges.boxLeft > edges.playerRight

17 )) {

18 collides = true;

19 return;

20 }

21 });

22

23 return collides;

24 }

Page 44: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 41

25

26 getEdges(box, player) {

27 return {

28 "boxLeft" : box.rectangle.x,

29 "boxRight" : box.rectangle.x + box.rectangle.width,

30 "boxTop" : box.rectangle.y,

31 "boxBottom" : box.rectangle.y + box.rectangle.height,

32 "playerLeft" : player.rectangle.x,

33 "playerRight" : player.rectangle.x + player.rectangle.width,

34 "playerTop" : player.rectangle.y,

35 "playerBottom" : player.rectangle.y + player.rectangle.height

36 };

37 }

38

39 collidesInDirection(box, player) {

40 let edges = this.getEdges(box, player);

41

42 let offsetLeft = edges.playerRight - edges.boxLeft;

43 let offsetRight = edges.boxRight - edges.playerLeft;

44 let offsetTop = edges.playerBottom - edges.boxTop;

45 let offsetBottom = edges.boxBottom - edges.playerTop;

46

47 if (Math.min(offsetLeft, offsetRight, offsetTop, offsetBottom) === offsetTop) {

48 return "down";

49 }

50

51 if (Math.min(offsetLeft, offsetRight, offsetTop, offsetBottom) === offsetBottom) {

52 return "up";

53 }

54

55 if (Math.min(offsetLeft, offsetRight, offsetTop, offsetBottom) === offsetLeft) {

56 return "right";

57 }

58

59 if (Math.min(offsetLeft, offsetRight, offsetTop, offsetBottom) === offsetRight) {

60 return "left";

61 }

62

63 return "unknown";

64 }

65

66 constructor(sprite, rectangle) {

67 this.sprite = sprite;

68 this.rectangle = rectangle;

69 }

Page 45: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 42

70

71 animate(state) {

72 if (this.sprite) {

73 this.sprite.x = this.rectangle.x;

74 this.sprite.y = this.rectangle.y;

75 }

76 }

77 }

78

79 export default Box;

..

This is from box.js

The Box class now calculates whether it is colliding with the player. Once we detect a collision,between a player and box, we can run them through collidesInDirection. We’ll get a neat UTF-8arrow pointing in the direction the player was moving before the collision happened.

While making this change, it occurred to me that the Ladder class isn’t doing anything different tothe box class. The climbing logic happens in the Player class, so the Ladder class may as well extendthe Box class:

1 import Box from "./box";

2

3 class Ladder extends Box {

4

5 }

6

7 export default Ladder;

..

This is from ladder.js

The Player class is looking a lot cleaner, after moving the collision logic out:

Page 46: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 43

1 animate(state) {

2 if (state.keys[37]) {

3 this.velocityX = Math.max(

4 this.velocityX - this.accelerationX,

5 this.maximumVelocityX * -1

6 );

7 }

8

9 if (state.keys[39]) {

10 this.velocityX = Math.min(

11 this.velocityX + this.accelerationX,

12 this.maximumVelocityX

13 );

14 }

15

16 this.velocityX *= this.frictionX;

17

18 this.velocityY = Math.min(

19 this.velocityY + this.accelerationY,

20 this.maximumVelocityY

21 );

22

23 var onLadder = false;

24

25 state.objects.forEach((object) => {

26 if (object === this) {

27 return;

28 }

29

30 if (object.collides(state)) {

31 let type = object.constructor.name;

32 let direction = object.collidesInDirection(object, this);

33

34 if (type === "Ladder") {

35 onLadder = true;

36

37 let player = this.rectangle;

38 let ladder = object.rectangle;

39

40 if (state.keys[38] || state.keys[40]) {

41 this.grounded = false;

42 this.jumping = false;

43 this.climbing = true;

44 this.velocityY = 0;

45 this.velocityX = 0;

Page 47: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 44

46 }

47

48 if (state.keys[38]) {

49 let limit = ladder.y - this.rectangle.height + 1;

50

51 this.rectangle.y = Math.max(

52 this.rectangle.y -= this.climbingSpeed,

53 limit

54 );

55

56 if (player.y === limit) {

57 this.grounded = true;

58 this.jumping = false;

59 }

60 }

61

62 if (state.keys[40] && player.y + player.height < ladder.y + ladder.height)\

63 {

64 this.rectangle.y += this.climbingSpeed;

65 }

66

67 return;

68 }

69

70 if (type === "Box") {

71 if (direction === "down") {

72 this.velocityY = 0;

73 this.grounded = true;

74 this.jumping = false;

75 }

76

77 if (direction === "up") {

78 this.velocityY = this.accelerationY;

79 }

80

81 if (direction === "left" && this.velocityX < 0) {

82 this.velocityX = 0;

83 }

84

85 if (direction === "right" && this.velocityX > 0) {

86 this.velocityX = 0;

87 }

88 }

89 }

90 });

Page 48: Making Games - Leanpubsamples.leanpub.com/making-games-sample.pdf · Introduction I’magamer.I’vebeenagamersincebeforeIwasaprogrammer.AndyetI’venevertriedtobuild agame…untilnow.Afewweeksago,acoworkersketchedasimple

Ladders 45

91

92 if (!onLadder) {

93 this.climbing = false;

94 }

95

96 if (state.keys[38] && this.grounded && !this.climbing) {

97 this.velocityY = this.jumpVelocity;

98 this.jumping = true;

99 this.grounded = false;

100 }

101

102 this.rectangle.x += this.velocityX;

103

104 if (!this.climbing) {

105 this.rectangle.y += this.velocityY;

106 }

107

108 this.sprite.x = this.rectangle.x;

109 this.sprite.y = this.rectangle.y;

110 }

..

This is from player.js

The ladder logic is mostly unchanged, except that the player will never move above the highestladder pixel. That’s how we stop the player from falling once they reach the top. This means you’llneed to make your ladders start one pixel above platforms you expect players to move onto. It alsomeans players can’t jump off of ladders.

The box collision logic is much cleaner and simpler now! And the result? Working ladders.

..

You can see a video of this code at https://vimeo.com/137680213.