11.coding-the-first-mob

Coding the first mob

In this lesson, we will guide you through the process of creating mobs for your game.

Here’s what we will do:

  1. First, we’ll look at the Mob scene and code.
  2. Then, we’ll create a flying shield mob that shoots at the robot from afar.

The mob system is the most complex part of this project, so we will take the time to create the flying shield together, step-by-step.

First, let’s explore the provided Mob scene and script.

Exploring the mob scene

Open the file res://mobs/Mob.tscn.

Prod around, and try to guess what each node is for.

The names and types of nodes should be enough to give you an idea of their purpose, but in case there’s a doubt, here’s a rundown of the scene.

Let’s start with the two areas:

The CollisionShape2D of each area should be unique to each mob. Some mobs will detect the robot from very far away, and others will need to be close. Some will attack from far away, and some will only attack when the robot is next to them.

Let’s now go over the other nodes:

Another scene to know is res://mobs/Cannon.tscn and its accompanying script, res://mobs/Cannon.gd. Open them, and take a look. You should be familiar with that type of code already.

The Cannon is just a Sprite with a Position2D. It receives a bullet scene and can then fire it when needed. It’s supposed to be attached to mobs and allows them to fire.

Note: The Sprite has no texture. We could use a Node2D instead. But using a Sprite is convenient as we can assign a texture to see it and make sure it rotates correctly when debugging. We might also actually assign a cannon texture in a later mob.

The code is similar to what you’ve programmed for the turrets in the tower defense series. We use the look_at() function to turn towards a target, and then some code instantiates a bullet and orients it in the proper direction.

Let’s now look at the mob code.

Exploring the mob code

Open res://mobs/Mob.gd.

Look at the left of the script, at the Outliner. This will give you a summary of all the functions available in this script.

Please take a moment to read the script yourself and try to see how much you understand.

Don’t be intimidated by the script’s length. We wrote it piece by piece; not all at once! You should also read it bit by bit without letting the size intimidate you.

The general way the script works is as follows:

  1. The _target variable will update to point at the player’s robot. When the robot enters the DetectionArea, _target is set, and the Active sprite becomes visible.
  2. The _target_within_range is a boolean that becomes true when the robot is within attack range (inside the AttackArea).
  3. When a bullet hits the mob, take_damage() is called, which plays an animation and removes health. If no health remains, _die() is called, which plays another animation, and removes the mob from the scene.

There’s no other default behavior. The rest is up to inherited mob scripts to implement. By default, a mob doesn’t attack or do anything.

But the mob script also offers a few convenient functions to be used in your mobs:

Implementing the shield

Let’s now inherit the mob scene and script to code the shield. We’ll go step by step through creating this first mob, as this is undoubtedly the most challenging part of the project.

You will then have the opportunity to create new mobs by yourself.

Our first enemy will fly around the robot and shoot from time to time.

Right-click Mob.tscn, and select New Inherited Scene. Name the top node Shield, and save the scene inside the res://mobs/shield/ directory.

Then, drag res://mobs/shield/shield_inactive.png to the Sprite, and res://mobs/shield/shield_active.png to the Sprite/Active node.

Drag the Cannon.tscn scene to the Shield root node.

Pick a bullet scene to feed the Cannon. Drag it to its Bullet Scene property.

Finally, extend the script.

Name the script Shield.gd and open it.

As usual, we create a reference for the nodes we will need:

onready var _cannon := $Cannon

We write a small function that starts the winding-up timer. Without the timer, the mob would shoot as soon as the robot entered the attack area. That doesn’t look too good. Instead, we want the mob to orbit the player a bit first.

func _prepare_to_attack() -> void:
    if not is_ready_to_attack():
        return
    _before_attack_timer.start()

We can call this function on each frame. Remember that is_ready_to_attack() will return false if BeforeAttackTimer is started. Therefore, every call to this function after the first will return without doing anything.

Once the timer finishes counting, it runs _on_BeforeAttackTimer_timeout(). This function will perform the attack:

# The wind-up timer has finished, we can attack.
func _on_BeforeAttackTimer_timeout() -> void:
    # The target might have exited the range while the timeout was running, so
    # we check again
    if not _target:
        return
    # Finally, we shoot.
    _cannon.shoot_at_target(_target)
    _cooldown_timer.start()

After those two utility functions, we finally approach _physics_process(), where most of the code will go. First, we check that there is a target. If there isn’t, the robot hasn’t entered DetectArea, so we do nothing.

func _physics_process(delta: float) -> void:
    if not _target:
            return

If there is a target, we turn the cannon towards it and check if the robot has entered the AttackArea. If so, we orbit around the robot and we start the attack.

