This is the continuation of the part 1 of this article series about "Making a Simple Space Shooter using p5.js and ES6".
What we have accomplished so far is to create our GameManager class and make our Player object move to the canvas.
To continue let's make our player fire bullets.
1. Firing Bullets
Let's add a fireBullet() function to our Player class.
class Player extends GameObject {
constructor(pos, vel, img, size, life, tag) {
super(pos, vel, img, size, life, tag);
this.fireRate = 10;
this.damage = 20;
this.bulletSpeed = 15;
}
fireBullet(bulletImg) {
return (frameCount % this.fireRate === 0) ? new GameObject(
createVector(this.pos.x, this.pos.y - (this.size.y / 2)), // position
createVector(0, -this.bulletSpeed), // velocity
bulletImg, // image asset
createVector(5, 12), // size
100, // life
'bullet' // tag
) : null;
}
}
-fireBullet() - takes bulletImg image as an argument that is defined in our AssetManager class
- (frameCount % this.fireRate === 0) it will only fire every frameCount that is divisible every frameRate we defined, otherwise return null meaning no bullet will be fired at that moment
- We are using this.bulletSpeed in negative as our y velocity since the bullet will be pointing upwards
Now let's update our GameManager class
class GameManager {
constructor() {
this.assetManager = new AssetManager();
}
init() {
this.bullets = [];
this.player = new Player(
createVector(width / 2, height - 100),
createVector(0, 0),
this.assetManager.playerImg,
createVector(75, 75),
100,
'player'
);
}
loadAssets() {
this.assetManager.preload();
}
update() {
this.renderPlayer();
this.renderBullets();
}
renderBullets() {
this.bullets.forEach(bullet => {
bullet.update();
bullet.show();
});
this.bullets = this.bullets.filter(b => !(b.pos.y < 0));
}
renderPlayer() {
this.player.pos.x = constrain(mouseX, 37.5, width - 37.5);
this.player.update();
this.player.show();
const bullet = this.player.fireBullet(this.assetManager.bulletImg);
if (bullet) {
this.bullets.push(bullet);
}
}
}
On the GameManager init, we added this.bullets = [];, to hold all of our bullets in the game.
- renderBullets() - show and updates all the bullets in the screen
- this.bullets = this.bullets.filter(b => !(b.pos.y < 0)); -removes all the bullets that are leaving the screen to prevent memory leak
- We have updated renderPlayer() to invoke Player's fireBullet() function on every function call, and only add the bullet if it is not null
After running the game, we can see an output like this:
Cool now we have a Player firing bullets, next to create our enemies.
2. Enemies and Collision
Before we create our enemy class, let's add a collision detection on our GameObject class.
isCollided(gameObjPos, gameObjSize) {
return ((gameObjSize.y + this.size.x) / 2) > (this.pos.dist(gameObjPos));
}
We are checking here if the distance from gameObject's position we are comparing it with, is less than the combination of two sizes of both gameObject, then it is colliding with each other, it will return true.
Let's now update our GameManager class.
class GameManager {
constructor() {
this.assetManager = new AssetManager();
}
init() {
this.bullets = [];
this.enemies = [];
this.enemyGenerateSpeed = 200;
this.enemySpeed = 2;
this.player = new Player(
createVector(width / 2, height - 100),
createVector(0, 0),
this.assetManager.playerImg,
createVector(75, 75),
100,
'player'
);
}
loadAssets() {
this.assetManager.preload();
}
update() {
this.renderPlayer();
this.renderBullets();
this.renderEnemies();
this.generateEnemy();
}
renderBullets() {
this.bullets.forEach(b => {
b.update();
b.show();
});
this.bullets = this.bullets.filter(b => {
let isCollided = false; // variable to check if the player is yet to collide to an enemy
this.enemies = this.enemies.map(e => {
if (!isCollided && b.isCollided(e.pos, e.size)) {// only check if the bullet has collided or not
e.life -= this.player.damage; //if the bullet has collided to an enemy subtract player damage to enemy life
isCollided = true; //set the collision to true
}
return e;
});
return !(b.pos.y < 0 || isCollided); // removes enemies that is out of the canvas and whose life is 0
});
}
renderPlayer() {
this.player.pos.x = constrain(mouseX, 37.5, width - 37.5);
this.player.update();
this.player.show();
const bullet = this.player.fireBullet(this.assetManager.bulletImg);
if (bullet) {
this.bullets.push(bullet);
}
}
generateEnemy() {
if (frameCount % this.enemyGenerateSpeed === 0) {
const enemyCount = random(2, 6); // random enemy count 2-6
let posX = [0, 1, 2, 3, 4, 5]; // x positions for the enemies to be generated
[...Array(parseInt(enemyCount)).keys()].forEach(a => {
let xIndex = posX.splice(parseInt(random(0, posX.length - 1)), 1)[0];
// removes an element in posX variable, indicates that position is taken and to avoid overlapping
this.enemies.push(new GameObject(
createVector((xIndex * 75) + 37.5, 0 - 75),
// multiply the zIndex by 75 since 75 is the width of the enemy
createVector(0, this.enemySpeed),
random(this.assetManager.enemiesImg),
createVector(70, 70),
60,
'enemy'
));
})
}
}
renderEnemies() {
this.enemies.forEach(e => {
e.update();
e.show();
});
this.enemies = this.enemies.filter(e => !(e.life <= 0 || e.pos.y > height));
}
}
We have added 3 new variables on init() method:
- enemies - to hold all the enemies that is within the game canvas
- enemyGeneration - number value that indicates for every nth frame we are creating a new set of enemies
- enemySpeed - indicates how fast the enemy should move.
New methods:
- renderEnemies() - shows and update the enemies' position
- this.enemies = this.enemies.filter(e => !(e.life <= 0 || e.pos.y > height)); - removes the enemy whose life is 0 or is out of the canvas.
- generateEnemy() - generates set on enemies on random position and random in count, that is based on the enemyGeneration variable
We have updated also the renderBullets() function to check if a bullet has collided to an enemy, if it has collided remove the bullet and subtracts player damage to enemy that has been hit.
After running our game again we can see this output:
Cool! now it looks like a game now.
That ends our part 2 of this article series.
Next Article: http://onecompileman.com/blogs/6