I’ve been doing a long running Phaser with TypeScript tutorial series that illustrates basically everything you need to know to create a complete game using the Phaser HTML5 game engine. I decided to put together a video tutorial showing how to put it all together to make a complete game.
Now, keep in mind, the game is incredibly simple… I wanted to keep the entire thing to under an hour after all ( I failed, it’s an hour and a half ), so I had to keep the scope reasonable in size. So as a result I’ve created Extreme Walking Simulator! Yeah, you walk… and that’s it actually. You walk forever and ever in front of the same looping background. However, you are left with a complete but simple game but most importantly, a framework of code that can be expanded upon to build much more complex games. It also illustrates program flow, drawing sprites, playing music, changing states, handling input and all the other things a “full” game has to do.
In addition to the code, all of the assets used are available on GitHub. This includes my incredible title music, the Blend files used for the main character and the level scene. Everything you need to create this game is there in the Assets folder. Feel free to play around and use it however you want. That said, the textures just came from Google Image Search, so no clue if there is a copyright on any of them, so don’t use them in a commercial project! I believe all of my Github stuff is set to Creative Commons license… if it’s not, assume you can do anything you want with it and that you have absolutely no warranty.
Ok, enough preamble, let’s jump right in! The tutorial is available in two parts on YouTube in 1080p ( or embedded below):
Following are the game itself, then the source listings, finally the two videos in an embedded player.
EXTREME WALKING SIMULATOR!
Controls:
- Click the title screen to get started
- Tap right arrow to start walking/speed up
- Tap left arrow to slow down/stop
- Press ESC to stop the torture!
The Source Code
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Hello Phaser</title> <link rel="stylesheet" href="app.css" type="text/css" /> <script src="phaser.js"></script> <script src="GameObjects/MyScene.js"></script> <script src="GameObjects/Player.js"></script> <script src="States/TitleScreenState.js"></script> <script src="States/GamePlayState.js"></script> <script src="States/GameOverState.js"></script> <script src="app.js"></script> </head> <body> <div id="content"></div> </body> </html>
app.ts
module Game { export class ExtremeWalkingSimulator { game: Phaser.Game; constructor() { this.game = new Phaser.Game(1280, 720, Phaser.AUTO, 'content', { create: this.create, preload: this.preload }); } preload() { // Graphics this.game.load.image("title", "Graphics/TitleScreen.png"); this.game.load.image("scene", "Graphics/scene720p.png"); this.game.load.image("gameover", "Graphics/GameOver.png"); //Spritesheets this.game.load.atlasXML("HERO_WALKING", "Graphics/Hero_Walking.png", "Graphics/Hero_Walking.xml"); this.game.load.atlasXML("HERO_IDLE", "Graphics/Hero_Idle.png", "Graphics/Hero_Idle.xml"); // Audio this.game.load.audio("TitleSong", ["Sounds/TitleSong.mp3", "Sounds/TitleSong.ogg",
"Sounds/TitleSong.wav"]); } create() { this.game.state.add("TitleScreenState", GameFromScratch.TitleScreenState, true); this.game.state.add("GamePlayState", GameFromScratch.GamePlayState, false); this.game.state.add("GameOverState", GameFromScratch.GameOverState, false); this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; } } } window.onload = () => { var game = new Game.ExtremeWalkingSimulator(); };
Player.ts
module GameFromScratch { export enum PlayerState { IDLE, WALKING } export class Player extends Phaser.Sprite { game: Phaser.Game; playerState: PlayerState; RIGHT_ARROW: Phaser.Key; LEFT_ARROW: Phaser.Key; ESCAPE: Phaser.Key; walkingSpeed: number; public static MAX_SPEED: number = 30; constructor(game: Phaser.Game, x: number, y: number) { this.game = game; this.walkingSpeed = 0; //Wire up input handlers this.RIGHT_ARROW = this.game.input.keyboard.addKey(Phaser.Keyboard.RIGHT); this.RIGHT_ARROW.onDown.add(Player.prototype.MoveRight, this); this.LEFT_ARROW = this.game.input.keyboard.addKey(Phaser.Keyboard.LEFT); this.LEFT_ARROW.onDown.add(Player.prototype.MoveLessRight, this); this.ESCAPE = this.game.input.keyboard.addKey(Phaser.Keyboard.ESC); this.ESCAPE.onDown.add(Player.prototype.GameOver, this); super(game, x, y, "HERO_WALKING", 0); this.anchor.set(0.0, 1.0); this.StartIdle(); } update() { if (this.playerState == PlayerState.WALKING) { this.x += (this.walkingSpeed / Player.MAX_SPEED) * (60 / this.game.time.elapsedMS); // This logic depends on scene being added first. var stageWidth = this.game.stage.getChildAt(0).getBounds().width; if (this.x > stageWidth * .75) this.x = stageWidth * .25; } super.update(); } // Worse function name ever! MoveLessRight() { if (this.playerState != PlayerState.IDLE) { this.walkingSpeed--; if (this.walkingSpeed > 0) this.animations.currentAnim.speed = this.walkingSpeed; else this.StartIdle(); } } MoveRight() { if (this.playerState == PlayerState.IDLE) { this.StartWalking(); } else { if (this.walkingSpeed < Player.MAX_SPEED) this.walkingSpeed++; this.animations.currentAnim.speed = this.walkingSpeed; } } StartWalking() { this.playerState = PlayerState.WALKING; this.walkingSpeed = 5; this.loadTexture("HERO_WALKING", 0); this.animations.add("walk"); this.animations.play("walk", this.walkingSpeed, true); } StartIdle() { this.loadTexture("HERO_IDLE", 0); this.playerState = PlayerState.IDLE; this.animations.add("Idle"); this.animations.play("Idle",15,true); } GameOver() { this.game.state.start("GameOverState"); } } }
MyScene.ts
module GameFromScratch { export class MyScene extends Phaser.Sprite { game: Phaser.Game; nextFrame: Phaser.Sprite; constructor(game: Phaser.Game, x: number, y: number) { super(game, x, y, "scene", 0); this.nextFrame = new Phaser.Sprite(this.game, this.width, 0, "scene", 0); this.game.add.existing(this.nextFrame); } } }
TitleScreenState.ts
module GameFromScratch { export class TitleScreenState extends Phaser.State { game: Phaser.Game; music: Phaser.Sound; constructor() { super(); } titleScreenImage: Phaser.Sprite; preload() { } create() { this.titleScreenImage = this.add.sprite(0, 0, "title"); this.titleScreenImage.scale.setTo(this.game.width/this.titleScreenImage.width, this.game.height
/this.titleScreenImage.height); this.music = this.game.add.audio("TitleSong"); this.music.volume = 100; this.music.loop = true; this.music.play(); this.input.onTap.addOnce(this.titleClicked, this); } titleClicked() { this.music.stop(); this.game.state.start("GamePlayState"); } } }
GamePlayState.ts
module GameFromScratch { export class GamePlayState extends Phaser.State { game: Phaser.Game; player: GameFromScratch.Player; myScene: GameFromScratch.MyScene; constructor() { super(); } preload() { } create() { this.myScene = new MyScene(this.game, 0, 0); this.player = new Player(this.game, 0, this.game.height - 50); this.game.add.existing(this.myScene); this.game.add.existing(this.player); this.game.world.setBounds(0,0,this.myScene.width * 2, this.myScene.height); this.game.camera.follow(this.player); } } }
GameOverState.ts
module GameFromScratch { export class GameOverState extends Phaser.State { game: Phaser.Game; gameOverSprite: Phaser.Sprite; constructor() { super(); } preload() { } create() { this.gameOverSprite = this.add.sprite(0, 0, "gameover", 0); this.gameOverSprite.scale.setTo(this.game.width / this.gameOverSprite.width, this.game.height /
this.gameOverSprite.height); this.input.onDown.add(() => { this.game.state.start("TitleScreenState", true); }, this);; } } }
The Videos
Part One:
Part Two: