In the previous "interlude" I created player character that can be moved around the screen with the cursor keys. In this post, I build on the code to give the player character the ability to shoot back at the enemy.
Using the bullet class that already exists
We can use the Bullet class object already created and utilised by the Danmaku spawner, to make bullets fly out of the player character. In this example, the player will simply shoot bullets directly upward so it would be just as easy to simply set the velocity of a bullet shaped image object rather than rely on the extended Bullet class. However, we'll leave ourselves the option of maybe expanding the firing pattern of the player for the future.
Create bullets pool specifically for player
The code is currently written such that our Player Class can access the bullet pool created for the danmaku spawner. The group is currently set to create 500 'blank' bullets to be recycled so given the current complexity of our spawner, should be plenty to accomodate the firing ability of our new player character. However, we will create a separate pool of bullets for the player. This will allow us to distinguish easily between bullets fired by our player and our Danmaku, should we want to in the future. It is created in exactly the same way as the enemy bullet group, except the number of bullets has been restricted to 10 (as opposed to 500), and we've given it a different name.
// create bullets group to be utilised by the player - make a pool of 10 bullets
playerBullets = this.physics.add.group({
name: " Player bullets",
enable: false
});
playerBullets.createMultiple({
classType: Bullet,
frameQuantity: 10,
active: false,
visible: false,
key: "bullet1" // set the default bulletType as "bullet1"
});
Add a method to the Player class to shoot bullets
We'll give the ability for the Player to shoot when the spacebar is pressed. So the first thing we need to do is to detect when the space bar is pressed. We can use the cursorkeys object created in the previous post to detect not only cursor key presses, but also spacebar. However, we will not use this object since if we do, we will not be able to shoot when one of the cursor keys and the space bar are pressed at the same time (since the cursorkey object can only contain one condition). Instead, we will create a object specifically for the spacebar using the following bit of code, to be included in the constructor function of the Player class.
this.spaceBar = scene.input.keyboard.addKey(
Phaser.Input.Keyboard.KeyCodes.SPACE
);
Then we can include following to execute a "shoot" function when the spacebar is pressed.
if (this.spaceBar.isDown) {
this.shoot(elapsedTime);
}
Basic elements of the player's shoot function can be copied from the fireweapon method of the DanmakuWrapper class, however, since we only want to fire when the space bar is pressed and not continuously loop like the danmaku spawner, we will revert back to the manual timer check, that we used back in the example I created in the very first of these series (step 1). The code is as shown below
shoot(elapsedTime) {
// only shoot bullet if time determined by this.reloadTime has passed since last time bullet fired
if (elapsedTime > this.bulletLastFired + this.reloadTime) {
this.bulletLastFired = elapsedTime;
const playerBullet = playerBullets.getFirstDead(false);
if (playerBullet) {
playerBullet.fire({
x: this.x, // bullet is fired from the player character position
y: this.y - this.displayHeight / 2, // bullet fired from top of the player character
shootAngle: -90, // direction in which the bullet is fired. 90 is upwards
bulletSpeed: 400, // the speed of the bullet
bulletAngle: -90, // make the bullet face the direction in which it is fired, ie upwards
bulletType: "bullet6"
}); // end of calling the Bullet class fire method
} // end of if statement which checks whether bullet is null
} // end of if statement which checks if sufficient time has elapsed since last bullet fired
} // end of shoot method
Other minor additions and amendments to the code
To make the new shoot function work, we will need to pass the elapsed time to it, when we call it from function update.
We will also add a few lines to display information about the player bullet group, in addition to the danmaku bullet group (now renamed).
function update(timeElapsed) {
player.move(timeElapsed); // call the player class update() method so that we can move the player around with the cursor keys
text.setText(poolInfo(enemyBullets)); // show the latest bullet pool info for danmaku
playerText.setText(poolInfo(playerBullets)); // show the latest bullet pool info for player
} // end of update function
I have also amended some of the parameters to instantiate the Danmwaku Wrapper class, including the number of cannons, bullet image and the direction of cannon rotation.
danmaku = new DanmakuWrapper(this, {
x: enemy.x, // origin of the danmaku spawn
y: enemy.y, // origin of the danmaku spawn
shootAngle: 90, // direction in which the cannon faces, ie bullet direction.
angleVelocity: -45, // angular velocity of the 'cannon' that fires the bullets
numberOfCannons: 5, // number of cannons
bulletSpeed: 200, // speed of bullets fired from cannon
bulletAcceleration: 0, // acceleration of bullets fired from cannon
timeBetweenBullets: 200, // time between bullets in ms
bulletType: "bullet10" // specify the bullet image to be used
});
Big Boss character
Finally, I have changed the picture of the enemy ship to a much meaner looking bigger ship, by adding a few extra lines of code to the create function - again this has been 'copied' from that used in many of Phaser Discourse examples by Samme.
// load the enemy ship image
this.load.image("enemyShip", "assets/sprites/bsquadron2.png");
// load a big boss image
this.load.spritesheet("bigBoss", "assets/sprites/bsquadron-enemies.png", {
frameWidth: 192,
frameHeight: 160
});
Unlike the previous enemy ship, which was a single image, this time the 'Big Boss' is being created as a sprite. Specifically, in previous examples, the image was loaded using add.image (or physics.add.image, to have an associated physics body, in case you want to utilise the features of the physics engine - which we have not so far for the enemy character) and then put on the scene using add.image, this time the 'spritesheet' is being loaded using load.spritesheet method. A 'spritesheet', just like an 'image' is a single picture file (usually png or jpg). However, the difference is that spritesheets have several 'frames', where as an 'image' is one frame. So the Big Boss 'spritesheet' we can borrowing from Phaser labs for this example is the file below. The png file is 768 pixels wide and 160 pixels high. You can see that there are 4 'frames' in the file. Each 'frame' is 192 pixels wide; 192 x 4 being 768. In the config of the load.spritesheet method you can tell the Phaser loader plugin how big each frames are. With the load.spritesheet method, you can only handle sprite sheets with frames of equal size, but Phaser does provide other methods to allow you to load spritesheets with different sized frames.
To cut a long explanation short, by utilising spritesheets containing multiple 'frames', and you can create animations which cycle through the frames in a predetermined order. Such animations can be attached to specific sprite objects. as opposed to 'image', is basically several images contained in one png file so that animation effects can be created. As such a few additional parameters to tell Phaser where the various frames are held in the sprite sheet.
In the original Phaser discourse example, the following code is used to place a sprite object with a physics body onto the scene. All it is doing is to utilise frame 1 (ie the 2nd frame - 1st frame being 0) from the sprite sheet above, which has been loaded with key = "bigBoss" as a sprite object in the middle of the screen. No animation is used.
enemy = this.physics.add.sprite(WIDTH / 2, HEIGHT / 2, "bigBoss", 1);
In this example we are creating, we are not utilising animation so we actually don't need to utilise a sprite. In fact, we don't even need a physics body (the phaser discourse example uses collision detection which relies on having physics bodies). So to simply put frame 1 on the screen as an image, the following code can be used instead.
enemy = this.add.image(WIDTH / 2, HEIGHT / 2, "bigBoss", 1); // can use this
However, we will create the image as a sprite object, so that we have the option to build on this example with collision detection and so on, in the future.
And that's basically it. We can now use the cursor keys to move the player, and press the space bar to shoot bullets straight upwards.
The entire code in Codepen Pen format is available for your perusal.
Comments