07.creating-a-bat-that-chases-the-mouse

Creating a bat that chases the mouse

In this lesson, we’ll add another hazard for the player to deal with: the terrifying cave bat.

We’ll add features as we move through the lessons as we did with the robot character. In the next three lessons, we’ll progressively enhance the bat’s functionality:

  1. First, we’ll create the bat scene with basic steering movement toward the mouse cursor.
  2. Then, the bat will detect the robot and move toward it instead of the mouse cursor.
  3. We’ll allow the robot to hide behind walls or platforms behind terrain. The bat won’t chase the robot if it can’t “see” it.
  4. Finally, we’ll add a basic kill mechanic when the bat touches the robot.

If the robot is near the bat, the bat will chase it away unless the player hides. Otherwise, the bat will return to the ceiling of the cave.

The bat will be a KinematicBody2D too, just like the robot. This is because we want the bat to interact with other physics bodies like walls and the player.

Rather than take input from the keyboard like the robot, we’ll control the bat’s movement with calculations in the _physics_process() function. Its movement will depend on what it can see and if the robot is near.

Let’s get creating!

Creating the bat scene

Create a new scene with a KinematicBody2D as the root and name it Bat.

Find the BatSkin scene inside the BatSkin/ directory and create an instance of the scene.

Like the robot’s skin, the BatSkin will automatically choose the right animation depending on what the bat is doing. You’re encouraged to look around the BatSkin.tscn scene to see how it works, but we created this to save some time, so we won’t go over it in detail.

Our KinematicBody2D needs a CollsionShape2D. Please give it a circular shape.

Save the scene in the SideScroller/ directory, attach a script to the Bat node, and we’ll get coding.

Moving the Bat

We’ll return to basic steering logic for the bat. Let’s look at the _physics_process() before breaking it down. You may recognize much of the logic from one of the earlier series: To Space and Beyond.

func _physics_process(delta: float) -> void:
    # We start by calculating the bat's movement direction.
    var direction := Vector2.UP
    var relative_cursor_position := get_local_mouse_position()
    # If the distance to the mouse is less than 500 pixels,
    if relative_cursor_position.length() < 500:
        # We convert the mouse's local position into a direction vector by
        # normalizing the position.
        direction = relative_cursor_position.normalized()

The bat is always in one of two states:

The direction starts facing upwards by default. We get the mouse cursor position relative to the bat’s position using get_local_mouse_position(). If the length is less than 500 pixels, the bat is close enough to move toward the cursor.

Below, the yellow line shows the cursor’s position relative to the bat, and the pink shows the mouse cursor’s global position (relative to the game world’s origin).

The normalized() function shortens the relative_cursor_position to a length of 1. We do this because the vector would scale the velocity too much without that, and our bat would fly around very fast!

Steering the bat

It’s time for steering!

At the top of the script, add the necessary properties:

export var max_speed := 450.0
export var drag_factor := 0.1

var velocity := Vector2.ZERO

And add this in the _physics_process() function. This is code you’ve seen before, but here is a reminder.

func _physics_process(delta: float) -> void:
    # ...
    var desired_velocity := max_speed * direction
    var steering_vector := desired_velocity - velocity
    velocity += steering_vector * drag_factor

    velocity = move_and_slide(velocity, Vector2.DOWN)

The desired_velocity is the most efficient velocity to reach the target. It’s either straight upwards at maximum speed or in the direction of the mouse cursor if it’s near.

The steering_vector is the difference between the desired_velocity and the current velocity this frame.

If we added steering_vector to velocity, the bat would instantly move toward the desired position. Instead, we use the drag_factor value to change the bat’s direction gradually.

Because the drag_factor is very low, the bat is slow to turn and accelerate. Increasing the drag_factor will make the bat more reactive.

We often use low values for enemies to give the player enough time to escape or avoid them.

You can now add the bat to the Level scene to play around with it yourself. Make sure to draw a ceiling with the TileMap, so it has a place to rest!

We’ve made the first steps to make the enemy, but there’s much to do! In the next lesson, we’ll make the bat target the player instead of the mouse.

The Code

Here’s the current code for the bat so far.

extends KinematicBody2D

export var max_speed := 450.0
export var drag_factor := 0.1

var velocity := Vector2.ZERO

func _physics_process(delta: float) -> void:
    # We start by calculating the bat's movement direction.
    var direction := Vector2.UP
    var relative_cursor_position := get_local_mouse_position()
    # If the distance to the mouse is less than 500 pixels,
    if relative_cursor_position.length() < 500:
        # We convert the mouse's local position into a direction vector by
        # normalizing the position.
        direction = relative_cursor_position.normalized()

    var desired_velocity := max_speed * direction
    var steering_vector := desired_velocity - velocity
    velocity += steering_vector * drag_factor

    velocity = move_and_slide(velocity, Vector2.DOWN)