Проблема
Вам нужна «самонаводящаяся ракета» — снаряд, который будет преследовать движущуюся цель.
Решение
В этом примере мы используем узел Area2D для снаряда. Области (Area) обычно хорошо подходят для пуль, так как нам нужно определять, когда они сталкиваются с чем-либо. Если вам также нужна пуля, которая может отскакивать/рикошетить, лучше выбрать узел типа PhysicsBody.
Настройка узла и поведение ракеты такие же, как и для «обычной» пули. Если вы создаете много типов пуль, вы можете использовать наследование, чтобы все ваши снаряды основывались на одной базовой настройке.
Узлы, которые мы будем использовать:
Area2D: Missile
Sprite2D
CollisionShape2D
Timer: Lifetime
Для текстуры вы можете использовать любое изображение, которое вам нравится. Вот пример:


Настройте узлы и задайте текстуру для спрайта, а также форму для столкновений. Убедитесь, что вы повернули узел Sprite2D на 90°, чтобы он был направлен вправо, совпадая с «направлением вперед» родительского узла.
Добавьте скрипт и подключите сигнал body_entered
узла Area2D и сигнал timeout
узла Timer.
Вот начальный скрипт:
extends Area2D
export var speed = 350
var velocity = Vector2.ZERO
var acceleration = Vector2.ZERO
func start(_transform):
global_transform = _transform
velocity = transform.x * speed
func _physics_process(delta):
velocity += acceleration * delta
velocity = velocity.clamped(speed)
rotation = velocity.angle()
position += velocity * delta
func _on_Missile_body_entered(body):
queue_free()
func _on_Lifetime_timeout():
queue_free()
Это создает «обычную» ракету, которая движется по прямой линии после выстрела. Чтобы использовать этот снаряд, создайте его экземпляр и вызовите метод start()
, передав нужный Transform2D
, чтобы задать его позицию и направление.
Проверь свои знания в нашем бесплатном ТЕСТЕ по Godot! Узнай, насколько хорошо ты его знаешь!
Для получения дополнительной информации см. раздел с похожими рецептами ниже.
Чтобы изменить поведение на преследование цели, мы будем использовать ускорение. Однако мы не хотим, чтобы ракета могла «разворачиваться на месте», поэтому добавим переменную для управления «силой поворота». Это задаст радиус поворота ракеты, который можно настроить для разных типов поведения. Также нам понадобится переменная для цели, чтобы ракета знала, за чем ей нужно следовать. Мы зададим это в методе start()
:
export var steer_force = 50.0
var target = null
func start(_transform, _target):
target = _target
...
Чтобы изменить направление ракеты для движения к цели, ей нужно ускоряться в этом направлении (ускорение — это изменение скорости). Ракета «хочет» двигаться прямо к цели, но её текущая скорость направлена в другую сторону. Используя немного векторной математики, мы можем найти эту разницу:


Зеленая стрелка показывает необходимое изменение скорости (то есть ускорение). Однако если ракета будет поворачивать мгновенно, это будет выглядеть неестественно, поэтому длину вектора «поворота» нужно ограничить. Для этого используется переменная steer_force
.
Вот функция для расчета этого ускорения. Обратите внимание, что если цель отсутствует, поворот не будет происходить, и ракета продолжит движение по прямой линии.
func seek():
var steer = Vector2.ZERO
if target:
var desired = (target.position - position).normalized() * speed
steer = (desired - velocity).normalized() * steer_force
return steer
Наконец, полученная сила поворота должна быть применена в методе _physics_process()
:
func _physics_process(delta):
acceleration += seek()
velocity += acceleration * delta
velocity = velocity.clamped(speed)
rotation = velocity.angle()
position += velocity * delta
Вот пример результата с небольшими визуальными эффектами, такими как дым от частиц и взрывы:
Вот полный скрипт, включая описанные выше эффекты. Подробности см. в связанных рецептах.
extends Area2D
export var speed = 350
export var steer_force = 50.0
var velocity = Vector2.ZERO
var acceleration = Vector2.ZERO
var target = null
func start(_transform, _target):
global_transform = _transform
rotation += rand_range(-0.09, 0.09)
velocity = transform.x * speed
target = _target
func seek():
var steer = Vector2.ZERO
if target:
var desired = (target.position - position).normalized() * speed
steer = (desired - velocity).normalized() * steer_force
return steer
func _physics_process(delta):
acceleration += seek()
velocity += acceleration * delta
velocity = velocity.clamped(speed)
rotation = velocity.angle()
position += velocity * delta
func _on_Missile_body_entered(body):
explode()
func _on_Lifetime_timeout():
explode()
func explode():
$Particles2D.emitting = false
set_physics_process(false)
$AnimationPlayer.play("explode")
await $AnimationPlayer.animation_finished
queue_free()