Powerups
A common game mechanic is powerups, which are items that players can pick up to gain temporary abilities or bonuses. Let's see how we can implement powerups in our game.
On this page, we will add a multi-shot powerup to Astroblast, which gives the player the ability to shoot three projectiles at once for a short period of time.
Importing assets
We are going to need a new image to represent our multi-shot powerup.
-
Download the following file by right-clicking the link and choosing Save Link As (Google Chrome) or Download Linked File (Safari):
-
Drag-and-drop the
bolt.pngfile into theimagesfolder in the file list on the left side of the editor.
This asset comes from the Space Shooter Remastered asset pack from Kenney, an excellent resource for free game assets!
Creating NumExtraShots
First, we will create a new collector called NumExtraShots to keep track of how many extra shots the player
has at any one moment.
Later we will make a powerup temporarily give an extra 2 shots to this value,
giving them a total of 3 shots at once.
Create a new file called multiShot.easel. Insert the following highlighted code into it:
pub collect unit.NumExtraShots ~ Sum
We are using Sum for the merge function of this collector.
This means, if a player is lucky enough to pick up two multi-shot powerups at the same time,
they will get 4 extra shots instead of just 2.
See Collectors to learn more about collector merge functions.
Creating the MultiShotPowerup
Now we will create a function MultiShotPowerup for the powerup itself.
When the powerup collides with a ship, it will give that ship 2 extra shots for 10 seconds,
after which the effect will expire.
Select multiShot.easel. Insert the following highlighted code into it:
pub collect unit.NumExtraShots ~ Sum
pub fn unit.MultiShotPowerup(pos) {
use body=unit
let radius=0.5, speed=2, color=#ffaa00
Body(pos=, velocity = speed * RandomVector)
BoundaryWrappingSystem(radius=)
DrainingSystem(radius=)
PolygonCollider(shape=Circle(radius=), category=Category:Powerup, collideWith=Category:Ship)
PolygonSprite(shape=Circle(radius=1.5*radius), color=, opacity=0.5, luminous=1, bloom=3, bloomAlpha=1)
ImageSprite(@bolt.png, radius=, luminous=1)
once BeforeCollide that {
if that.Category.Overlaps(Category:Ship) {
Spawn {
give NumExtraShots(2)
once Tick(10s) { Expire }
}
}
Expire
}
}
Spawning the MultiShotPowerup
We will once again make the powerup appear randomly when an asteroid is destroyed,
in the same way as the ExtraLifePowerup.
We will make MultiShotPowerup more common than ExtraLifePowerup, which will stay rare.
- Select
asteroid.easel. - Find the
SpawnAsteroidLootfunction. - Replace its contents with the following highlighted code:
// ...
fn SpawnAsteroidLoot(pos) {
if Random < 0.005 {
surprise {
0.2 => Spawn unit { ExtraLifePowerup(pos=) },
0.8 => Spawn unit { MultiShotPowerup(pos=) },
}
}
}
The surprise block is a convenient way to randomly choose between multiple options with different probabilities.
Using NumExtraShots
When a ship is doing a multi-shot, each projectile should be fired at a different angle so that they spread out.
We will put this logic in a function MultiShot so we can reuse it for both lasers and bombs.
Creating the MultiShot function
Select multiShot.easel. Insert the following highlighted code into it at the bottom:
pub collect unit.NumExtraShots ~ Sum
pub fn unit.MultiShotPowerup(pos) {
// ...
}
pub fn MultiShot(anglePerShot=0.05rev, [unit]) |angleOffset| {
let startAngle = -0.5 * anglePerShot * NumExtraShots
for i in Range(0, 1+NumExtraShots) {
delve(angleOffset = startAngle + i*anglePerShot)
}
}
Multi-shot Laser
Now let's modify our LaserAbility function to use MultiShot.
- Select
laser.easel. - Inside
LaserAbility, find theSpawn projectile { Laser }block. - Delete it and replace it with the following highlighted code:
pub fn ability.LaserAbility([unit, owner]) {
let keycode=Digit1, cooldown=0.5s
on ButtonDown(keycode) { SelectedAbility = ability }
on ButtonDown(Click) {
while SelectedAbility == ability && IsButtonDown(Click) {
MultiShot angleOffset {
Spawn projectile {
Laser(angleOffset=)
}
}
await Tick(cooldown / RateOfFireFactor)
}
}
give AbilityGallery {
with SelectedAbility {
AbilityIcon(
keycode=, image=@laser-precision.svg,
isSelected=(SelectedAbility == ability),
tooltip="Press 1 for Lasers",
)
}
}
}
// ...
This will pass the angleOffset from MultiShot into Laser. We need to modify Laser to accept this parameter.
We will do that next.
Angled lasers
Select laser.easel. Add angleOffset into the Laser function like this:
pub fn ability.LaserAbility([unit, owner]) {
// ...
}
pub fn projectile.Laser(angleOffset=0rev, [unit, owner]) {
use body=projectile, luminous=1
let speed=50, radius=0.25, color=#00ccff
Body(
pos=unit.Pos,
velocity = speed * Direction(unit.Heading + angleOffset),
)
// ...
}
That's the laser complete, now we need to do the same with bombs.
Multi-shot bombs
- Select
bomb.easel. - In the
BombAbilityfunction, find theSpawn projectile { Bomb }block - Delete it and replace it with the following highlighted code:
pub fn ability.BombAbility([unit, owner]) {
let keycode=Digit2, cooldown=1.25s
on ButtonDown(keycode) { SelectedAbility = ability }
on ButtonDown(Click) {
while SelectedAbility == ability && IsButtonDown(Click) {
MultiShot angleOffset {
Spawn projectile {
Bomb(angleOffset=)
}
}
await Tick(cooldown / RateOfFireFactor)
}
}
give AbilityGallery {
with SelectedAbility {
AbilityIcon(
keycode=, image=@fire-bomb.svg,
isSelected=(SelectedAbility == ability),
tooltip="Press 2 for Bombs",
)
}
}
}
// ...
Angled bombs
Select bomb.easel. Add angleOffset into the Bomb function like this:
pub fn ability.BombAbility([unit, owner]) {
// ...
}
pub fn projectile.Bomb(angleOffset=0rev, [unit, owner]) {
use body=projectile, luminous=1
let speed=10, impulse=20, radius=0.3, color=#00ccff
let shape=Capsule(radius=, extent=0.1)
let areaOfEffect=5, detonateRadius=0.35*areaOfEffect
Body(
pos=unit.Pos,
velocity = speed * Direction(unit.Heading + angleOffset),
turnRate = 2rev * (Random < 0.5 ? 1 : -1),
)
// ...
}
Click Preview to test your game. Destroy some asteroids, and eventually a multi-shot powerup will appear. Pick it up, and you should be able to shoot three lasers or bombs at once. This effect should only last for 10 seconds, after which you will go back to shooting one projectile at a time.
If you are having trouble getting a multi-shot powerup,
try increasing the chance of it spawning in SpawnAsteroidLoot (e.g. if Random < 0.1) so it spawns more frequently while you're testing.
Don't forget to revert this change afterward though,
otherwise your players will be getting multi-shot powerups all the time!
Buff glow
In games, a temporary positive effect is called a buff, and it is common for buffs to come with a glowing visual effect so players can see they are still active. Let's do this with our multi-shot powerup.
Select multiShot.easel.
Insert the following highlighted code into MultiShotPowerup:
pub collect unit.NumExtraShots ~ Sum
pub fn unit.MultiShotPowerup(pos) {
// ...
once BeforeCollide that {
if that.Category.Overlaps(Category:Ship) {
Spawn {
use unit=that, body=unit
give that.NumExtraShots(2)
once Tick(10s) { Expire }
PolygonSprite(
shape=Circle(0), color=, luminous=1,
bloom=5, bloomAlpha=1, opacity=0.5, layer=-10,
)
}
}
Expire
}
}
Click Preview to test your game. Destroy some asteroids until you can pick up a multi-shot powerup. You should see a golden glow around your ship while the buff is active.
This is an example of a PolygonSprite that is owned by one entity (the buff effect),
but is attached to the body is a different entity (the ship receiving the buff).
The result of this is the sprite follows the ship around the screen,
but expires when the buff expires (after 10 seconds), as opposed to when the ship expires which would be much later.
Recap
In this chapter, we implemented a multi-shot powerup that gives the player the ability to shoot three projectiles at once for a short period of time.
We created a new collector NumExtraShots to keep track of how many extra shots the player has.
Our collector has a Sum merge function, which means if the player picks up two multi-shot powerups at once,
they will get 4 extra shots instead of just 2.
A new function MultiShot to handle the logic of spawning multiple projectiles at different angles.
We implemented this using subblocks
which let us reuse the same MultiShot function for both lasers and bombs, even though they spawn different projectiles.
Finally, we added a glowing visual effect to the ship while the multi-shot buff is active.
This was an example of a PolygonSprite that is owned by one entity (the buff effect),
but is attached to the body of a different entity (the ship receiving the buff).