4. 武器与导弹系统
2026/4/14大约 4 分钟
武器与导弹系统
皇牌空战的战斗核心就是导弹和机炮。导弹追踪目标的方式、被追踪时的规避技巧、机炮的射击提前量——这些才是让空战好玩的关键。本章实现完整的武器系统。
本章你将学到
- 雷达锁定系统:如何在 3D 空间中搜索并锁定目标
- 追踪导弹:比例导航制导的简化实现
- 机炮:前置瞄准(提前量)的计算
- 导弹规避:发现来袭导弹并机动甩脱
雷达锁定系统
雷达锁定分两步:搜索(找到目标)→ 锁定(保持追踪一段时间后可以发射导弹)。
GDScript
extends Node3D
## 雷达锁定系统
class_name RadarSystem
@export var lock_angle: float = 30.0 ## 雷达搜索角度(度)
@export var lock_range: float = 8000.0 ## 最大锁定距离(米)
@export var time_to_lock: float = 1.5 ## 完成锁定所需时间(秒)
var current_target: Node3D = null ## 当前锁定目标
var lock_progress: float = 0.0 ## 锁定进度(0~1)
var is_locked: bool = false ## 是否完成锁定
signal target_locked(target: Node3D)
signal lock_lost
func _process(delta: float) -> void:
if current_target == null:
_search_for_target()
return
# 检查目标是否还在雷达范围内
if not _is_target_in_range(current_target):
_lose_target()
return
# 积累锁定进度
lock_progress = minf(lock_progress + delta / time_to_lock, 1.0)
if lock_progress >= 1.0 and not is_locked:
is_locked = true
emit_signal("target_locked", current_target)
func _search_for_target() -> void:
## 搜索雷达角度范围内最近的敌方飞机
var enemies := get_tree().get_nodes_in_group("enemy_aircraft")
var nearest_dist := INF
var nearest: Node3D = null
for enemy in enemies:
if not _is_target_in_range(enemy):
continue
var dist := global_position.distance_to(enemy.global_position)
if dist < nearest_dist:
nearest_dist = dist
nearest = enemy
if nearest != null:
current_target = nearest
lock_progress = 0.0
func _is_target_in_range(target: Node3D) -> bool:
var to_target := target.global_position - global_position
if to_target.length() > lock_range:
return false
# 检查目标是否在飞机前方锥形范围内
var forward := -global_transform.basis.z
var angle := rad_to_deg(forward.angle_to(to_target.normalized()))
return angle <= lock_angle
func _lose_target() -> void:
current_target = null
lock_progress = 0.0
is_locked = false
emit_signal("lock_lost")C
using Godot;
public partial class RadarSystem : Node3D
{
[Export] public float LockAngle = 30f;
[Export] public float LockRange = 8000f;
[Export] public float TimeToLock = 1.5f;
public Node3D CurrentTarget { get; private set; } = null;
public float LockProgress { get; private set; } = 0f;
public bool IsLocked { get; private set; } = false;
[Signal] public delegate void TargetLockedEventHandler(Node3D target);
[Signal] public delegate void LockLostEventHandler();
public override void _Process(double delta)
{
if (CurrentTarget == null)
{
SearchForTarget();
return;
}
if (!IsTargetInRange(CurrentTarget))
{
LoseTarget();
return;
}
LockProgress = Mathf.Min(LockProgress + (float)delta / TimeToLock, 1f);
if (LockProgress >= 1f && !IsLocked)
{
IsLocked = true;
EmitSignal(SignalName.TargetLocked, CurrentTarget);
}
}
private void SearchForTarget()
{
var enemies = GetTree().GetNodesInGroup("enemy_aircraft");
float nearestDist = float.MaxValue;
Node3D nearest = null;
foreach (Node3D enemy in enemies)
{
if (!IsTargetInRange(enemy)) continue;
float dist = GlobalPosition.DistanceTo(enemy.GlobalPosition);
if (dist < nearestDist)
{
nearestDist = dist;
nearest = enemy;
}
}
if (nearest != null)
{
CurrentTarget = nearest;
LockProgress = 0f;
}
}
private bool IsTargetInRange(Node3D target)
{
var toTarget = target.GlobalPosition - GlobalPosition;
if (toTarget.Length() > LockRange) return false;
var forward = -GlobalTransform.Basis.Z;
float angle = Mathf.RadToDeg(forward.AngleTo(toTarget.Normalized()));
return angle <= LockAngle;
}
private void LoseTarget()
{
CurrentTarget = null;
LockProgress = 0f;
IsLocked = false;
EmitSignal(SignalName.LockLost);
}
}追踪导弹(比例导航制导)
追踪导弹不是"一直指向目标飞过去",而是用比例导航(PN):导弹修正飞行方向的速率与"目标视线转动速率"成正比。这让导弹能预测目标位置,更难规避。
GDScript
extends RigidBody3D
## 追踪导弹 - 比例导航制导
class_name GuidedMissile
@export var speed: float = 400.0 ## 导弹飞行速度(米/秒)
@export var nav_constant: float = 3.0 ## 比例导航常数(越大追踪越激进)
@export var max_flight_time: float = 10.0 ## 最大飞行时间(秒)
@export var warhead_radius: float = 8.0 ## 爆炸范围(米)
var target: Node3D = null
var elapsed_time: float = 0.0
var prev_los_direction: Vector3 ## 上一帧的目标视线方向(用于计算视线转动率)
func initialize(launch_target: Node3D, launch_velocity: Vector3) -> void:
target = launch_target
linear_velocity = launch_velocity ## 继承发射平台的速度
prev_los_direction = (target.global_position - global_position).normalized()
func _physics_process(delta: float) -> void:
elapsed_time += delta
# 超过飞行时间或目标消失则自毁
if elapsed_time > max_flight_time or not is_instance_valid(target):
_self_destruct()
return
_apply_proportional_navigation(delta)
_check_proximity()
func _apply_proportional_navigation(delta: float) -> void:
var to_target := target.global_position - global_position
var current_los := to_target.normalized() ## 当前视线方向
# 视线转动速率:视线方向的变化量(除以时间)
var los_rate := (current_los - prev_los_direction) / delta
prev_los_direction = current_los
# 比例导航指令:加速度 = 导航常数 × 关闭速率 × 视线转动率
var closing_speed := -linear_velocity.dot(current_los) ## 接近速率
var acceleration_command := nav_constant * closing_speed * los_rate
# 应用导航加速度(限制最大值防止导弹"转弯转死")
apply_central_force(acceleration_command.limit_length(speed * 5.0))
# 保持导弹速度恒定
linear_velocity = linear_velocity.normalized() * speed
func _check_proximity() -> void:
if not is_instance_valid(target):
return
if global_position.distance_to(target.global_position) < warhead_radius:
_detonate()
func _detonate() -> void:
# TODO: 触发爆炸效果和伤害判定
queue_free()
func _self_destruct() -> void:
queue_free()C
using Godot;
public partial class GuidedMissile : RigidBody3D
{
[Export] public float Speed = 400f;
[Export] public float NavConstant = 3f;
[Export] public float MaxFlightTime = 10f;
[Export] public float WarheadRadius = 8f;
private Node3D _target;
private float _elapsedTime = 0f;
private Vector3 _prevLosDirection;
public void Initialize(Node3D launchTarget, Vector3 launchVelocity)
{
_target = launchTarget;
LinearVelocity = launchVelocity;
_prevLosDirection = (_target.GlobalPosition - GlobalPosition).Normalized();
}
public override void _PhysicsProcess(double delta)
{
_elapsedTime += (float)delta;
if (_elapsedTime > MaxFlightTime || !IsInstanceValid(_target))
{
QueueFree();
return;
}
ApplyProportionalNavigation((float)delta);
CheckProximity();
}
private void ApplyProportionalNavigation(float delta)
{
var toTarget = _target.GlobalPosition - GlobalPosition;
var currentLos = toTarget.Normalized();
var losRate = (currentLos - _prevLosDirection) / delta;
_prevLosDirection = currentLos;
float closingSpeed = -LinearVelocity.Dot(currentLos);
var accelCommand = NavConstant * closingSpeed * losRate;
ApplyCentralForce(accelCommand.LimitLength(Speed * 5f));
LinearVelocity = LinearVelocity.Normalized() * Speed;
}
private void CheckProximity()
{
if (!IsInstanceValid(_target)) return;
if (GlobalPosition.DistanceTo(_target.GlobalPosition) < WarheadRadius)
QueueFree(); // TODO: 爆炸效果
}
}下一步
武器系统完成后,制作 敌机AI。
