Automatic Aiming
If you’ve played a survivors-like game before you’ll quickly realize what’s missing. The bullets move in the same direction as you move.
This is bad for a few reasons. If you stand still the bullets don’t fire off. It’s awkward and counter-intuitive to move towards your enemies to fire at them. Also, it’s not in the survivors-like style.
Normally, weapons automatically fire at the closest enemy.
There are a couple ways we could do this.
First, we could put the enemy body in a group called “enemy”. Then, we’d use a for loop and loop through all the enemies in get_tree().get_nodes_in_group()
and compare them all to find the closest one.
However, that’ll get very slow when you have a lot of enemies. Since you have to go through potentially hundreds of enemies every time you spawn a bullet, which will be happening pretty often if rapid-fire weapons are introduced.
The best way around this is to limit your calculations to a smaller area. This also provides the benefit of not firing at a random enemy a long distance away for no reason.
-
Add an Area2D node with a collider, and make it a circle that’s a bit wider than the camera.
-
Get a reference to that Area2D in your projectile spawner. Before instantiating the projectile add:
var closest_enemy: Enemyfor body in fire_radius.get_overlapping_bodies():if body is Enemy:if is_instance_valid(closest_enemy):if player.global_position.distance_squared_to(body.global_position) < player.global_position.distance_squared_to(closest_enemy.global_position):closest_enemy = bodyelse:closest_enemy = bodyif not is_instance_valid(closest_enemy):return -
Let’s break this down.
We have a
closest_enemy
variable which will be an object of typeEnemy
, this assumes you have named your enemy classEnemy
.We use that and loop through all the overlapping bodies in our Area2D, this assumes the name of your Area2D variable is
fire_radius
. If the body that we’re looping over is not an enemy, don’t do anything with it. It might be a wall and we don’t want to target walls.We need to find the closest enemy. We can’t check if the currently looped enemy is closer than
closest_enemy
ifclosest_enemy
isn’t set to any enemy at all. So ifclosest_enemy
isn’t a valid instance we just set it to that currently looped enemy. If we only have a single overlapping enemy this is where the loop would end.On the next loop, if there are multiple enemies in the area we next need to check if that second looped enemy is closer to the player than
closest_enemy
or not. If it is, it becomes the newclosest_enemy
. We usedistance_squared_to
instead ofdistance_to
because it’s a lot faster.Finally, check if we found an enemy at all. If not,
return
and stop the rest of the function that spawns a bullet. -
Next, after this block of code, and after you instantiate the bullet, you need to replace
new_projectile.direction = player.velocity.normalized()
withnew_projectile.direction = player.global_position.direction_to(closest_enemy.global_position)
. This will finally set the direction to the enemy.direction_to
returns a normalized vector already, so we don’t need to usenormalized
on it.
Play your mystical game after modifying the names of variables to your specific script. If all is well, this will work.
Multiple weapons
Section titled Multiple weaponsHaving many weapons means you’ll have a lot of objects you want to change all at once without it being a constant tedious process.
You might end up having over a hundred types of weapons. Even if you only have 2, it’s easier to make changes once more than twice. Plus, it lets us demonstrate high level programming concepts like abstraction. Showing off is always fun.
Also, we’re going to need something to see if the player has met the requirements for a weapon.
This could be anything from seeing if the player has picked up a weapon, to if they spent money gained from slaying enemies on it.
We’ll just be using a simple score counter to see how many enemies the player has defeated.