func _physics_process(delta: float) -> void:
    #...
    _cannon.look_at(_target.global_position)

    if _target_within_range:
            orbit_target()
            # We can call this on every frame, it's not a problem; if the
            # _before_attack_timer is already started, nothing will happen (on most
            # frames, this function does nothing)
            _prepare_to_attack()

If there is a target that isn’t within attack range, the robot is still within DetectArea but too far for orbiting or attacking. Then, we follow the robot:

func _physics_process(delta: float) -> void:
    #...
    else:
            follow(_target.global_position)

Here’s the breakdown of what happens when the robot enters the AttackArea:

  1. On the first frame after the robot enters, _prepare_to_attack() will be called. It will run BeforeAttackTimer
  2. Every frame after that, _prepare_to_attack() will be called, but because BeforeAttackTimer started, the function will do nothing.
  3. After BeforeAttackTimer has timed out, CoolDownTimer will start.
  4. Every frame after that, _prepare_to_attack() will also be called, but because CoolDownTimer is started, the function will still do nothing.
  5. After CoolDownTimer has timed out, if the robot is still in range, we return to step 1.

That’s it! You’ve made your first mob. Open TestRoom.tscn, and drop the mob there. Run the scene to see the shield follow, orbit around the robot, and shoot at the player.

Playing with the mob’s properties

Before we challenge you to make more mobs, please take a time to tweak the shield’s properties in the Inspector and try see what feels right.

By doing this, you are shifting from code to game design. Tweaking the parameters of your game allows you to balance it and is part of the many tasks that game designers handle.

Here are some properties you can tweak:

These paramaters already allow you to change a lot about how the shield feels to play against. But you can do much more!

Challenge: Can you make a shield that never forgets the robot? (hint: override _on_DetectionArea_body_exited)

Challenge: Can you make a shield that shoots bullets in a cone?

Challenge: Can you make a shield that doesn’t see the robot when the robot is behind a wall? (hint: you will need to use Raycast2D)

Other mobs

Now that you have your first mob, you can build other ones.

We have prepared a few sprites and assets for you to do so.

Creating the bomb mob

In the directory mobs/bomb/, you will find a Bomb.tscn scene.

Additionally to the normal Mob nodes, the bomb also has:

  1. An Area2D named ShockArea. This represents the Bomb’s explosion radius and should hurt the robot when the robot enters it.

  2. Two new animations:

    1. “will_explode”: plays a little wobbly animation to indicate it’s about to go kaboom.
    2. “explode”: plays an explosion animation and, importantly, grows the ShockArea for a few frames.

We want to extend Mob.gd with those behaviors:

  1. If the robot enters AttackArea, we play the “will_explode” animation.
  2. When the “will_explode” animation finishes playing, we want to play the “explode” animation.
  3. When the “explode” animation finishes playing, we want to call queue_free().
  4. If the robot exits AttackArea, we want to play RESET, then the “hover” animation.
  5. If the robot enters ShockArea, then it should take damage. Remember, ShockArea’s size is 0, unless the “explode” animation is playing.

To help you a bit, here’s the function _on_AnimationPlayer_animation_finished().

Careful! The AnimationPlayer isn’t connected to it, you have to connect its animation_finished signal yourself.

func _on_AnimationPlayer_animation_finished(anim_name: String) -> void:
    if anim_name == "will_explode":
        _disable()
        _die_sound.play()
        _animation_player.play("explode")
    elif anim_name == "explode":
        queue_free()
    elif anim_name == "RESET":
        _animation_player.play("hover")

If you’re stuck, feel free to check out the bomb’s code files in the finished game project. They contain a functional bomb mob.

Challenge: Can you make a bomb that slows the player when the player enters the field?

Challenge: Can you make a bomb that can also shoot bullets?

The sword mob

If you open mobs/sword/, you’ll find two sword images. You’ll have to create an inherited Mob scene and assign them to the mob sprites.

Here’s the functionality we want for the sword: when the robot enters the AttackArea, the sword waits a bit, then lunges towards the player.

That’s it!

Bonus points if:

  1. The sword freezes a bit before lunging, to telegraph the attack.
  2. The sword picks the location of the player before attacking, and then doesn’t change it (so it can miss the player).
  3. The sword rotates to face the player constantly.

Hint: You will need an Area2D to damage the robot. We usually call this kind of area a Hurtbox.

The sword is very hard to get perfectly right, so don’t be disappointed if you don’t manage to code it. We even had a few bugs ourselves while writing it!

Still, it’s a good challenge and even if you don’t nail it, an excellent practice.