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
.Let’s define a new target_list
array that we will use to
store new targets.
var target_list := []
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.
append(body)
target_list.# 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_list[0] target
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:
Array.find()
to find the index of the
item.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"]
remove(index) array.
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)
remove(index) target_list.
Lastly, we should change the target
.
target
to
null
.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_list[0]
target else:
= null target
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:
target_list
array._on_Timer_timeout
to stop
shooting when the list is empty.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.
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")
connect("timeout", self, "_on_Timer_timeout")
timer.
func _physics_process(_delta: float) -> void:
var target_angle := PI / 2
if target:
= target.global_position.angle_to_point(global_position)
target_angle = lerp_angle(sprite.rotation, target_angle, rotation_factor)
sprite.rotation
func _on_body_entered(body: PhysicsBody2D) -> void:
# We add the body to our target list.
append(body)
target_list.# 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_list[0]
target
func _on_body_exited(body: PhysicsBody2D) -> void:
var index := target_list.find(body)
remove(index)
target_list.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_list[0]
target else:
= null
target
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)
= cannon.global_transform rocket.global_transform