#Setting Up Homing Missile Target

36 messages · Page 1 of 1 (latest)

pure bridge
#

Anyone know how to make a 2D homing projectile? I've kind of got one working, but it only works when I directly place the projectile in the actual scene with the player. I'm trying to get it to home in on the player after spawning.

#
@export var target: Node2D = null

func _ready():
    call_deferred("seeker_setup")

func seeker_setup():
    await get_tree().physics_frame
    if target:
        navigation_agent_2D.target_position = target.global_position

func movement(delta):
    var current_agent_position = global_position
    var next_path_position = navigation_agent_2D.get_next_path_position()
    
    if target:
        navigation_agent_2D.target_position = target.global_position
    
    if navigation_agent_2D.is_navigation_finished():
        return
    
    velocity = current_agent_position.direction_to(next_path_position) * turn_speed * delta```
#

It works as it should in the scene because I can link it directly in the level scene

#

I also want it to only track on the x axis, but I would need to solve this issue first

#

Thanks in advance

latent crest
#

The approach I took in my last game was to start by setting the projectile's velocity so it is moving towards the target's current position. I then turned the projectile to track the target in the _physics_process with code like this:

  var offset_angle := velocity.angle_to(target_node.global_position - global_position)
  if abs(offset_angle) < MAX_OFFSET_ANGLE:
    velocity = velocity.rotated(offset_angle)

MAX_OFFSET_ANGLE is the maximum angular deviation from the protectile's current trajectory to the target that will cause the projectile to turn towards the projectile. I didn't want the homing to be too good or it makes it impossible for the player to evade! I was using a value of 0.05.
Another way to turn the projectile would be to use move_toward which I think would be something like this, although I haven't actually tried it:

  velocity = velocity.move_toward(target_node.global_position - global_position, TURN_SPEED * delta)
pure bridge
latent crest
#

In my game only the enemies fired homing missiles so they were always tracking the player.
However it should be easy to pass a reference to the object that is to be tracked to the instantiated projectile. Take out the @export from the target variable declairation and then in the code where you instantiate the projectile, after instantiating it but before you add it to the scene tree, directly set the target variable to be a reference to the target object.

pure bridge
#

So, I have a node that instantiates the missiles at the top of the screen that looks like this

    var e = enemies.pick_random().instantiate()
    e.global_position = Vector2(randf_range(50, 490), -50)
    enemy_container.add_child(e)```
#

I would set the target here?

#

I'm confused how I would set the missile's target from this node

strange sorrel
#

You would have the missile pick the target when the missile is instantiated. Perhaps by calculating the closest enemy and choosing that one.

#

Or are you wondering how to make this connection in the first place?

pure bridge
#

yeah, I don't know how to go about doing this process

#

I have an area 2D set up in front of the missile to detect the player, but that's about it

strange sorrel
#

So your missile would have a var target: Entity.
Every frame the missile moves toward the target. So far so good.
There's a few ways for the missile to get the reference to the target. I'm not a fan of Autoloads but that's the easiest way to do it (It's also not a terribly egregious use of one in this case). If your world has an enemies node, and all your enemies are children of that node, you can go though that list and select the one you want. You can do it with signals as well but I don't think there's much advantage to that method.

latent crest
#

After instantiating the missile you can set the target directly with something like:

  e.target = get_node("/root/Player")

Replacing the path with the correct path to reach your player object. It would be most efficient to store the reference to the player object in a variable in an autoload and then reference it from there instead of searching for the player node each time though.

strange sorrel
#

Oh, my bad. I thought it was suppose to home to an enemy

latent crest
#

Actually, instead of using an autoload you could just put a target variable in the script that spawns the missiles, set it to the player in that script's _ready function and then use that when setting the target for each missile.

pure bridge
#

how would I signal to the missile script that the target has been set from there?

latent crest
#

You don't need to send a signal. You would be setting the missile's target directly with:

e.target = get_node("/root/Player")
pure bridge
#

it still comes back as a null_instance

#

I tried it moving the @export var target: Node2D = null into this node as well and tried it that way, but with the same result

#

the missile is preloaded in this script as well

#

this is what the entire script looks like ```extends Node2D

@export var enemies: Array[PackedScene] = []
@onready var enemy_container: Node2D = $EnemyContainer
@onready var player: CharacterBody2D = $Player
@onready var spawn: Marker2D = $Spawn

var missile = preload("res://Level/Scenes/Missile.tscn")
#@export var target: Node2D = null

Called when the node enters the scene tree for the first time.

func _ready():
player.position = spawn.global_position

Called every frame. 'delta' is the elapsed time since the previous frame.

func _process(delta):
pass

func _on_enemy_spawn_timer_timeout():
var e = enemies.pick_random().instantiate()
missile.target = get_node("res://Player/Scenes/Player.tscn")
e.global_position = Vector2(randf_range(50, 490), -50)
enemy_container.add_child(e)```

latent crest
#

Take out missile.target = get_node("res://Player/Scenes/Player.tscn") and replace with e.target = player

pure bridge
#

That worked, thank you.

#

I have to make a separate spawner just for the missiles since the other enemies do not have targeting

#

but also track on the y axis which I do not want

#

idk if you know how to do that

latent crest
#

You could add a target variable to the other enemies scripts but just not do anything with it.

pure bridge
#

that is also true, I didn't even think of that

#

well, thanks again for the help!

latent crest
#

No problem, good luck with your game!

pure bridge
#

you too