
Cloud Invaders is a fast-paced arcade shooter where players control a planet defending itself from waves of toxic pollution clouds. Move left and right to dodge falling pollution bombs while shooting clouds to keep the skies clean. As the game progresses, pollution attacks intensify, making quick reflexes and precision essential. Survive as long as possible and fight for a cleaner planet—one shot at a time!
Phaser.io is a game engine that allows you to create games directly in your web browser (like Chrome or Firefox). This means you don’t even need to download any complex software. All you do is write simple code, and voila, your game works in the browser.
– Simple and accessible: It’s perfect for beginners. Even if you’ve never coded before, you can follow this tutorial and create your own game.
– Compatibility: Phaser works on almost every browser, so your friends can play your game too!
– Easy experimentation: You can easily modify your code and see the changes right away.
Before we start coding, you will need a few simple tools:
– A web browser (Chrome, Firefox, Edge, etc.).
– A text editor to write your code (we recommend Visual Studio Code or Notepad if you prefer something simple).
NOTE : If you don’t want to use Visual Studio Code, you can use Notepad, which is already installed on your computer.
HTML is the language used to create web pages. It’s like the skeleton of a page. With HTML, you tell the browser what to display. It is composed of tags defined by the element name surrounded by chevrons.
The HTML document itself begins with `<html>` and ends with `</html>`. The visible part of the HTML document is between `<body>` and `</body>`.
CSS makes web pages look nice. It’s like choosing the colors and styles for your page.
JavaScript makes web pages interactive. It’s the brain behind everything that moves and does actions. Phaser.io uses JavaScript to create video games.
Phaser is actually a JavaScript library. This means it gives you pre-built tools to create games. You don’t have to code everything from scratch!
“`html
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″ />
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″ />
<title>My First Phaser Game</title>
<script src=”https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js”></script>
</head>
<body>
<script>
// This is where our Phaser game starts
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
scene: {
preload: preload,
create: create,
update: update,
},
};
const game = new Phaser.Game(config);
function preload() {
// Load assets here
}
function create() {
// Initialize the game here
}
function update() {
// Game logic goes here
}
</script>
</body>
</html>
“`
– `<!DOCTYPE html>` tells the browser that this is a web page.
– The line `<script src=”https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js”></script>` loads Phaser.io into your page. This line indicates that we want to load a javascript code that can be found at the address indicated in src.
The config object is like a recipe. It sets the size of your game (800 pixels wide and 600 pixels tall) and defines the game stages (`preload`, `create`, `update`). Several other attributes can be used to define other specific parameters, such as :
– type: Phaser will automatically decide whether to use WebGL or Canvas rendering.
– scale: Ensures the game fits within the browser window.
The preload() function is where you load the images, sounds, or other resources needed for your game. For example, you might load a character or background image here.
The create() function is used to place these images or other elements into your game. This is where you start bringing your game to life.
The update() function runs continuously while the game is active. This is where you write the logic that makes your characters or objects move.
Once you’ve pasted the code into your HTML file, open that file in your web browser. You should see a blank screen (since we haven’t added anything yet), but this is a good start!
If something doesn’t work as expected, don’t worry! Here’s how to find and fix mistakes:
– On most browsers, press `F12` or right-click on the page and select “Inspect”.
– Go to the Console tab. This is where the browser will display error messages when something goes wrong.
Errors are a normal part of coding. The key is to be patient, use the console to spot them, and fix them step by step.
First, we need an HTML file to load Phaser and your game code. Create a file called `index.html` and add the following code:
“`html
<!DOCTYPE html>
<html>
<head>
<script src=”https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser-arcade-physics.min.js”></script>
<script src=”./index.js”></script>
<style>
body, html {
background: black;
margin: 0;
padding: 0;
overflow: hidden;
width: 100%;
height: 100%;
}
canvas {
display: block;
}
</style>
</head>
<body>
</body>
</html>
“`
This HTML file:
– Loads Phaser from a CDN to create the game.
– Loads the JavaScript file where we will write all the game code.
Let’s start by setting up some constants and game variables, and creating the basic game scene. Add this code to `index.js`:
“`javascript
const GAME_WIDTH = 1000;
const GAME_HEIGHT = 600;
const ENNEMY_SPACING_X = 62;
const ENNEMY_SPACING_Y = 62;
const ENNEMY_ROWS = 3;
const ENNEMY_COLS = Math.floor(GAME_WIDTH / ENNEMY_SPACING_X) – 1;
const ENNEMY_SHOOT_INTERVAL = 600;
const SHOOT_DELAY = 800;
“`
– GAME_WIDTH & GAME_HEIGHT: Defines the game area dimensions.
– ENNEMY_SPACING_X & ENNEMY_SPACING_Y: Determines the spacing between enemies in the grid formation.
– ENNEMY_ROWS & ENNEMY_COLS: Specifies the number of rows and columns in the enemy grid.
– ENNEMY_SHOOT_INTERVAL: Controls how frequently enemies shoot (in milliseconds).
– SHOOT_DELAY: Defines the minimum time interval between two consecutive player shots.
Class `MainScene`
The `MainScene` class represents the main game scene where all logic is implemented.
“`javascript
class MainScene extends Phaser.Scene {
constructor() {
[…]
}
preload() {
[…]
}
create() {
[…]
}
update() {
[…]
}
}
“`
`constructor()`
“`javascript
constructor() {
super({ key: ‘MainScene’ });
}
“`
– Calls the Phaser.Scene constructor with a unique key `’MainScene’`.
– Allows this scene to be referenced and managed by Phaser.
`preload()`
“`javascript
preload() {
// Load the assets
this.load.setBaseURL(“./assets/”); // Root assets folder
/ Enemies sprites /
this.load.spritesheet(‘cloud’, ‘sprites/cloud.png’, {
frameWidth: 64,
frameHeight: 64,
});
this.load.image(‘planet’, ‘sprites/planet.png’);
this.load.image(‘ship_bullet’, ‘sprites/ship_bullet.png’); // Player bullet sprite
this.load.image(‘bullet’, ‘sprites/bullet.png’); // Enemy bullet sprite
this.load.image(‘explode’, ‘sprites/explode.png’); // Player explosion sprite
this.load.image(‘boom’, ‘sprites/boom.png’); // Enemy explosion sprite
this.load.audio(‘shoot’, ‘sounds/shoot.wav’); // Shoot sound
this.load.audio(‘invader_killed’, ‘sounds/invader_killed.wav’); // Invader killed sound
this.load.audio(‘explosion’, ‘sounds/explosion.wav’); // Explosion sound
}
“`
– Loads game assets including images, spritesheets, and sounds.
– Spritesheets:
– `’cloud’`: Enemy animation with 64×64 frame size.
– Images:
– `’planet’`: Player-controlled spaceship.
– `’ship_bullet’` & `’bullet’`: Bullets fired by the player and enemies.
– `’explode’` & `’boom’`: Explosion effects for player and enemies.
– Sounds:
– `’shoot’`: Sound played when the player fires a bullet.
– `’invader_killed’`: Sound when an enemy is destroyed.
– `’explosion’`: Sound effect when the player is hit.
`create()`
“`javascript
create() {
this.lastFired = 0; // Last time the player fired
this.gameStop = false;
this.centerX = this.cameras.main.width / 2;
this.centerY = this.cameras.main.height / 2;
this.bottomY = this.cameras.main.height – 50;
// Create the player ship
this.ship = this.physics.add.image(this.centerX, this.bottomY, ‘planet’).setCollideWorldBounds(true);
// Create a group for bullets
this.bullets = this.physics.add.group({
defaultKey: ‘ship_bullet’,
maxSize: 10
});
this.bullets.setOrigin(0.5, 1);
// Create a group for enemies
this.enemies = this.physics.add.group();
// Create a group for enemy bullets
this.enemyBullets = this.physics.add.group({
defaultKey: ‘bullet’,
maxSize: 10
});
this.enemyBullets.setOrigin(0.5, 1);
// Setup animations for the invaders
this.createAnimations();
// Create the enemies in a grid formation
this.createEnemies();
// Setup collision between bullets and enemies
this.physics.add.overlap(this.bullets, this.enemies, this.hitEnemy, null, this);
// Setup collision between enemy bullets and the ship
this.physics.add.overlap(this.enemyBullets, this.ship, this.gameOver, null, this);
// Events
this.input.on(‘pointerdown’, this.shootBullet, this);
// Create the keyboard cursor keys
this.cursors = this.input.keyboard.createCursorKeys();
// Create the space key
this.fireKey = this.input.keyboard.addKey(
Phaser.Input.Keyboard.KeyCodes.SPACE
);
}
“`
– Initializes player and enemy elements:
– The player-controlled planet is added at the bottom center.
– Player and enemy bullet groups are created with size limits.
– Enemies are grouped for easier management.
– Physics & Collisions:
– Collisions are enabled between:
– Player bullets and enemies (`hitEnemy` function).
– Enemy bullets and the player (`gameOver` function).
– Input Handling:
– Clicking the screen (`pointerdown`) or pressing SPACE will trigger `shootBullet()`.
– Arrow keys are stored in `this.cursors` for player movement.
`update()`
“`javascript
update() {
if (this.gameStop) return;
// Update the player’s movement based on the keyboard input
if (this.cursors.left.isDown) {
this.ship.setVelocityX(-300);
} else if (this.cursors.right.isDown) {
this.ship.setVelocityX(300);
} else {
this.ship.setVelocityX(0);
}
// Shoot a bullet when the space key is pressed
if (this.fireKey.isDown) {
this.shootBullet();
}
// Remove bullets that are out of bounds
this.bullets.children.each(function (bullet) {
if (bullet.active) {
if (bullet.y < 0) {
bullet.setActive(false);
bullet.setVisible(false);
}
}
}, this);
// Remove enemy bullets that are out of bounds
this.enemyBullets.children.each(function (bullet) {
if (bullet.active) {
if (bullet.y > this.cameras.main.height) {
bullet.setActive(false);
bullet.setVisible(false);
}
}
}, this);
}
“`
– Handles Player Movement:
– Moves left or right when arrow keys are pressed.
– Stops moving when no key is pressed.
– Handles Shooting:
– Calls `shootBullet()` if the SPACE key is held down.
– Bullet Cleanup:
– Removes bullets that go off-screen to avoid unnecessary processing.
Now that we have our game basics, let’s create animations for the enemies. Add the following function to your `MainScene` class:
“`javascript
// Create animations for the invaders
createAnimations() {
this.anims.create({
key: ‘cloud_anim’,
frames: this.anims.generateFrameNumbers(‘cloud’, { start: 0, end: 1 }),
frameRate: 2,
repeat: -1 // loop
});
}
“`
Explanation:
The `createAnimations()` method defines an animation for the enemy entity (`cloud`). In Phaser, animations are managed using the Animation Manager (`this.anims.create()`), which allows us to create and assign sequences of frames to sprites. In this function, an animation is created with the key `’cloud_anim’`, which will be used later to apply the animation to an enemy sprite. The frames for this animation are extracted from the `cloud` sprite sheet using `this.anims.generateFrameNumbers(‘cloud’, { start: 0, end: 1 })`, which selects two frames (`0` and `1`). The animation plays at a frame rate of 2 FPS, meaning it switches frames twice per second, creating a slow flickering or movement effect. Additionally, the parameter `repeat: -1` ensures that the animation loops infinitely.
This animation is later applied to enemy sprites when they are created using `enemy.play(‘cloud_anim’, true);`, making the enemy appear more dynamic.
Next, we’ll create a grid of enemies, similar to the classic Space Invaders rows. Add the following function to your `MainScene` class:
“`javascript
createEnemies() {
const OFFSET_CENTER_X = (GAME_WIDTH – ((ENNEMY_COLS-1) ENNEMY_SPACING_X)) / 2;
const ENNEMY_OFFSET_Y = ENNEMY_SPACING_Y;
for (let row = 0; row < ENNEMY_ROWS; row++) {
for (let col = 0; col < ENNEMY_COLS; col++) {
const x = OFFSET_CENTER_X + col ENNEMY_SPACING_X;
const y = ENNEMY_OFFSET_Y + row ENNEMY_SPACING_Y;
// Create cloud
const enemy = this.enemies.create(x, y, ‘cloud’);
enemy.setOrigin(0.5, 0.5);
enemy.setCollideWorldBounds(true);
// Play animation
enemy.play(‘cloud_anim’, true);
// To mark the row
enemy.setData(‘row’, row);
}
}
}
“`
Explanation:
The `createEnemies()` method is responsible for generating enemy entities in a structured grid formation. It first calculates the horizontal offset (`OFFSET_CENTER_X`) to ensure that the enemies are centered on the screen. This is done by subtracting the total width occupied by the enemies from the game’s width and dividing the remainder by two. The vertical starting position of the enemies is set using `ENNEMY_OFFSET_Y`, which corresponds to the defined spacing between rows.
A nested loop is then used to iterate through the number of rows (`ENNEMY_ROWS`) and columns (`ENNEMY_COLS`) to place each enemy at the correct position. The x-coordinate of each enemy is determined by adding the horizontal offset to the column index multiplied by the enemy spacing, ensuring uniform distribution. Similarly, the y-coordinate is calculated using the row index and vertical spacing.
For each position in the grid, a new enemy sprite (`cloud`) is created using `this.enemies.create(x, y, ‘cloud’)`. The sprite’s origin is set to its center (`0.5, 0.5`), ensuring it aligns correctly in the grid. The enemy is also constrained within the world bounds using `setCollideWorldBounds(true)`, preventing it from moving outside the game area.
Once the enemy is created, it immediately starts playing the predefined animation `”cloud_anim”` (which we previously created), giving it a visual effect of movement or flickering. Finally, the method assigns the row index to the enemy using `enemy.setData(“row”, row)`, allowing us to identify its position within the formation. This will be useful later to retrieve the first row of clouds, which will be responsible for shooting bullets.
Now, let’s add shooting mechanics so the player can fire bullets. Update your `MainScene` class with the following:
“`javascript
shootBullet() {
if (this.gameStop) return;
const now = this.time.now;
// Limit the player’s shooting rate
if (now – this.lastFired < SHOOT_DELAY) {
return;
}
this.lastFired = now; // Record the last fired time
// Create a bullet
const bullet = this.bullets.get(this.ship.x, this.ship.y – 20);
if (bullet) {
bullet.setActive(true);
bullet.setVisible(true);
bullet.setVelocityY(-500); // Bullet moves upward
this.sound.play(‘shoot’);
}
}
“`
Explanation:
– shootBullet(): This function creates a bullet when the spacebar is pressed, limiting the fire rate to prevent rapid shooting.
Now, let’s allow enemies to shoot back and handle bullet collisions. Add these functions to your `MainScene` class:
“`javascript
enemyShoot() {
// Only top row enemies can shoot
const topRowEnemies = this.enemies.getChildren().filter(enemy => enemy.getData(‘row’) === 0);
if (topRowEnemies.length === 0) {
return;
}
// Get a random enemy
const randomEnemy = Phaser.Utils.Array.GetRandom(topRowEnemies)
// Shoot a bullet from the enemy
const bullet = this.enemyBullets.get(randomEnemy.x, randomEnemy.y + 20);
if (bullet) {
bullet.setActive(true);
bullet.setVisible(true);
bullet.setVelocityY(500);
}
}
hitEnemy(bullet, enemy) {
// Disable the bullet
bullet.setActive(false);
bullet.setVisible(false);
// Remove the bullet
bullet.destroy();
enemy.destroy(); // Remove the enemy
this.sound.play(‘invader_killed’); // Play the invader killed sound
// Play the explosion animation by creating an explosion sprite at the enemy’s position
const explosion = this.add.sprite(enemy.x, enemy.y, ‘boom’);
// Destroy the explosion sprite after 500ms
this.time.delayedCall(500, () => {
explosion.destroy();
}, [], this);
// Check if there are any enemies left
if (this.enemies.countActive() === 0) {
// Win the game if there are no enemies left
this.win();
}
}
“`
Explanation:
The `enemyShoot()` method is responsible for making the enemies shoot bullets at the player. It first filters out the enemies to ensure that only those in the first row (row 0) can shoot. This is done using `this.enemies.getChildren().filter(enemy => enemy.getData(‘row’) === 0)`, which retrieves only the enemies that have their `”row”` data set to `0`. If there are no enemies left in the top row, the function exits early (`return;`), preventing any further shooting.
If there are valid enemies in the top row, the method randomly selects one using `Phaser.Utils.Array.GetRandom(topRowEnemies)`. This prevents bullets from always coming from the same position, making the game more unpredictable.
Once an enemy is selected, the function retrieves an inactive bullet from the enemy bullet group (`this.enemyBullets.get(randomEnemy.x, randomEnemy.y + 20)`). The bullet is then activated (`setActive(true)`) and made visible (`setVisible(true)`). Finally, the bullet is given a downward velocity (`setVelocityY(500)`) to make it move toward the player.
The `hitEnemy()` method handles collisions between player bullets and enemies. When a bullet hits an enemy, the bullet is deactivated and destroyed (`bullet.setActive(false); bullet.destroy();`) to ensure it does not continue moving or interacting. The enemy is then also destroyed (`enemy.destroy();`), and a sound effect (`this.sound.play(‘invader_killed’);`) is played to indicate that the enemy has been eliminated.
To enhance the visual feedback, an explosion animation is created at the enemy’s position using `this.add.sprite(enemy.x, enemy.y, ‘boom’);`. This explosion is removed after 500 milliseconds using `this.time.delayedCall(500, () => { explosion.destroy(); });`, preventing unnecessary elements from remaining in the game scene.
Finally, the method checks if there are any enemies left in the game. This is done using `this.enemies.countActive() === 0`. If no active enemies remain, the player wins the game, and the `win()` method is triggered.
But, to make the enemies shoot bullets at regular intervals, we need to add the following code inside the `create()` method:
“`javascript
create() {
// … previous code
// Enemy shoot
this.enemyShootEvent = this.time.addEvent({
delay: ENNEMY_SHOOT_INTERVAL,
callback: this.enemyShoot,
callbackScope: this,
loop: true
});
}
“`
– This code creates a repeating event (`time.addEvent`) that will call `enemyShoot()` every `ENNEMY_SHOOT_INTERVAL` milliseconds.
– The `callbackScope: this` ensures that the method `enemyShoot()` is executed in the context of the current scene, allowing it to properly access game elements.
– The `loop: true` parameter ensures that the enemies keep shooting continuously, rather than only firing once.
Without this addition, the `enemyShoot()` method would never be called, and enemies would not fire bullets, making the game significantly easier ! By implementing this event, we introduce a dynamic challenge, forcing the player to dodge incoming fire while trying to eliminate enemies.
Finally, let’s define what happens when the player wins or loses. Add these functions to your `MainScene` class:
“`javascript
cleanUpGame() {
this.bullets.clear(true, true);
this.enemyBullets.clear(true, true);
this.enemies.clear(true, true);
this.ship.setVelocity(0);
}
gameEnd() {
this.cleanUpGame();
this.gameStop = true;
this.time.removeEvent(this.enemyShootEvent); // Stop the enemy shoot event
// Restart the game when any key is pressed
this.input.keyboard.once(‘keydown’, this.restartGame, this);
this.add.text(this.centerX, this.centerY + 30, ‘Press any key to restart’)
.setFontSize(32)
.setFontFamily(‘Arial’)
.setOrigin(0.5, 0.5)
}
win() {
this.gameEnd();
// Win title
this.add.text(this.centerX, this.centerY – 30, ‘You Win’)
.setFontSize(64)
.setFontStyle(‘bold’)
.setFontFamily(‘Arial’)
.setColor(’00ff00′)
.setOrigin(0.5, 0.5)
}
gameOver() {
this.gameEnd();
// Player explosion
this.sound.play(‘explosion’);
this.ship.setTexture(‘explode’);
// Game over title
this.add.text(this.centerX, this.centerY – 30, ‘Game Over’)
.setFontSize(64)
.setFontStyle(‘bold’)
.setFontFamily(‘Arial’)
.setColor(‘ff0000’)
.setOrigin(0.5, 0.5);
}
restartGame() {
this.scene.restart();
}
“`
Explanation:
– cleanUpGame(): Cleans up the game when it’s over.
– gameEnd(): Ends the game, waiting for the player to press a key to restart.
– gameOver(): Displays a “Game Over” message if the player loses.
– win(): Displays a “You Win” message if the player destroys all enemies.
“`javascript
const GAME_WIDTH = 1000;
const GAME_HEIGHT = 600;
const ENNEMY_SPACING_X = 62;
const ENNEMY_SPACING_Y = 62;
const ENNEMY_ROWS = 3;
const ENNEMY_COLS = Math.floor(GAME_WIDTH / ENNEMY_SPACING_X) -1;
const ENNEMY_SHOOT_INTERVAL = 600;
const SHOOT_DELAY = 800;
class MainScene extends Phaser.Scene {
constructor() {
super({ key: ‘MainScene’ });
}
preload() {
// Load the assets
this.load.setBaseURL(“./assets/”); // Root assets folder
/ Ennemies sprites /
this.load.spritesheet(‘cloud’, ‘sprites/cloud.png’, {
frameWidth: 64,
frameHeight: 64,
});
this.load.image(‘planet’, ‘sprites/planet.png’);
this.load.image(‘ship_bullet’, ‘sprites/ship_bullet.png’); // Player bullet sprite
this.load.image(‘bullet’, ‘sprites/bullet.png’); // Ennemy bullet sprite
this.load.image(‘explode’, ‘sprites/explode.png’); // Player explosion sprite
this.load.image(‘boom’, ‘sprites/boom.png’); // Ennemy explosion sprite
this.load.audio(‘shoot’, ‘sounds/shoot.wav’); // Shoot sound
this.load.audio(‘invader_killed’, ‘sounds/invader_killed.wav’); // Invader killed sound
this.load.audio(‘explosion’, ‘sounds/explosion.wav’); // Explosion sound
}
create() {
this.lastFired = 0; // Last time the player fired
this.gameStop = false;
this.centerX = this.cameras.main.width / 2;
this.centerY = this.cameras.main.height / 2;
this.bottomY = this.cameras.main.height – 50;
// Create the player ship
this.ship = this.physics.add.image(this.centerX, this.bottomY, ‘planet’).setCollideWorldBounds(true);
// Create a group for bullets
this.bullets = this.physics.add.group({
defaultKey: ‘ship_bullet’,
maxSize: 10
});
this.bullets.setOrigin(0.5, 1);
// Create a group for enemies
this.enemies = this.physics.add.group()
// Create a group for enemy bullets
this.enemyBullets = this.physics.add.group({
defaultKey: ‘bullet’,
maxSize: 10
});
this.enemyBullets.setOrigin(0.5, 1);
// Setup animations for the invaders
this.createAnimations();
// Create the enemies in a grid formation
this.createEnemies();
// Setup collision between bullets and enemies
this.physics.add.overlap(this.bullets, this.enemies, this.hitEnemy, null, this);
// Setup collision between enemy bullets and the ship
this.physics.add.overlap(this.enemyBullets, this.ship, this.gameOver, null, this);
// Enemy shoot
this.enemyShootEvent = this.time.addEvent({
delay: ENNEMY_SHOOT_INTERVAL,
callback: this.enemyShoot,
callbackScope: this,
loop: true
});
// Events
this.input.on(‘pointerdown’, this.shootBullet, this);
// Create the keyboard cursor keys
this.cursors = this.input.keyboard.createCursorKeys();
// Create the space key
this.fireKey = this.input.keyboard.addKey(
Phaser.Input.Keyboard.KeyCodes.SPACE
);
}
// Create animations for the clouds invader
createAnimations() {
this.anims.create({
key: ‘cloud_anim’,
frames: this.anims.generateFrameNumbers(‘cloud’, { start: 0, end: 1 }),
frameRate: 2,
repeat: -1 // loop
});
}
// Create the enemies in a grid formation
createEnemies() {
const OFFSET_CENTER_X = (GAME_WIDTH – ((ENNEMY_COLS-1) ENNEMY_SPACING_X)) / 2;
const ENNEMY_OFFSET_Y = ENNEMY_SPACING_Y;
for (let row = 0; row < ENNEMY_ROWS; row++) {
for (let col = 0; col < ENNEMY_COLS; col++) {
const x = OFFSET_CENTER_X + col ENNEMY_SPACING_X;
const y = ENNEMY_OFFSET_Y + row ENNEMY_SPACING_Y;
// Create cloud
const enemy = this.enemies.create(x, y, ‘cloud’);
enemy.setOrigin(0.5, 0.5);
enemy.setCollideWorldBounds(true);
// Play animation
enemy.play(‘cloud_anim’, true);
// To mark the row
enemy.setData(‘row’, row);
}
}
}
update() {
if (this.gameStop) return;
// Update the player’s movement based on the keyboard input
if (this.cursors.left.isDown) {
this.ship.setVelocityX(-300);
} else if (this.cursors.right.isDown) {
this.ship.setVelocityX(300);
} else {
this.ship.setVelocityX(0);
}
// Shoot a bullet when the space key is pressed
if (this.fireKey.isDown) {
this.shootBullet();
}
// Remove bullets that are out of bounds
this.bullets.children.each(function (bullet) {
if (bullet.active) {
if (bullet.y < 0) {
bullet.setActive(false);
bullet.setVisible(false);
}
}
}, this);
// Remove enemy bullets that are out of bounds
this.enemyBullets.children.each(function (bullet) {
if (bullet.active) {
if (bullet.y > this.cameras.main.height) {
bullet.setActive(false);
bullet.setVisible(false);
}
}
}, this);
}
enemyShoot() {
// Only top row enemies can shoot
const topRowEnemies = this.enemies.getChildren().filter(enemy => enemy.getData(‘row’) === 0);
if (topRowEnemies.length === 0) {
return;
}
// Get a random enemy
const randomEnemy = Phaser.Utils.Array.GetRandom(topRowEnemies)
// Shoot a bullet from the enemy
const bullet = this.enemyBullets.get(randomEnemy.x, randomEnemy.y + 20);
if (bullet) {
bullet.setActive(true);
bullet.setVisible(true);
bullet.setVelocityY(500);
}
}
shootBullet() {
if (this.gameStop) return;
const now = this.time.now;
// Limit the player’s shooting rate
if (now – this.lastFired < SHOOT_DELAY) {
return;
}
this.lastFired = now; // Update the last fired time
// Shoot a bullet
const bullet = this.bullets.get(this.ship.x, this.ship.y – 20);
if (bullet) {
bullet.setActive(true);
bullet.setVisible(true);
bullet.setVelocityY(-500);
this.sound.play(‘shoot’);
}
}
hitEnemy(bullet, enemy) {
// Disable the bullet
bullet.setActive(false);
bullet.setVisible(false);
// Remove the bullet
bullet.destroy();
enemy.destroy(); // Remove the enemy
this.sound.play(‘invader_killed’); // Play the invader killed sound
// Play the explosion animation by creating an explosion sprite at the enemy’s position
const explosion = this.add.sprite(enemy.x, enemy.y, ‘boom’);
// Destroy the explosion sprite after 500ms
this.time.delayedCall(500, () => {
explosion.destroy();
}, [], this);
// Check if there are any enemies left
if (this.enemies.countActive() === 0) {
// Win the game if there are no enemies left
this.win();
}
}
// Clean up the game
cleanUpGame() {
this.bullets.clear(true, true);
this.enemyBullets.clear(true, true);
this.enemies.clear(true, true);
this.ship.setVelocity(0);
}
gameEnd() {
this.cleanUpGame();
this.gameStop = true;
this.time.removeEvent(this.enemyShootEvent); // Stop the enemy shoot event
// Restart the game when any key is pressed
this.input.keyboard.once(‘keydown’, this.restartGame, this);
this.add.text(this.centerX, this.centerY + 30, ‘Press any key to restart’)
.setFontSize(32)
.setFontFamily(‘Arial’)
.setOrigin(0.5, 0.5)
}
win() {
this.gameEnd();
// Win title
this.add.text(this.centerX, this.centerY – 30, ‘You Win’)
.setFontSize(64)
.setFontStyle(‘bold’)
.setFontFamily(‘Arial’)
.setColor(’00ff00′)
.setOrigin(0.5, 0.5)
}
gameOver() {
this.gameEnd();
// Player explosion
this.sound.play(‘explosion’);
this.ship.setTexture(‘explode’);
// Game over title
this.add.text(this.centerX, this.centerY – 30, ‘Game Over’)
.setFontSize(64)
.setFontStyle(‘bold’)
.setFontFamily(‘Arial’)
.setColor(‘ff0000’)
.setOrigin(0.5, 0.5);
}
// Restart the game
restartGame() {
this.scene.restart();
}
}
const config = {
type: Phaser.AUTO,
width: GAME_WIDTH,
height: GAME_HEIGHT,
scene: MainScene,
physics: {
default: ‘arcade’,
arcade: {
gravity: { y: 0 },
debug: false
}
},
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
},
pixelArt: true,
};
const game = new Phaser.Game(config);
“`
Here are a few ideas for expanding your game:
– Add power-ups: Let the player collect items to enhance their ship.
– Increase difficulty: Speed up enemy bullets or add more enemies in later waves.
– Create new levels: Design new challenges for the player after they win.