6. 敌人AI
2026/4/14大约 10 分钟
6. 坦克大战——敌人AI
简介
AI是"人工智能"(Artificial Intelligence)的缩写。在游戏里,AI就是让电脑控制的敌人"看起来像有脑子一样"行动。坦克大战里的敌人AI不需要特别聪明——它会移动、会射击、偶尔会朝玩家或基地方向走就行。
你可以把敌人AI想象成一个不太聪明的保安——他会在固定路线上巡逻,偶尔看到可疑人员会追过去,但不会用复杂的策略围堵你。
敌人AI的行为模式
经典坦克大战的敌人有几种基本行为,就像保安有不同的工作模式:
| 行为模式 | 说明 | 出现频率 |
|---|---|---|
| 随机移动 | 随机选择一个方向走,撞墙就换方向 | 最常见,占60% |
| 朝玩家移动 | 大概知道玩家在哪个方向,朝那个方向走 | 中等,占25% |
| 朝基地移动 | 直接往基地(鹰标志)方向走 | 危险,占15% |
| 射击 | 面朝某个方向时自动开枪 | 持续进行 |
敌人AI状态机
状态机就像一个自动切换的开关——敌人当前处于某种"状态"(比如正在巡逻),当满足某个条件时(比如看到了玩家),就切换到另一种状态(开始追踪)。
┌──────────┐ 看到玩家 ┌──────────┐
│ 随机巡逻 │ ────────────→ │ 追踪玩家 │
│ (Random) │ ←──────────── │ (Chase) │
└──────────┘ 丢失目标 └──────────┘
│ │
│ 碰到墙壁 │ 撞墙
│ 换方向 │ 换方向
↓ ↓
┌──────────┐ 每隔一段时间 ┌──────────┐
│ 攻击基地 │ ←───────────── │ 射击 │
│ (Attack) │ │ (Shoot) │
└──────────┘ └──────────┘// EnemyAI.cs - 敌人AI控制器
using Godot;
public enum EnemyAIState
{
Random, // 随机巡逻
Chase, // 追踪玩家
AttackBase // 攻击基地
}
public partial class EnemyAI : Node
{
// ========== 配置 ==========
// AI切换方向的间隔时间(秒)
[Export] public float DirectionChangeInterval { get; set; } = 2.0f;
// 追踪玩家的概率(每次决策时)
[Export] public float ChaseProbability { get; set; } = 0.3f;
// 攻击基地的概率
[Export] public float AttackBaseProbability { get; set; } = 0.15f;
// 射击间隔
[Export] public float ShootInterval { get; set; } = 1.5f;
// ========== 状态 ==========
private EnemyAIState _currentState = EnemyAIState.Random;
private float _directionTimer = 0.0f;
private float _shootTimer = 0.0f;
private float _stateDecisionTimer = 0.0f;
// ========== 引用 ==========
private EnemyTank _tank;
private PlayerTank _player;
private Node2D _base;
// ========== 初始化 ==========
public void Initialize(EnemyTank tank)
{
_tank = tank;
// 找到玩家和基地
_player = GetNodeOrNull<PlayerTank>("/root/Game/TanksContainer/Player");
_base = GetNodeOrNull<Node2D>("/root/Game/Base");
// 随机初始方向
ChangeToRandomDirection();
}
// ========== 每帧更新 ==========
public void UpdateAI(double delta)
{
if (!_tank.IsAlive) return;
float dt = (float)delta;
// 更新各种计时器
_directionTimer -= dt;
_shootTimer -= dt;
_stateDecisionTimer -= dt;
// 定时做一次"决策"(决定下一步干什么)
if (_stateDecisionTimer <= 0)
{
MakeDecision();
_stateDecisionTimer = 1.0f; // 每秒做一次决策
}
// 定时换方向
if (_directionTimer <= 0)
{
ChangeDirectionBasedOnState();
_directionTimer = DirectionChangeInterval;
}
// 定时射击
if (_shootTimer <= 0)
{
TryShoot();
_shootTimer = ShootInterval;
}
}
// ========== AI决策 ==========
// 做一次决策:决定当前应该处于什么状态
private void MakeDecision()
{
if (_player == null || !_player.IsAlive)
{
// 玩家已经死了,直接攻击基地
_currentState = EnemyAIState.AttackBase;
return;
}
// 生成一个0到1之间的随机数
float roll = (float)GD.RandRange(0.0, 1.0);
if (roll < AttackBaseProbability)
{
// 15%概率攻击基地
_currentState = EnemyAIState.AttackBase;
}
else if (roll < AttackBaseProbability + ChaseProbability)
{
// 30%概率追踪玩家
_currentState = EnemyAIState.Chase;
}
else
{
// 55%概率随机巡逻
_currentState = EnemyAIState.Random;
}
}
// 根据当前状态决定往哪个方向走
private void ChangeDirectionBasedOnState()
{
switch (_currentState)
{
case EnemyAIState.Random:
ChangeToRandomDirection();
break;
case EnemyAIState.Chase:
ChangeToChasePlayer();
break;
case EnemyAIState.AttackBase:
ChangeToAttackBase();
break;
}
}
// ========== 具体行为 ==========
// 随机选择一个方向
private void ChangeToRandomDirection()
{
// 随机选一个方向(上、下、左、右)
int randomDir = (int)(GD.Randi() % 4);
_tank.SetDirection((GameManager.Direction)randomDir);
}
// 朝玩家方向移动
private void ChangeToChasePlayer()
{
if (_player == null) return;
// 计算敌人到玩家的方向
Vector2 diff = _player.Position - _tank.Position;
// 选择差距更大的那个轴(上下还是左右差距更大)
if (Mathf.Abs(diff.X) > Mathf.Abs(diff.Y))
{
// 左右差距更大,往左或往右走
_tank.SetDirection(diff.X > 0
? GameManager.Direction.Right
: GameManager.Direction.Left);
}
else
{
// 上下差距更大,往上或往下走
_tank.SetDirection(diff.Y > 0
? GameManager.Direction.Down
: GameManager.Direction.Up);
}
}
// 朝基地方向移动
private void ChangeToAttackBase()
{
if (_base == null) return;
Vector2 diff = _base.Position - _tank.Position;
if (Mathf.Abs(diff.X) > Mathf.Abs(diff.Y))
{
_tank.SetDirection(diff.X > 0
? GameManager.Direction.Right
: GameManager.Direction.Left);
}
else
{
_tank.SetDirection(diff.Y > 0
? GameManager.Direction.Down
: GameManager.Direction.Up);
}
}
// 尝试射击
private void TryShoot()
{
// 面朝玩家方向时才射击(提高命中率)
if (_player != null && _player.IsAlive)
{
Vector2 diff = _player.Position - _tank.Position;
bool isFacingPlayer = IsFacingTarget(diff);
// 面朝玩家时有80%概率射击,否则30%
if (isFacingPlayer && (float)GD.RandRange(0.0, 1.0) < 0.8f)
{
_tank.Shoot();
}
else if (!isFacingPlayer && (float)GD.RandRange(0.0, 1.0) < 0.3f)
{
_tank.Shoot();
}
}
else
{
// 玩家死了就随便射
if ((float)GD.RandRange(0.0, 1.0) < 0.5f)
{
_tank.Shoot();
}
}
}
// 判断当前是否面朝目标方向
private bool IsFacingTarget(Vector2 directionToTarget)
{
return _tank.MoveDirection switch
{
GameManager.Direction.Up => directionToTarget.Y < -10,
GameManager.Direction.Down => directionToTarget.Y > 10,
GameManager.Direction.Left => directionToTarget.X < -10,
GameManager.Direction.Right => directionToTarget.X > 10,
_ => false
};
}
}# enemy_ai.gd - 敌人AI控制器
extends Node
# AI状态枚举
enum AIState {
RANDOM, # 随机巡逻
CHASE, # 追踪玩家
ATTACK_BASE # 攻击基地
}
# ========== 配置 ==========
## AI切换方向的间隔时间(秒)
@export var direction_change_interval: float = 2.0
## 追踪玩家的概率(每次决策时)
@export var chase_probability: float = 0.3
## 攻击基地的概率
@export var attack_base_probability: float = 0.15
## 射击间隔
@export var shoot_interval: float = 1.5
# ========== 状态 ==========
var current_state: int = AIState.RANDOM
var direction_timer: float = 0.0
var shoot_timer: float = 0.0
var state_decision_timer: float = 0.0
# ========== 引用 ==========
var tank: Node # EnemyTank 节点
var player: Node # PlayerTank 节点
var base_node: Node # 基地节点
# ========== 初始化 ==========
func initialize(enemy_tank: Node) -> void:
tank = enemy_tank
# 找到玩家和基地
player = get_node_or_null("/root/Game/TanksContainer/Player")
base_node = get_node_or_null("/root/Game/Base")
# 随机初始方向
change_to_random_direction()
# ========== 每帧更新 ==========
func update_ai(delta: float) -> void:
if not tank.is_alive:
return
# 更新各种计时器
direction_timer -= delta
shoot_timer -= delta
state_decision_timer -= delta
# 定时做一次"决策"(决定下一步干什么)
if state_decision_timer <= 0:
make_decision()
state_decision_timer = 1.0 # 每秒做一次决策
# 定时换方向
if direction_timer <= 0:
change_direction_based_on_state()
direction_timer = direction_change_interval
# 定时射击
if shoot_timer <= 0:
try_shoot()
shoot_timer = shoot_interval
# ========== AI决策 ==========
## 做一次决策:决定当前应该处于什么状态
func make_decision() -> void:
if player == null or not player.is_alive:
# 玩家已经死了,直接攻击基地
current_state = AIState.ATTACK_BASE
return
# 生成一个0到1之间的随机数
var roll: float = randf_range(0.0, 1.0)
if roll < attack_base_probability:
# 15%概率攻击基地
current_state = AIState.ATTACK_BASE
elif roll < attack_base_probability + chase_probability:
# 30%概率追踪玩家
current_state = AIState.CHASE
else:
# 55%概率随机巡逻
current_state = AIState.RANDOM
## 根据当前状态决定往哪个方向走
func change_direction_based_on_state() -> void:
match current_state:
AIState.RANDOM:
change_to_random_direction()
AIState.CHASE:
change_to_chase_player()
AIState.ATTACK_BASE:
change_to_attack_base()
# ========== 具体行为 ==========
## 随机选择一个方向
func change_to_random_direction() -> void:
# 随机选一个方向(上、下、左、右)
var random_dir: int = randi() % 4
tank.set_direction(random_dir)
## 朝玩家方向移动
func change_to_chase_player() -> void:
if player == null:
return
# 计算敌人到玩家的方向
var diff = player.position - tank.position
# 选择差距更大的那个轴
if absf(diff.x) > absf(diff.y):
# 左右差距更大
tank.set_direction(GameManager.Direction.RIGHT if diff.x > 0 else GameManager.Direction.LEFT)
else:
# 上下差距更大
tank.set_direction(GameManager.Direction.DOWN if diff.y > 0 else GameManager.Direction.UP)
## 朝基地方向移动
func change_to_attack_base() -> void:
if base_node == null:
return
var diff = base_node.position - tank.position
if absf(diff.x) > absf(diff.y):
tank.set_direction(GameManager.Direction.RIGHT if diff.x > 0 else GameManager.Direction.LEFT)
else:
tank.set_direction(GameManager.Direction.DOWN if diff.y > 0 else GameManager.Direction.UP)
## 尝试射击
func try_shoot() -> void:
if player != null and player.is_alive:
var diff = player.position - tank.position
var is_facing_player = is_facing_target(diff)
# 面朝玩家时有80%概率射击,否则30%
if is_facing_player and randf_range(0.0, 1.0) < 0.8:
tank.shoot()
elif not is_facing_player and randf_range(0.0, 1.0) < 0.3:
tank.shoot()
else:
# 玩家死了就随便射
if randf_range(0.0, 1.0) < 0.5:
tank.shoot()
## 判断当前是否面朝目标方向
func is_facing_target(direction_to_target: Vector2) -> bool:
match tank.move_direction:
GameManager.Direction.UP:
return direction_to_target.y < -10
GameManager.Direction.DOWN:
return direction_to_target.y > 10
GameManager.Direction.LEFT:
return direction_to_target.x < -10
GameManager.Direction.RIGHT:
return direction_to_target.x > 10
_:
return false敌人坦克
敌人坦克继承自 TankBase,增加了AI控制和生命值系统。
// EnemyTank.cs - 敌人坦克
using Godot;
public enum EnemyType
{
Basic, // 基础坦克(银色,1血)
Fast, // 快速坦克(浅蓝,1血,速度快)
Power, // 强力坦克(绿色,2血,子弹快)
Armor // 装甲坦克(红色,4血,子弹快)
}
public partial class EnemyTank : TankBase
{
// ========== 敌人类型 ==========
[Export] public EnemyType Type { get; set; } = EnemyType.Basic;
// ========== AI控制器 ==========
private EnemyAI _ai;
// ========== 生命值 ==========
private int _maxHealth = 1;
private int _currentHealth = 1;
// ========== 射击 ==========
private float _shootCooldown = 1.0f;
private float _shootTimer = 0.0f;
private PackedScene _bulletScene;
// ========== 初始化 ==========
public override void _Ready()
{
base._Ready();
// 根据类型设置属性
SetupByType();
// 创建AI控制器
_ai = new EnemyAI();
AddChild(_ai);
_ai.Initialize(this);
// 加载子弹场景
_bulletScene = GD.Load<PackedScene>("res://scenes/bullets/bullet.tscn");
// 出生时短暂保护(防止被出生点附近卡住)
SetShielded(true, 1.0f);
}
// 根据敌人类型设置属性
private void SetupByType()
{
switch (Type)
{
case EnemyType.Basic:
_maxHealth = 1;
_speed = BASE_SPEED * 0.8f;
_shootCooldown = 1.5f;
break;
case EnemyType.Fast:
_maxHealth = 1;
_speed = BASE_SPEED * 1.5f;
_shootCooldown = 1.5f;
break;
case EnemyType.Power:
_maxHealth = 2;
_speed = BASE_SPEED * 0.8f;
_shootCooldown = 1.0f;
break;
case EnemyType.Armor:
_maxHealth = 4;
_speed = BASE_SPEED * 0.7f;
_shootCooldown = 1.0f;
break;
}
_currentHealth = _maxHealth;
}
// ========== 每帧更新 ==========
public override void _Process(double delta)
{
if (!_isAlive) return;
if (GameManager.Instance.CurrentState != GameManager.GameState.Playing) return;
// 更新AI
_ai.UpdateAI(delta);
// 更新射击计时器
_shootTimer -= (float)delta;
}
// ========== 受伤和死亡 ==========
// 被子弹击中
public void TakeDamage(int damage = 1)
{
if (_isShielded) return;
_currentHealth -= damage;
// 被击中时闪烁一下
FlashWhite();
if (_currentHealth <= 0)
{
Die();
}
}
// 死亡处理
private void Die()
{
// 加分
int score = Type switch
{
EnemyType.Basic => 100,
EnemyType.Fast => 200,
EnemyType.Power => 300,
EnemyType.Armor => 400,
_ => 100
};
GameManager.Instance.AddScore(score);
// 有概率掉落道具(25%概率)
if ((float)GD.RandRange(0.0, 1.0) < 0.25f)
{
SpawnItem();
}
// 摧毁坦克
Destroy();
}
// 被击中时闪白
private void FlashWhite()
{
var tween = CreateTween();
_sprite.Modulate = new Color(1, 1, 1, 0.5f);
tween.TweenProperty(_sprite, "modulate", new Color(1, 1, 1, 1.0f), 0.2f);
}
// 掉落道具
private void SpawnItem()
{
var itemTypes = new[] { "star", "bomb", "shovel" };
int randomIndex = (int)(GD.Randi() % itemTypes.Length);
string itemType = itemTypes[randomIndex];
var scene = GD.Load<PackedScene>($"res://scenes/items/{itemType}.tscn");
var item = scene.Instantiate<Node2D>();
item.Position = Position;
var container = GetNode("/root/Game/ItemsContainer");
container.AddChild(item);
}
// 发射子弹
public void Shoot()
{
if (_shootTimer > 0) return;
if (_bulletScene == null) return;
var bullet = _bulletScene.Instantiate<Bullet>();
bullet.IsPlayerBullet = false;
bullet.Position = GetBulletSpawnPosition();
bullet.Direction = _moveDirection;
var container = GetNode("/root/Game/BulletsContainer");
container.AddChild(bullet);
_shootTimer = _shootCooldown;
}
// 获取子弹出生位置
private Vector2 GetBulletSpawnPosition()
{
float offset = TANK_SIZE / 2 + 4;
return _moveDirection switch
{
GameManager.Direction.Up => Position + new Vector2(0, -offset),
GameManager.Direction.Down => Position + new Vector2(0, offset),
GameManager.Direction.Left => Position + new Vector2(-offset, 0),
GameManager.Direction.Right => Position + new Vector2(offset, 0),
_ => Position
};
}
public override void Destroy()
{
base.Destroy();
// 通知生成器,可以生成新敌人了
var spawner = GetNodeOrNull("/root/Game/EnemySpawner");
if (spawner != null)
{
spawner.Call("on_enemy_died");
}
}
}# enemy_tank.gd - 敌人坦克
extends "res://scripts/tanks/tank_base.gd"
# 敌人类型枚举
enum EnemyType {
BASIC, # 基础坦克(银色,1血)
FAST, # 快速坦克(浅蓝,1血,速度快)
POWER, # 强力坦克(绿色,2血,子弹快)
ARMOR # 装甲坦克(红色,4血,子弹快)
}
# ========== 敌人类型 ==========
@export var type: int = EnemyType.BASIC
# ========== AI控制器 ==========
var ai: Node # EnemyAI 节点
# ========== 生命值 ==========
var max_health: int = 1
var current_health: int = 1
# ========== 射击 ==========
var shoot_cooldown: float = 1.0
var shoot_timer: float = 0.0
var bullet_scene: PackedScene
# ========== 初始化 ==========
func _ready() -> void:
super._ready()
# 根据类型设置属性
setup_by_type()
# 创建AI控制器
ai = preload("res://scripts/tanks/enemy_ai.gd").new()
add_child(ai)
ai.initialize(self)
# 加载子弹场景
bullet_scene = load("res://scenes/bullets/bullet.tscn")
# 出生时短暂保护
set_shielded(true, 1.0)
## 根据敌人类型设置属性
func setup_by_type() -> void:
match type:
EnemyType.BASIC:
max_health = 1
speed = BASE_SPEED * 0.8
shoot_cooldown = 1.5
EnemyType.FAST:
max_health = 1
speed = BASE_SPEED * 1.5
shoot_cooldown = 1.5
EnemyType.POWER:
max_health = 2
speed = BASE_SPEED * 0.8
shoot_cooldown = 1.0
EnemyType.ARMOR:
max_health = 4
speed = BASE_SPEED * 0.7
shoot_cooldown = 1.0
current_health = max_health
# ========== 每帧更新 ==========
func _process(delta: float) -> void:
if not is_alive:
return
if GameManager.current_state != GameManager.GameState.PLAYING:
return
# 更新AI
ai.update_ai(delta)
# 更新射击计时器
shoot_timer -= delta
# ========== 受伤和死亡 ==========
## 被子弹击中
func take_damage(damage: int = 1) -> void:
if is_shielded:
return
current_health -= damage
# 被击中时闪烁一下
flash_white()
if current_health <= 0:
die()
## 死亡处理
func die() -> void:
# 加分
var score: int
match type:
EnemyType.BASIC: score = 100
EnemyType.FAST: score = 200
EnemyType.POWER: score = 300
EnemyType.ARMOR: score = 400
_: score = 100
GameManager.add_score(score)
# 有概率掉落道具(25%概率)
if randf() < 0.25:
spawn_item()
# 摧毁坦克
destroy()
## 被击中时闪白
func flash_white() -> void:
var tween = create_tween()
sprite.modulate = Color(1, 1, 1, 0.5)
tween.tween_property(sprite, "modulate", Color(1, 1, 1, 1.0), 0.2)
## 掉落道具
func spawn_item() -> void:
var item_types = ["star", "bomb", "shovel"]
var random_index: int = randi() % item_types.size()
var item_type = item_types[random_index]
var scene = load("res://scenes/items/%s.tscn" % item_type)
var item = scene.instantiate()
item.position = position
var container = get_node("/root/Game/ItemsContainer")
container.add_child(item)
## 发射子弹
func shoot() -> void:
if shoot_timer > 0:
return
if bullet_scene == null:
return
var bullet = bullet_scene.instantiate()
bullet.is_player_bullet = false
bullet.position = get_bullet_spawn_position()
bullet.direction = move_direction
var container = get_node("/root/Game/BulletsContainer")
container.add_child(bullet)
shoot_timer = shoot_cooldown
## 获取子弹出生位置
func get_bullet_spawn_position() -> Vector2:
var offset: float = TANK_SIZE / 2 + 4
match move_direction:
GameManager.Direction.UP:
return position + Vector2(0, -offset)
GameManager.Direction.DOWN:
return position + Vector2(0, offset)
GameManager.Direction.LEFT:
return position + Vector2(-offset, 0)
GameManager.Direction.RIGHT:
return position + Vector2(offset, 0)
_:
return position
func destroy() -> void:
super.destroy()
# 通知生成器
var spawner = get_node_or_null("/root/Game/EnemySpawner")
if spawner != null:
spawner.on_enemy_died()不同难度AI的行为差异
通过调整AI的参数,可以让不同类型的敌人表现出不同的"智商":
| 敌人类型 | 追踪概率 | 射击频率 | 方向切换频率 | 特殊行为 |
|---|---|---|---|---|
| 基础坦克 | 20% | 低(2秒) | 慢(3秒) | 几乎纯随机 |
| 快速坦克 | 25% | 低(2秒) | 快(1秒) | 频繁换方向 |
| 强力坦克 | 40% | 中(1秒) | 中(2秒) | 偶尔追踪 |
| 装甲坦克 | 50% | 高(0.8秒) | 慢(2.5秒) | 持续追踪 |
下一章预告
敌人AI完成了!现在敌人坦克会移动、会追踪、会射击。下一章我们将实现基地防御系统——保护基地不被摧毁,以及三种实用道具:星星升级、炸弹清屏、铲子加固。
