In this lesson, we will create our first variation of the existing turret: one that shoots at all mobs in range simultaneously.
Create a new script by right-clicking on the
TowerDefense/
directory and selecting New
Script.
Name it TurretMultiShot.gd
, then click
Create.
Double-click the new file in the FileSystem dock to open the script and erase everything in it. Once it’s completely empty, write this single line at the top:
extends "Turret.gd"
This script now extends the code in the Turret.gd
file
and can do everything our Turret can do.
Be careful; the quotes ""
and the .gd
extension are both mandatory.
When you extend, for example, an extends Area2D
,
without quotes or file extension.
That’s because
is a type built into the Godot engine. When you extend your own scripts, you have to specify a valid file path.You may use a relative path like
extends "../../SomeScript.gd"
or an absolute path like
extends "res://SomeDirectory/SomeScript.gd"
.
Because the Turret script is inside the same directory as
this script, we can write extend "Turret.gd"
.
Since our Turret already stores multiple targets, all we have to do is shoot at all of them at once.
We need to change the _on_Timer_timeout()
function, as
this is when the turret shoots.
When we extend a script, if we write a new function that has the same name as an existing function, we overwrite its functionality.
We say that we override the function. Let’s
override _on_Timer_timeout()
.
In TurretMultiShot.gd
, write:
func _on_Timer_timeout() -> void:
pass
Now, the new turret will not shoot every second. This is because our
new function, which does nothing, replaces the parent
Turret.gd
script’s _on_Timer_timeout()
function.
We want the new turret’s attack to work differently from before:
transform
property. Instead, we have to calculate the angle
to each mob.To shoot at every target, we need a loop. In each loop iteration, we create a rocket and add it to the stage:
func _on_Timer_timeout() -> void:
for target in target_list:
var rocket := preload("common/Rocket.tscn").instance()
add_child(rocket)
Then, we need to find the angle from the mouth of the cannon to the mob itself.
We already know how to get the angle to a mob. We’ve done it before for the Turret:
var target_angle: float = target.global_position.angle_to_point(global_position)
We need to change the calculation slightly to get the angle to the
cannon
because some rockets will not align with the turret.
Switch global_position
to
cannon.global_position
.
func _on_Timer_timeout() -> void:
for target in target_list:
# ...
= target.global_position.angle_to_point(cannon.global_position) rocket.rotation
Finally, we place the rocket at the cannon’s tip.
func _on_Timer_timeout() -> void:
for target in target_list:
# ...
= cannon.global_position rocket.global_position
Drag the script to the second turret to test it.
Run the scene and drag mobs in range of the turret. You’ll see that it shoots at multiple targets simultaneously!
We could stop here, but we can make it a little better.
At the moment, the turret only turns towards the first target. It would look nicer if it aimed at the midpoint between all the targets.
To calculate the average between multiple points, we add all the points, then divide by the number of points.
Let’s override the _rotate_to_target()
function.
Like before, we define a target_angle
with a default
value, but if we have targets, we calculate the targets’ average
position and the angle to that point.
func _rotate_to_target() -> void:
var target_angle := PI / 2
if target_list:
# We define a variable to calculate the targets' average position.
var average_position := Vector2.ZERO
# We loop over all targets and sum their positions.
for target in target_list:
+= target.global_position
average_position # We divide by the number of positions. That's our average position.
/= target_list.size()
average_position # We then calculate the angle to that point like before.
= average_position.angle_to_point(global_position) target_angle
We can now rotate the turret’s sprite accordingly.
func _rotate_to_target() -> void:
# ...
= lerp_angle(sprite.rotation, target_angle, rotation_factor) sprite.rotation
If you run the scene now you should see the turret aim at the average of the targets in range.
As you can see, a few code changes can produce drastically different results.
In the next lesson, we’ll code a turret that aims at the target with the lowest health.
Here’s the complete code for TurretMultiShot.gd
.
extends "Turret.gd"
func _rotate_to_target() -> void:
var target_angle := PI / 2
if target_list:
# We define a variable to calculate the targets' average position.
var average_position := Vector2.ZERO
# We loop over all targets and sum their positions.
for target in target_list:
+= target.global_position
average_position # We divide by the number of positions. That's our average position.
/= target_list.size()
average_position # We then calculate the angle to that point like before.
= average_position.angle_to_point(global_position)
target_angle = lerp_angle(sprite.rotation, target_angle, rotation_factor)
sprite.rotation
func _on_Timer_timeout() -> void:
for target in target_list:
var rocket := preload("common/Rocket.tscn").instance()
add_child(rocket)
= target.global_position.angle_to_point(cannon.global_position)
rocket.rotation = cannon.global_position rocket.global_position