I already blogged about Block it and I want to talk one more time about this game for two reasons:
1 – I need to port it into TypeScript for a future project.
2 – It’s a quick physics game which has been built with Arcade physics, but I am already rewriting it with no physics engines at all, for a better portability.
Anyway, the game is quite simple:
Tap or click to start and to activate the upper and lower walls at the right time, to make the ball bounce. If the ball flies off the screen, it’s game over.
Ball bounces randomly each time it touches the upper or lower walls.
And here is the source code, mostly uncommented but quite straightforward.
Anyway, the interesting stuff will come when I’ll drop the physics engine.
The game is split into one html file, one css file and six TypeScript files:
index.html
The web page which hosts the game, to be run inside thegame element.
<!DOCTYPE html> <html> <head> <meta name="viewport" content="initial-scale=1, maximum-scale=1"> <link rel="stylesheet" href="style.css"> </style> <script src="main.js"></script> </head> <body> <div id="thegame"></div> </body> </html>
style.css
The cascading style sheets of the main web page.
* { padding : 0; margin : 0; } canvas { touch-action : none; -ms-touch-action : none; }
gameOptions.ts
Configurable game options. It’s a good practice to place all configurable game options, if possible, in a single and separate file, for a quick tuning of the game.
// CONFIGURABLE GAME OPTIONS export const GameOptions = { // duration of the wall, in milliseconds wallDuration : 100, // ball start speed, in pixels/second ballStartSpeed : 500, // ball speed increase at each successful bounce, in pixels/second ballSpeedIncrease : 20, // random bounce angle range angleRange : 50, // angle needed for the ball to go up, in degrees upDirection : 90, // angle needed for the ball to go down, in degrees downDirection : 270, // ball radius, in pixels ballRadius : 25, // wall thickness, in pixels wallSize : 32, // wall padding from game canvas, in pixels wallPadding : 16 }
main.ts
This is where the game is created, with all Phaser related options.
// MAIN GAME FILE // modules to import import Phaser from 'phaser'; import { PreloadAssets } from './preloadAssets'; import { PlayGame } from './playGame'; // object to initialize the Scale Manager const scaleObject : Phaser.Types.Core.ScaleConfig = { mode : Phaser.Scale.FIT, autoCenter : Phaser.Scale.CENTER_BOTH, parent : 'thegame', width : 480, height : 640 } // game configuration object const configObject : Phaser.Types.Core.GameConfig = { type : Phaser.AUTO, backgroundColor : 0xfe5430, scale : scaleObject, physics : { default : 'arcade' }, scene : [PreloadAssets, PlayGame] } // the game itself new Phaser.Game(configObject);
preloadAssets.ts
Here we preload all assets to be used in the game, such as the sprites used for the ball and the walls.
// CLASS TO PRELOAD ASSETS // this class extends Scene class export class PreloadAssets extends Phaser.Scene { // constructor constructor() { super({ key : 'PreloadAssets' }); } // method to be execute during class preloading preload(): void { // this is how we preload an image this.load.image('ball', 'assets/ball.png'); this.load.image('wall', 'assets/wall.png'); } // method to be called once the instance has been created create(): void { // call PlayGame class this.scene.start('PlayGame'); } }
playGame.ts
Main game file, all game logic is stored here.
// THE GAME ITSELF import { GameOptions } from './gameOptions'; import WallSprite from './wallSprite'; import BallSprite from './ballSprite'; // this class extends Scene class export class PlayGame extends Phaser.Scene { gameOver : boolean; canActivateWall : boolean; ballSpeed : number; wallGroup : Phaser.Physics.Arcade.Group; theBall : BallSprite; lowerWall : WallSprite; upperWall : WallSprite; gameWidth : number; gameHeight : number; gameStarted : boolean; score : number; scoreText : Phaser.GameObjects.Text; // constructor constructor() { super({ key: 'PlayGame' }); } // method to be executed when the scene has been created create() : void { this.score = 0; this.gameWidth = this.game.config.width as number; this.gameHeight = this.game.config.height as number; this.scoreText = this.add.text(this.gameWidth / 2, this.gameHeight / 2, '0', { fontFamily: 'Arial', fontStyle : 'bold', fontSize: '256px', color: '#ffffff' }); this.scoreText.setOrigin(0.5); this.scoreText.setAlpha(0.3); this.gameStarted = false; this.gameOver = false; this.canActivateWall = true; this.ballSpeed = GameOptions.ballStartSpeed; this.wallGroup = this.physics.add.group(); this.theBall = new BallSprite(this, this.gameWidth / 2, this.gameHeight / 2, GameOptions.ballRadius); let wallWidth : number = this.gameWidth - GameOptions.wallPadding * 2 - GameOptions.wallSize * 2; let wallHeight : number = this.gameHeight - GameOptions.wallPadding * 2; let totalSpacing : number = GameOptions.wallPadding + GameOptions.wallSize; new WallSprite(this, GameOptions.wallPadding, GameOptions.wallPadding, GameOptions.wallSize, wallHeight, this.wallGroup, false); new WallSprite(this, this.gameWidth - totalSpacing, GameOptions.wallPadding, GameOptions.wallSize, wallHeight, this.wallGroup, false); this.upperWall = new WallSprite(this, totalSpacing, GameOptions.wallPadding, wallWidth, GameOptions.wallSize, this.wallGroup, true); this.lowerWall = new WallSprite(this, totalSpacing, this.gameHeight - totalSpacing, wallWidth, GameOptions.wallSize, this.wallGroup, true); this.physics.add.collider(this.theBall, this.wallGroup, this.ballVsWall, undefined, this); this.input.on('pointerdown', this.activateWall, this); } // method to check collision between ball and walls ballVsWall(theBall : Phaser.Types.Physics.Arcade.GameObjectWithBody, theWall : Phaser.Types.Physics.Arcade.GameObjectWithBody) : void { let wall : WallSprite = theWall as WallSprite; this.canActivateWall = true; if (wall.activable) { let direction : number = theBall.body.y < wall.body.y ? GameOptions.downDirection : GameOptions.upDirection; this.score ++; this.scoreText.setText(this.score.toString()); this.randomBounce(direction); } } // method to activate the walls activateWall() : void { if (!this.gameStarted) { let direction : number = Phaser.Math.Between(0, 1) == 0 ? GameOptions.upDirection : GameOptions.downDirection; this.randomBounce(direction); this.upperWall.turnOff(); this.lowerWall.turnOff(); this.gameStarted = true; return; } if (this.canActivateWall) { this.canActivateWall = false; this.upperWall.turnOn(); this.lowerWall.turnOn(); this.time.addEvent({ delay: GameOptions.wallDuration, callbackScope: this, callback: () => { this.upperWall.turnOff(); this.lowerWall.turnOff(); } }); } } // method to give the ball a random bounce randomBounce(direction : number) : void { this.ballSpeed += GameOptions.ballSpeedIncrease; let ballVelocity : Phaser.Math.Vector2 = this.physics.velocityFromAngle(Phaser.Math.Between(direction - GameOptions.angleRange, direction + GameOptions.angleRange), this.ballSpeed); this.theBall.setVelocity(ballVelocity.x, ballVelocity.y); } // method to be called at each frame update() : void{ if ((this.theBall.y > this.gameHeight || this.theBall.y < 0) && !this.gameOver) { this.gameOver = true; this.cameras.main.shake(800, 0.05); this.time.addEvent({ delay: 800, callbackScope: this, callback: () => { this.scene.start('PlayGame'); } }); } } }
wallSprite.ts
Custom class for the walls, extending Phaser.Physics.Arcade.Sprite.
export default class WallSprite extends Phaser.Physics.Arcade.Sprite { activable : boolean; constructor(scene : Phaser.Scene, posX : number, posY : number, width : number, height : number, group : Phaser.Physics.Arcade.Group, isActivable : boolean) { super(scene, posX, posY, 'wall'); this.setDisplaySize(width, height); this.setOrigin(0); scene.add.existing(this); scene.physics.add.existing(this); group.add(this); this.setImmovable(true); this.activable = isActivable } turnOff() : void { this.setAlpha(0.1); this.body.checkCollision.none = true; } turnOn() : void { this.setAlpha(1); this.body.checkCollision.none = false; } }
ballSprite.ts
Custom class for the ball, extending Phaser.Physics.Arcade.Sprite.
export default class ballSprite extends Phaser.Physics.Arcade.Sprite { constructor(scene : Phaser.Scene, posX : number, posY : number, radius : number) { super(scene, posX, posY, 'ball'); scene.add.existing(this); scene.physics.add.existing(this); this.body.setCircle(64); let ballSize : number = radius * 2 this.setDisplaySize(ballSize, ballSize); this.setBounce(1); } }
And that’s it. Wonder how to build a game like this with no physics engine, maybe featuring continuous collision detection? Stay tuned, and meanwhile download the source code.