Boss battles are one of the most enjoyable experiences in games. Building a good boss battle is always a challenge, but the theory behind it is quite simple. The first rule to follow is that a boss should consist of three unique stages of increasing difficulty. The second rule is that the boss should emphasize the latest skills that the user has acquired. The third and final rule is that the player should always have something to do.
Our boss battle will not be against another character, but against a fortress. The first stage will consist of three retractable Cannons that will shoot Cannonballs across the room. All three Cannons must be destroyed to move onto the second stage. Stage two will have a powerful LaserCannon that will move up and down and shoot a room wide Laser Beam that the player will need to avoid. The final stage will be to destroy the Boss Core that is protected by two Shields. The Shields will only be open for a short period of time. During the entire boss fight, there will be an indestructible Gun that will shoot bullets at the player wherever they are in the room. Each progressing stage, this Gun will fire more rapidly, making the game more challenging. Let's start building the boss!
We will start with the indestructible Gun since it will be the primary boss attack through the battle. The Gun will need to rotate so that it always points towards the player. When it shoots a Gun Bullet, the instance of the Gun Bullet will come from the tip of the Gun and move in the direction that the Gun is pointing:
spr_Gun_Bullet
, and load Chapter 5/Sprites/Gun_Bullet.gif
with Remove Background checked. Center the Origin and click on OK.obj_Gun_Bullet
, and assign spr_Gun_Bullet
as the Sprite.2000
.scr_Damage
, with the following code:if (obj_Player.action != DAMAGE) { health -= myDamage; with (obj_Player) { y -= 1; vspeed = -MAXGRAVITY; hspeed = 8 * -facing; action = DAMAGE; isDamaged = true; } }
This script is specifically for the enemy weapons. We start by checking to see if the player is not already damaged so that the player isn't punished repetitively. Then we reduce the global health by the value indicated by the variable myDamage
. By using a variable like this, we can have different weapons apply differing amounts of damage. We then affect the player directly through a with
statement. We want to launch the player into the air, but first we need to raise the player off the ground by one pixel first to ensure the ground collision code doesn't snap it back down. Next we apply a vertical velocity and a horizontal velocity in the opposite direction that they are facing for a push back effect. We set the player's action to the DAMAGE
state and indicate that damage has happened.
scr_Gun_Bullet_Create
, and initialize the myDamage
variable. Then apply it to a Create event in obj_Gun_Bullet
.myDamage = 5;
scr_Gun_Bullet_Collision
, which calls the damage script and removes the Bullet. We did not put the destruction of the instance into scr_Damage
so that we have the option for weapons that can't be destroyed, use this script:scr_Damage(); instance_destroy();
obj_Gun_Bullet
with this script attached. The Gun Bullet is now complete.spr_Gun_Idle
and spr_Gun_Run
. Load Chapter 5/Sprites/Gun_Idle.gif
and Chapter 5/Sprites/Gun_Run.gif
to their associated sprite with Remove Background checked.0
and Y: 16
on both sprites and click on OK.obj_Gun
, and assign spr_Gun_Idle
as the Sprite.-1000
.scr_Gun_Create
, which will be added to obj_Gun
as a Create event:action = IDLE; facing = RIGHT; tipOfGun = sprite_width; canFire = false; delay = 90; alarm[0] = delay; myIdle = spr_Gun_Idle; myRun = spr_Gun_Run;
We will be using the animation system here, so we need to set values for the action and facing variables which are required. The following four variables relate to the shooting of the Gun. First is tipOfGun
for where the end of the barrel is located, canFire
is the trigger, delay
is how long to pause between shots, and the alarm will shoot the Gun Bullet. Finally, we have two states of animation that we need to apply. We do not need to add all the other variables such as myDamage
unless the object utilizes that state.
scr_Gun_Step
, which will be placed in a Step | Step event. Here's the code we need:scr_Animation_Control(); if (image_index > image_number-1) { action = IDLE; } if (canFire) { action = RUN; alarm[1] = 5; canFire = false; } image_angle = point_direction(x, y, obj_Player.x, obj_Player.y);
We start by running the animation script. We want the Gun to play the firing animation only once, so we check the currently displayed image against the last image of the sprite. Using image_number
gives us the number of frames, but we need to subtract by one as frames of animation start at zero. If it is the last frame, then the Gun goes into the IDLE
state. Next we check to see if the Gun is to shoot. If it is, we change states to play the shooting animation, set a second alarm for 5 frames, and then turn off canFire
. Finally we track the player by rotating the sprite based on the angle between the Gun and the player.
scr_Gun_Alarm0
, for an Alarm | Alarm 0 event:canFire = true;
scr_Gun_Alarm1
, that will be added as an Alarm | Alarm 1 event:myX = x + lengthdir_x(tipOfGun, image_angle); myY = y + lengthdir_y(tipOfGun, image_angle); bullet = instance_create(myX, myY, obj_Gun_Bullet); bullet.speed = 16; bullet.direction = image_angle; alarm[0] = delay;
Since we need the bullet to leave the end of the barrel of the gun we are going to need some trigonometry. We could use sine and cosine to calculate the X and Y values from the origin of the circle and radial distance, but there is a much easier way. Here we are using lengthdir_x
and lengthdir_y
to do the math for us. All that it needs is the radial distance and the angle which we can then add to the local coordinates of the Gun. Once we have those variables, we can create the bullet in the proper position, set its speed, and direction. Finally we reset the first alarm so that the Gun will fire again.
scr_Player_Damage
, with the following code:if (isOnGround) { isDamaged = false; } else { scr_Gravity(); }
We check to see if the player is on the ground or not as that will deactivate the damage state. If the player is in the air, we apply gravity and that is it.
scr_Player_Step
and add a conditional statement for whether the player is damaged or not. Here is the entire script with the new code in bold:if (isDamaged) { scr_Player_Damage(); } else { if (isOnGround) { scr_Player_GroundControls(); } else { scr_Player_AirControls(); } scr_Player_Attack(); } scr_Animation_Control();
We check to see if the player is in damage mode, and if it is, we run the damage script. Otherwise, we function as normal with all the control systems in the else
statement. The animation script is always called regardless of damage.
The first stage weapon is a Cannon that hides itself for protection and only exposes itself to shoot. We will have three Cannons stacked on top of each other to make the player have to jump onto platforms. To destroy the Cannons the player will need to shoot each Cannon while it is exposed:
spr_Cannonball
, and load Chapter 5/Sprites/Cannonball.gif
with Remove Background checked.12
, Y: 32
and click on OK.obj_Cannonball
, and assign spr_Cannonball
as the Sprite.-900
so that it will appear in front of most objects.scr_Cannonball_Create
:myDamage = 10; hspeed = -24;
This weapon is powerful and will cause 10 points of damage. We also set the horizontal velocity so that it quickly moves across the room.
scr_Damage
to a Collision | obj_Player event. The Cannonball is now ready to be shot.spr_Cannon_IdleDown
, spr_Cannon_IdleUp
, spr_Cannon_RunDown
, spr_Cannon_RunUp
, and spr_Cannon_Damage
. Load the associated files from the Chapter 5/Sprites/
folder without checking Remove Background.obj_Cannon
, and assign spr_Cannon_IdleDown
as the Sprite.-1000
so that the Cannon will be in front of the rest of the Boss parts.scr_Cannon_Create
, to initialize all the variables in the Create event:myHealth = 20; action = IDLEDOWN; facing = RIGHT; canFire = false; myIdleUp = spr_Cannon_IdleUp; myIdleDown = spr_Cannon_IdleDown; myRunUp = spr_Cannon_RunUp; myRunDown = spr_Cannon_RunDown; myDamage = spr_Cannon_Damage;
The Cannon will take several hits before it is destroyed, so we have a myHealth
variable to track the damage. We then set the action state by facing to the right, as we are not flipping the sprite, and establish a shooting variable. We then have all the animation states we need for the Cannon to work.
scr_Cannon_Step
, for a Step | Step event with the functionality for switching states and firing the Cannonballs:scr_Animation_Control(); if (image_index > image_number-1) { if (action == RUNUP) { action = IDLEUP;} else if (action == RUNDOWN) { action = IDLEDOWN;} } if (canFire) { action = RUNUP; alarm[0] = 60; canFire = false; } if (myHealth <= 0) { instance_destroy(); }
Similar to the Gun, we start with calling the animation system script. We then check if the Cannon is on the last frame of the animation. Here we have two different idle states depending on whether the Cannon is exposed or not. We check to see which state we are in and set the appropriate idle state. Next we check if the Cannon should shoot, and if it should, we expose the Cannon and set an alarm to create the Cannonball in two seconds. Finally, we do a health check and if the Cannon is out of life, it removes itself from the game.
scr_Cannon_Alarm0
, and add it to an Alarm | Alarm 0 event with the following code:instance_create(x, y, obj_Cannonball); action = RUNDOWN;
Here we just create a Cannonball and then set the animation to retract the Cannon.
scr_Cannon_Collision
, and apply it to a Collision | obj_Bullet event with the following code:if (action == IDLEUP) { myHealth -= 10; action = DAMAGE; with (other) {instance_destroy();} }
We start by making sure that damage will only be applied if the Cannon is exposed. If it is, then we take 10 points of its health, change to the damage animation, and remove the bullet. The Cannon is now complete.
obj_Boss
. There is no sprite to assign as the Boss is comprised of other objects.scr_Boss_Create
, to initialize variables in the Create event:isPhase_01 = true; isPhase_02 = false; isPhase_03 = false; isBossDefeated = false; boss_X = 672; gun = instance_create(32, 32, obj_Gun); cannonA = instance_create(boss_X, 64, obj_Cannon); cannonB = instance_create(boss_X, 192, obj_Cannon); cannonC = instance_create(boss_X, 320, obj_Cannon);
We start by establishing variables for the three phases and whether the boss has been defeated. We then create a variable for the X location of the boss with the indestructible Gun located in the upper left corner of the room and a tower of Cannons right where the Boss is. We establish variables for each weapon so that the Boss can control them.
tm_Boss_Phase01
.180
. This will start six seconds into the battle.scr_Phase01_180
, and fire the middle Cannon. Apply this script to the Time Line:if (instance_exists(cannonB)) { cannonB.canFire = true;}
Since the player can destroy the Cannons, we need to check to see if the Cannon is still in existence. If it is, we set the Cannon's canFire
variable to true and the Cannon code will handle the rest.
360
.scr_Phase01_360
, and activate the other two Cannons:if (instance_exists(cannonA)) { cannonA.canFire = true; } if (instance_exists(cannonC)) { cannonC.canFire = true; }
We need to check both Cannons individually so that if one is destroyed, the other will still shoot.
scr_Boss_Create
and start a looping Time Line after the last line of code:timeline_index = tm_Boss_Phase01; timeline_running = true; timeline_loop = true;
BossArena
and make sure you remove the instance of the Gun if it is still in the room.obj_Boss
on the right side of the map, though the actual location does not matter.obj_Ground
as seen in the following screenshot:Once the player destroys all the Cannons, the second phase will begin. Here we will have a giant LaserCannon that moves constantly up and down. Every few seconds it will fire a large Laser Beam that will stretch across the entire room. The player can damage the LaserCannon at all times, though it will have much more health:
spr_LaserBeam
, and load Chapter 5/Sprites/LaserBeam.gif
without checking Remove Background. The sprite may appear small, being only eight pixels wide, but we will stretch this sprite across the screen so it could work in any room.8
and Y: 32
.obj_LaserBeam
, apply spr_LaserBeam
as the Sprite and set the Depth to -600
.scr_LaserBeam_Create
, to initialize variables in a Create event:myDamage = 20; myLaserCannon = 0; image_xscale = room_width / 8;
The amount of damage from this weapon is much higher than the other weapons, which is fitting for the second phase. We also have a myLaserCannon
variable that will be used to keep the Laser Beam aligned with the LaserCannon as it moves. The value has been set to zero, though this will become the ID of the LaserCannon that spawns it, which we will get to in a moment. Finally, we stretch the sprite across the room. The variable image_xscale
is a multiplier, which is why we are dividing the room width by eight, the width of the sprite.
scr_LaserBeam_EndStep
, to make the beam move with the LaserCannon.x = myLaserCannon.x; y = myLaserCannon.y;
We move the X and Y coordinates with the LaserCannon that creates the Laser Beam. We are placing this into the End Step event because the LaserCannon will move on a Step event and this will ensure that it is always in the correct position.
scr_Damage
to be added to a Collision | obj_Player event. The Laser Beam is now complete.spr_LaserCannon_Idle
, spr_LaserCannon_Run
, and spr_LaserCannon_Damage
. Load the associated files from the Chapter 5/Sprites/
folder all of which need to have Remove Background checked.16
and Y: 56
. This will help place the Laser Beam where we want it to be.obj_LaserCannon
, and assign spr_LaserCannon _Idle
as the Sprite.-700
so that the LaserCannon is behind the Cannons and Gun, but in front of the Laser Beam.scr_Laser_Create
, with the following code:myHealth = 50; mySpeed = 2; myBuffer = 64; action = IDLE; facing = RIGHT; canFire = false; myIdle = spr_LaserCannon _Idle; myRun = spr_LaserCannon _Run; myDamage = spr_LaserCannon _Damage;
We first set all the standard variables for the LaserCannon's health, current state, facing direction, and that it isn't shooting. We then set all the animation system variables for the three states that the LaserCannon has.
scr_LaserCannon_Step
, and add it to a Step | Step event with the following code:scr_Animation_Control(); if (image_index > image_number-1) { action = IDLE; } if (canFire) { action = RUN; alarm[0] = 5; canFire = false; } if (myHealth <= 0) { instance_destroy(); }
This should be starting to look quite familiar. We start by running the animation system script. We then check to see if the last frame of animation has played, and if so, set the LaserCannon to its idle state. Next, if the LaserCannon is to shoot, we change states and set a short alarm so that the Laser Beam is created after the shooting animation has played. Finally, we do a health check and remove the LaserCannon if it is out of health.
We aren't done with this script yet. We still need to add in the movement. When the LaserCannon is first created, it will not be moving. We don't want it to start moving until the second phase has started. After that point we want the LaserCannon to take care of the vertical motion.
scr_LaserCannon_Step
:if (y < myBuffer) { vspeed = mySpeed; } if (y > room_height - myBuffer) { vspeed = -mySpeed; }
scr_LaserCannon_Alarm0
, attached with the Laser Beam creation code:beam = instance_create(x, y, obj_LaserBeam); beam.myLaserCannon = self.id;
We create an instance of the beam right at the tip of the LaserCannon and then we set the Laser Beam's myLaserCannon
variable to be the unique ID of the LaserCannon that created it. The benefit of doing this means that we could have more than one LaserCannon in the room if we wanted.
scr_LaserCannon_Collision
, and place it into a Collision | obj_Bullet event:if (obj_Boss.isPhase_02) { myHealth -= 5; action = DAMAGE; with (other) { instance_destroy(); } }
Since we don't want the player to be able to destroy the LaserCannon before the second phase, we check what phase the Boss is currently in, to determine if damage should be applied or not. If the Boss is in the second phase, we reduce the LaserCannon's health, change it to the damage state and remove the Bullet. The LaserCannon is now complete and ready to be implemented into the Boss.
scr_Boss_Create
and insert this code before the Time Line is run:laser = instance_create(boss_X, 352, obj_LaserCannon);
tm_Boss_Phase02
.210
.scr_Phase02_210
, and assign it with the code to activate the LaserCannon:laser.canFire = true;
270
. This will give us a Laser Beam that lasts two seconds.scr_Phase02_270
, and remove the Laser Beam.with (laser.beam) { instance_destroy(); }
When the LaserCannon shoots, it creates the beam
variable which we can now use to remove it.
obj_Boss
with a new Script, scr_Boss_Step
, assigned with the following code:if (!instance_exists(obj_Cannon) && !isPhase_02) { laser.vspeed = laser.mySpeed; timeline_index = tm_Boss_Phase02; timeline_position = 0; gun.delay = 45; isPhase_02 = true; }
We start by checking if there are any instances of the Cannon remaining in the world and if they have all been destroyed, we check to see if the second phase has started. Upon the second phase starting, we set the LaserCannon into motion downwards and switch the Time Line to the new phase and reset the Time Line to the beginning. We are also going to make the challenge a bit more difficult by decreasing the delay between shots from the Gun. We end this code by changing isPhase_02
to true so that this is only executed once.
For the final stage we are not going to add another weapon, but instead we will create a destructible Boss Core that is protected by two Shields. The Shields will open every few seconds to expose the Boss Core. We will also change the Gun to shoot in quick bursts:
spr_BossCore_Idle
and spr_BossCore_Damage
. With Remove Background checked, load Chapter 5/Sprites/BossCore_Idle.gif
and Chapter 5/Sprites/BossCore_Damage.gif
to the appropriate sprite.-32
and Y: 64
so that it will be properly located behind the Shields.obj_BossCore
, and assign spr_BossCore_Idle
as the Sprite.scr_BossCore_Create
, and initialize the required variables as follows. Remember to assign this to a Create event:myHealth = 100; action = IDLE; facing = RIGHT; myIdle = spr_BossCore_Idle; myDamage = spr_BossCore_Damage;
scr_BossCore_Step
with the following code:scr_Animation_Control(); if (action == DAMAGE) { if (image_index > image_number-1) { action = IDLE; } } if (myHealth <= 0) { instance_destroy(); }
scr_BossCore_Collision
, and write the following code:if (obj_Boss.isPhase_03 && action == IDLE) { myHealth -= 2; action = DAMAGE; with (other) { instance_destroy(); } }
We first check to see if the Boss is in the final phase and that the Boss Core is in its idle state. If it is, we reduce the health and switch over to the damage animation. We also make sure that the Bullet is removed. The Boss Core is now complete and we can move onto the Shields.
spr_Shield_Upper
and spr_Shield_Lower
. Load Chapter 5/Sprites/Shield_Upper.gif
and Chapter 5/Sprites/Shield_Lower.gif
to the associated sprite. Remember to check Remove Background.spr_Shield_Upper
to X: 0
and Y: 269
so that the origin is on the bottom of the image. We don't need to change the Origin of spr_Shield_Lower
.obj_Shield_Upper
and obj_Shield_Lower
, and assign the appropriate sprites.-500
so that they are in front of the Boss Core but behind all the other parts of the Boss.scr_ShieldUpper_Create
, applied to a Create event in obj_Shield_Upper
:isShielding = true; openPosition = y-64; mySpeed = 2;
The first variable will activate whether the Shield is up or down. The second variable sets the value for how high to lift the Shield; in this case it will move up 64 pixels. Finally we set a variable for the movement speed.
scr_ShieldLower_Create
, and apply it to the Create event of obj_Shield_Lower
:isShielding = true; openPosition = y+64; mySpeed = 2;
obj_Shield_Upper
, with a new Script, scr_ShieldUpper_Step
, attached with the following code to control the shield's movement:if (isShielding && y < ystart) { y += mySpeed; } if (!isShielding && y > openPosition) { y -= mySpeed; }
We start by checking if the Shield is supposed to be down and whether it is all the way down or not. If it isn't all the way down, we move the Shield a bit down. The second if
statement does the opposite, checking to see if the Shield is supposed to be up and whether it is all the way up. If not, we lift the Shield up a bit.
scr_ShieldLower_Step
, attached to a Step | Step event in obj_Shield_Lower
:if (isShielding && y > ystart) { y -= 2; } if (!isShielding && y < openPosition) { y += 2; }
scr_Shield_Collision
, with the following code:if (obj_Boss.isPhase_03) { with (other) { instance_destroy(); } }
The Shields will never take damage, but they should only detect collision during the final phase.
scr_Boss_Create
and insert the following code after the last weapon:core = instance_create(boss_X, 272, obj_BossCore); shieldUpper = instance_create(boss_X, 272, obj_Shield_Upper); shieldLower = instance_create(boss_X, 272, obj_Shield_Lower);
We create the Boss Core and the Shields all at the same location.
tm_Boss_Phase03
, to deal with the Shields and Gun functionality.120
, and then create a new Script, scr_Phase03_120
, with the following code:shieldUpper.isShielding = false; shieldLower.isShielding = false; gun.delay = 10;
Here we are setting the Shields to open and increasing the shooting rate of the Gun.
180
and create a new Script, scr_Phase03_180
. All we are going to do here is turn off the Gun's alarm so that there is a brief respite in the shooting. This is achieved by setting the delay to -1.gun.delay = -1;
300
, and create a new Script, scr_Phase03_300
. Now we reactivate the Gun's alarm.gun.delay = 10;
360
with another new Script, scr_Phase03_360
, where we lower the Shields and return the Gun to a regular shooting rate:shieldUpper.isShielding = true; shieldLower.isShielding = true; gun.delay = 45;
scr_Boss_Step
and add the following code at the end:if (!instance_exists(obj_LaserCannon) && !isPhase_03) { timeline_index = tm_Boss_Phase03; timeline_position = 0; isPhase_03 = true; }
We check whether the LaserCannon has been destroyed and if we are supposed to be in the final phase or not. If we are, all we need to do is switch the timeline
, set it to the beginning, and set it to the final phase.
scr_Boss_Step
write the last conditional statement:if (!instance_exists(obj_BossCore) && !isBossDefeated) { timeline_running = false; with (gun) { instance_destroy(); } isBossDefeated = true; }
We check to see if the Boss Core has been destroyed and if the win condition has been called. If the Boss has been defeated, we stop the Timeline and declare the defeat.
18.218.78.102