04.targetting-multiple-enemies-with-the-turret

Making the turret change target

In the previous lessons, we created a turret, but each new mob it saw “erased” any memory of a previous mob.

Also, the turret fires continuously even if it doesn’t have a target in range.

We don’t want that to happen! In this lesson, we will not only fix those issues, but also set the foundations to create multiple kinds of towers based on our turret’s script.

The first problem is that we keep track of only one target, even if there are multiple targets in the tower’s range. We need to track them all.

To track a list of things, we need an Array.

Let’s define a new target_list array that we will use to store new targets.

var target_list := []

Adding and removing from the array

We can add new items to our target_list array by calling target_list.append(new_item). In this case, we’ll append the body that enters our collision area.

Update the _on_body_entered() function’s code to match the listing below.

func _on_body_entered(body: PhysicsBody2D) -> void:
    # We add the body to our target list.
    target_list.append(body)
    # We update the target variable to target the first element in the
    # target_list array.
    #
    # Since we just appended the body to the array, we know that there's at
    # least one target in the list, so we can safely get the array's first
    # item.
    target = target_list[0]

Now let’s look at how to remove targets from the target_list array.

To remove a specific item from an array, we must specify its index. If you don’t know the index of an item, removing it takes two steps:

  1. We first call Array.find() to find the index of the item.
  2. We remove the item using the index.

For example, in the array ["a", "b", "c", "d"], if we wanted to remove the string "c" without knowing its index, we could write:

var array := ["a", "b", "c", "d"]
# The variable will have a value of 2.
var index := array.find("c")
# After this, the array will be ["a", "b", "d"]
array.remove(index)

If you know an item’s index in advance, you should skip the call to Array.find(). The example above is just to help you picture how you can use Array.find() and Array.remove() together.

Note: If Array.find() does not find the item, it returns -1. You can use a conditional block to ensure that the function found the desired item. In our case, there is no logical way for the body to be absent from the array, so we do not need to check for that condition.

Rewrite _on_body_exited() with the following code to remove targets that leave the turret’s area from the target_list.

func _on_body_exited(body: PhysicsBody2D) -> void:
    var index := target_list.find(body)
    target_list.remove(index)

Lastly, we should change the target.

func _on_body_exited(body: PhysicsBody2D) -> void:
    # ...
    if target_list:
        # The body that left the area may be the current target, which is why we
        # must update the target if the array isn't empty.
        target = target_list[0]
    else:
        target = null

Preventing the turret from shooting when it shouldn’t

Finally, we prevent the turret from firing a rocket if it does not have any target. We add a condition in the _on_Timer_timeout() function.

func _on_Timer_timeout() -> void:
    # If there are no targets in the target_list array, we stop the function and
    # skip firing a rocket.
    if not target_list:
        return
    # ...

The line if not target_list means “if the list is empty.” In that case, we return from the function. Now, if there are no enemies in sight, we will stop shooting.

And voila! Your turret’s bugs are now fixed. It will change target when its current target leaves the area.

To recap, the only differences with the previous script until now are:

We could alter the turret’s behavior with only a few changes.

In the next lesson, we’ll rework our turret’s code to build new kinds of turrets derived from this base.

The code

Here’s the updated code for Turret.gd.

extends Area2D

export(float, 0.01, 1.0) var rotation_factor := 0.1

var target: PhysicsBody2D = null
var target_list := []

onready var sprite := $Sprite
onready var timer := $Timer
onready var cannon := $Sprite/Position2D


func _ready() -> void:
    connect("body_entered", self, "_on_body_entered")
    connect("body_exited", self, "_on_body_exited")
    timer.connect("timeout", self, "_on_Timer_timeout")

func _physics_process(_delta: float) -> void:
    var target_angle := PI / 2
    if target:
        target_angle = target.global_position.angle_to_point(global_position)
    sprite.rotation = lerp_angle(sprite.rotation, target_angle, rotation_factor)

func _on_body_entered(body: PhysicsBody2D) -> void:
    # We add the body to our target list.
    target_list.append(body)
    # We update the target variable to target the first element in the
    # target_list array.
    #
    # Since we just appended the body to the array, we know that there's at
    # least one target in the list, so we can safely get the array's first
    # item.
    target = target_list[0]

func _on_body_exited(body: PhysicsBody2D) -> void:
    var index := target_list.find(body)
    target_list.remove(index)
    if target_list:
        # The body that left the area may be the current target, which is why we
        # must update the target if the array isn't empty.
        target = target_list[0]
    else:
        target = null

func _on_Timer_timeout() -> void:
    # If there are no targets in the target_list array, we stop the function and
    # skip firing a rocket.
    if not target_list:
        return
    var rocket := preload("common/Rocket.tscn").instance()
    add_child(rocket)
    rocket.global_transform = cannon.global_transform