5. 敌机AI
2026/4/14大约 3 分钟
敌机AI
一个好的空战AI必须能做到:追着玩家咬尾、发射导弹、被锁定后机动规避、跟编队协同。本章实现从简单到进阶的敌机行为树。
本章你将学到
- 空战AI的状态机设计
- 基本机动:咬尾追踪(跟随玩家6点钟方向)
- 导弹规避机动:筋斗、桶滚、高G转弯
- 攻击决策:何时发射导弹、何时用机炮
- 编队飞行:长机-僚机协同
AI状态机
GDScript
extends AircraftController
## 敌机AI控制器
class_name EnemyAircraftAI
enum AIState { PATROL, INTERCEPT, POSITION, ATTACK, EVADE, DISENGAGE }
@export var detection_range: float = 5000.0 ## 发现玩家的距离
@export var attack_range: float = 2000.0 ## 开始攻击的距离
@export var gun_range: float = 500.0 ## 机炮有效射程
var state: AIState = AIState.PATROL
var player: Node3D = null
var incoming_missile: Node3D = null ## 来袭导弹
func _physics_process(delta: float) -> void:
match state:
AIState.PATROL: _patrol(delta)
AIState.INTERCEPT: _intercept(delta)
AIState.POSITION: _position_for_attack(delta)
AIState.ATTACK: _attack(delta)
AIState.EVADE: _evade_missile(delta)
AIState.DISENGAGE: _disengage(delta)
super._physics_process(delta) ## 调用父类物理更新
func _patrol(delta: float) -> void:
## 沿巡逻路径飞行,同时搜索玩家
player = _find_player()
if player != null:
state = AIState.INTERCEPT
func _intercept(delta: float) -> void:
## 全速飞向玩家
_fly_toward(player.global_position, delta)
throttle = 1.0
if global_position.distance_to(player.global_position) < attack_range:
state = AIState.POSITION
func _position_for_attack(delta: float) -> void:
## 机动到玩家后方(6点钟位置)
var player_behind := player.global_position - player.global_transform.basis.z * 500
_fly_toward(player_behind, delta)
if _is_behind_player():
state = AIState.ATTACK
func _attack(delta: float) -> void:
## 锁定并攻击
_fly_toward(player.global_position, delta)
# 简化:直接发射导弹(实际应通过武器系统)
if _is_behind_player() and randf() < 0.005: ## 每帧0.5%概率发射
_fire_missile()
func _evade_missile(delta: float) -> void:
## 高G转弯规避来袭导弹
# 向垂直于来袭导弹方向的最优逃脱方向转弯
if incoming_missile == null:
state = AIState.POSITION
return
var evade_dir := global_transform.basis.y ## 向上拉起(最常见规避动作)
_apply_manual_input(evade_dir, delta)
throttle = 1.0
func _fly_toward(target_pos: Vector3, delta: float) -> void:
## 计算需要的偏转并施加输入
var to_target := (target_pos - global_position).normalized()
var forward := -global_transform.basis.z
# 计算转向误差
var right_dot := to_target.dot(global_transform.basis.x)
var up_dot := to_target.dot(global_transform.basis.y)
# 简单比例控制
var pitch_input := clampf(up_dot * 3.0, -1.0, 1.0)
var yaw_input := clampf(right_dot * 2.0, -1.0, 1.0)
apply_torque(global_transform.basis * Vector3(
pitch_input * deg_to_rad(pitch_rate),
yaw_input * deg_to_rad(yaw_rate),
0
))
func _is_behind_player() -> bool:
if player == null:
return false
var to_ai := (global_position - player.global_position).normalized()
return to_ai.dot(-player.global_transform.basis.z) > 0.7 ## 在后方±45°内
func _find_player() -> Node3D:
var players := get_tree().get_nodes_in_group("player_aircraft")
if players.is_empty():
return null
var p := players[0] as Node3D
if global_position.distance_to(p.global_position) < detection_range:
return p
return null
func _fire_missile() -> void:
pass ## TODO: 调用武器系统发射导弹
func _apply_manual_input(direction: Vector3, delta: float) -> void:
pass ## TODO: 向特定方向施加转向力C
using Godot;
public partial class EnemyAircraftAI : AircraftController
{
public enum AIState { Patrol, Intercept, Position, Attack, Evade, Disengage }
[Export] public float DetectionRange = 5000f;
[Export] public float AttackRange = 2000f;
[Export] public float GunRange = 500f;
private AIState _state = AIState.Patrol;
private Node3D _player = null;
private Node3D _incomingMissile = null;
public override void _PhysicsProcess(double delta)
{
switch (_state)
{
case AIState.Patrol: Patrol((float)delta); break;
case AIState.Intercept: Intercept((float)delta); break;
case AIState.Position: PositionForAttack((float)delta); break;
case AIState.Attack: Attack((float)delta); break;
case AIState.Evade: EvadeMissile((float)delta); break;
case AIState.Disengage: Disengage((float)delta); break;
}
base._PhysicsProcess(delta);
}
private void Patrol(float delta)
{
_player = FindPlayer();
if (_player != null) _state = AIState.Intercept;
}
private void Intercept(float delta)
{
FlyToward(_player.GlobalPosition, delta);
Throttle = 1f;
if (GlobalPosition.DistanceTo(_player.GlobalPosition) < AttackRange)
_state = AIState.Position;
}
private void PositionForAttack(float delta)
{
var targetPos = _player.GlobalPosition - _player.GlobalTransform.Basis.Z * 500f;
FlyToward(targetPos, delta);
if (IsBehindPlayer()) _state = AIState.Attack;
}
private void Attack(float delta)
{
FlyToward(_player.GlobalPosition, delta);
if (IsBehindPlayer() && GD.Randf() < 0.005f)
FireMissile();
}
private void EvadeMissile(float delta)
{
if (_incomingMissile == null) { _state = AIState.Position; return; }
// 向上拉起规避
ApplyTorque(GlobalTransform.Basis.Y * 10f);
Throttle = 1f;
}
private void Disengage(float delta) { /* 脱离逻辑 */ }
private void FlyToward(Vector3 targetPos, float delta)
{
var toTarget = (targetPos - GlobalPosition).Normalized();
float rightDot = toTarget.Dot(GlobalTransform.Basis.X);
float upDot = toTarget.Dot(GlobalTransform.Basis.Y);
ApplyTorque(GlobalTransform.Basis * new Vector3(
Mathf.Clamp(upDot * 3f, -1f, 1f) * Mathf.DegToRad(PitchRate),
Mathf.Clamp(rightDot * 2f, -1f, 1f) * Mathf.DegToRad(YawRate),
0
));
}
private bool IsBehindPlayer()
{
if (_player == null) return false;
var toAI = (GlobalPosition - _player.GlobalPosition).Normalized();
return toAI.Dot(-_player.GlobalTransform.Basis.Z) > 0.7f;
}
private Node3D FindPlayer()
{
var players = GetTree().GetNodesInGroup("player_aircraft");
if (players.Count == 0) return null;
var p = players[0] as Node3D;
return GlobalPosition.DistanceTo(p.GlobalPosition) < DetectionRange ? p : null;
}
private void FireMissile() { /* TODO */ }
}下一步
敌机AI完成后,进行 任务与地图设计。
