extends Node2D const PROJECTILE := preload("res://scenes/game/weapon_projectile.tscn") func _ready() -> void: seed(1234) # Both stationary and crossing targets should be intercepted on the first pass. for target_velocity in [Vector2.ZERO, Vector2(0, 55)]: var target := CharacterBody2D.new() target.position = Vector2(350, 70) target.velocity = target_velocity add_child(target) var missile := _missile(target, 1200.0) var closest := INF for step in 240: var previous: Vector2 = missile.position target.position += target_velocity / 120.0 missile._physics_process(1.0 / 120.0) var near := Geometry2D.get_closest_point_to_segment(target.position, previous, missile.position) closest = minf(closest, near.distance_to(target.position)) assert(absf(missile._turn_velocity) <= missile.homing_max_turn_speed + 0.001) if closest < 18.0 or missile.is_queued_for_deletion(): break assert(closest < 18.0, "Missile missed a reachable intercept: %.2f" % closest) missile.free() target.free() # Compare actual flight direction against the previous turn limits. var side_target := Node2D.new() side_target.position = Vector2(100, 200) add_child(side_target) var faster := _missile(side_target, 600.0) var previous := _missile(side_target, 600.0) previous.homing_turn_acceleration = 2.3 previous.homing_max_turn_speed = 1.7 previous.missile_steering_acceleration = 520.0 for step in 48: faster._physics_process(1.0 / 120.0) previous._physics_process(1.0 / 120.0) assert(faster._heading > previous._heading * 1.5) assert(faster._flight_velocity.angle() > previous._flight_velocity.angle() * 1.5) assert(faster.homing_target == side_target) faster.free() previous.free() side_target.free() # Preserve the original pursuit behavior beyond acquisition range. var far_target := Node2D.new() far_target.position = Vector2(5000, 0) far_target.add_to_group("enemies") add_child(far_target) var limited := _missile(far_target, 200.0) limited._has_locked_target = true for step in 120: limited._physics_process(1.0 / 120.0) if limited.is_queued_for_deletion(): break assert(not limited.is_queued_for_deletion() and limited.homing_target == far_target) assert(limited._has_locked_target) limited.free() far_target.free() print("MISSILE_GUIDANCE_TEST: PASS (stationary and moving intercepts, faster heading and actual flight turn, original target pursuit)") get_tree().quit() func _missile(target: Node2D, flight_range: float) -> Area2D: var missile := PROJECTILE.instantiate() as Area2D missile.configure(Vector2.ZERO, Vector2.RIGHT, ItemDefinition.WeaponKind.MISSILE, 400.0, 10.0, flight_range, target) missile.collision_mask = 0 add_child(missile) missile.set_physics_process(false) missile.set_process(false) return missile