In "Danmaku Interlude Part 6", I implemented (or more accurately, copied) code to move the big boss ship around the screen using Tweens. Tweens are extremely powerful but even by using its basic functionality, you could achieve quite impressive effects.
In this post, we take it a step further and use another functionality of Phaser 3 tweens, which is called chaining of tweens; put simply it allows you to chain together a series of different tweens.
As a reminder, the code used in interlude part 6 was as below. In that example, the big boss suddenly appeared at the top part of the screen, then immediately started to float around.
enemyMoving = this.tweens.add({
targets: enemy.body.velocity,
props: {
x: { from: 150, to: -150, duration: 4000 },
y: { from: 50, to: -50, duration: 2000 }
},
ease: "Sine.easeInOut",
yoyo: true,
repeat: -1
});
In this post, we will create a chained tween to have the big boss, slowly descend from the top, starting offsreen, wait for a while, then start the floating around movement. Rather than try to explain the Phaser tweens functionality, I will walk through the relevant code, shown below (as I will explain later, the code has been restructured such that i) the enemy is now a separate extended game object, and ii) tween is now included in the new Enemy game object, hence the use of scene, as opposed to this).
this.enemyMoving = scene.tweens.timeline({
tweens: [
{
targets: this,
y: 150,
duration: 5000,
onComplete: function (tween, targets, sprite) {
sprite.firing = true;
},
onCompleteParams: [this]
},
{
targets: this.body.velocity,
props: {
x: { from: 150, to: -150, duration: 4000 },
y: { from: 50, to: -50, duration: 2000 }
},
ease: "Sine.easeInOut",
yoyo: true,
repeat: -1,
offset: 6000
}
]
});
The blue bit is the same as before, except for the addition of the "offset: 6000" highlighted in bold. It is the red section which makes the big boss character from offscreen to descend to y position = 150, over a period of 5000 ms, i.e. 5 seconds. Leaving aside the onComplete bits, the next important bit is "offset: 6000". What this is telling Phaser to do, is to start the next tween (i.e. blue bits) 6000 ms (ie 6 seconds) from the start of the chained tween. In other words, the ship moves down to y=150 in 5 seconds, but then it does nothing for another 1 second, before the second part of the tween takes over and starts floating around.
The onComplete parameter allows you to execute certain code upon completion of a tween. In the above code, I switch a variable called "firing" to true - this essentially tells the enemy game object to start firing the danmaku. Official Phaser documentation on how to use the onComplete parameter is a bit confusing, but the above seems to work.
Creating a enemy object class
I have created a separate enemy object class, whose role is essentially to instantiate a danmaku, fire it, and hold flight patterns (like the one above). On top of the "parent" Enemy object class, I have created sister classes for the normal enemy and the "big boss". There is nothing particularly new here, so I will not discuss it here in detail.
By creating a separate enemy class, I have now embedded the "follow" function within the enemy class preUpdate function, so that I do not have to call it from the main update function in order to keep the danmaku to "track" the game object.
Intermittent firing mechanism
In lesson 4, I created a "intermittent" danmaku, using 2 timer events. One to fire a series of 3 bullets (which I refer to as a "Round"), and the other one to essentially fire the former timer event. Specifically, the timer event for firing of the 3 bullets "round" was as follows.
const timerConfig = {
delay: this.timeBetweenBullets,
callback: this.fireweapon,
callbackScope: this,
repeat: this.bulletsInRound - 1 // repeat n+1 times
};
const machineGun = new Phaser.Time.TimerEvent(timerConfig);
This would be reset and fired by calling the below function.
function fireround() {
machineGun.reset(timerConfig); // seems reset of config of the timer event required after each time it is completed
scene.time.addEvent(machineGun); // call the timer event that controls the firing of each round
}
Then the following timer event calls the fireround function in an infinite loop.
this.repeatWeaponFire = scene.time.addEvent({
delay: this.timeBetweenRounds,
callback: fireround,
callbackScope: this,
loop: true
}); // end of timer event
In the lesson 4 code, all of the above was included within the danmaku class.
In the latest version of the code, the following are included in constructor function of the danmaku class.
this.fireRoundConfig = {
delay: this.timeBetweenBullets,
callback: this.fireweapon,
callbackScope: this,
repeat: this.bulletsInRound - 1 // repeat n+1 times
};
this.fireRound = new Phaser.Time.TimerEvent(this.fireRoundConfig); // end of timer event
} // end of constructor
fireARound(scene) {
if (this.fireRound.getOverallRemaining() === 0) {
// if the event has been fired and completed, reset
this.fireRound.reset(this.fireRoundConfig); // seems reset of config of the timer event required after each time it is completed
}
The major difference (apart from the fact that delay of 2000ms is hardcoded, as opposed to referring to this.timeBetweenRounds) is that the timer event to call the fireARound timer event - shown below - is created in the constructor function of the Enemy object.
// create a timer event to fire rounds at frequency determined by this.timeBetweenRounds
this.reloadWeaponTimer = scene.time.addEvent({
delay: 2000,
callback: this.fireDanmaku,
args: [scene],
callbackScope: this,
loop: true
}); // end of timer event
And the fireARound timer event is not called directly, but indirectly via the following method of the Enemy object.
fireDanmaku(scene) {
if (!this.firing) {
return;
}
this.danmaku.fireARound(scene);
}
In hindsight, it might have been more compact and easier to understand to create the "outer" timer event in the danmaku object itself, also.
Comments