ByteFisher AI 编程实战(十一):AI辅助Unity开发(下)——完整项目实战

上篇我们看了 AI 如何生成单个脚本。本篇更进一步——用 AI 从零构建一个完整的游戏原型,涵盖从需求描述到可运行的完整流程。

一、项目:第三人称射击游戏原型

我们将用 AI 构建一个简化版的第三人称射击游戏,包含玩家移动和射击、敌人生成和 AI 行为、计分系统和 UI、游戏循环。整个过程全部使用 AI 辅助完成。

1.1 技术栈

  • Unity 2022.3 LTS + URP
  • C# 脚本 + Cinemachine 相机控制
  • NavMesh 寻路
  • 对象池管理子弹

二、用 AI 搭建项目骨架

2.1 需求描述

在 Cursor 中,向 AI 描述整体需求:

1
2
3
4
5
6
7
8
帮我规划一个第三人称射击游戏的 Unity 项目结构。
功能模块:
1. Player:移动、射击、生命值
2. Enemy:追击玩家、攻击、被消灭
3. Spawner:波次生成敌人
4. GameManager:分数、游戏状态管理
5. UI:血量条、分数显示、游戏结束面板
6. 子弹:对象池管理

AI 给出项目结构建议后,我们逐个模块让 AI 实现。

2.2 PlayerController——玩家控制

1
2
3
4
5
6
7
创建玩家控制脚本:
- 使用 CharacterController 移动
- WASD 控制角色移动方向
- 鼠标左键射击(发射子弹)
- 按 R 键换弹
- 血条系统,受击闪烁效果
- 鼠标控制视角,角色跟随视角旋转

生成的 PlayerController 脚本(关键部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class PlayerController : MonoBehaviour
{
[Header("移动")]
[SerializeField] private float moveSpeed = 6f;
[SerializeField] private float jumpForce = 8f;

[Header("射击")]
[SerializeField] private GameObject bulletPrefab;
[SerializeField] private Transform firePoint;
[SerializeField] private float fireRate = 0.2f;
[SerializeField] private int maxAmmo = 30;

[Header("生命值")]
[SerializeField] private int maxHealth = 100;

private CharacterController controller;
private Camera playerCamera;
private ObjectPool<Bullet> bulletPool;
private int currentHealth;
private int currentAmmo;
private float lastFireTime;
private Vector3 velocity;

private void Awake()
{
controller = GetComponent<CharacterController>();
playerCamera = GetComponentInChildren<Camera>();
bulletPool = new ObjectPool<Bullet>(bulletPrefab.GetComponent<Bullet>(), 20);
currentHealth = maxHealth;
currentAmmo = maxAmmo;
}

private void Update()
{
HandleMovement();
HandleCamera();
HandleShooting();
}

private void HandleShooting()
{
if (Input.GetButton("Fire1") && Time.time - lastFireTime >= fireRate)
{
if (currentAmmo <= 0) return;

Bullet bullet = bulletPool.Get();
bullet.transform.position = firePoint.position;
bullet.transform.rotation = firePoint.rotation;
bullet.Initialize(bulletPool);

currentAmmo--;
lastFireTime = Time.time;
}

if (Input.GetKeyDown(KeyCode.R))
StartCoroutine(Reload());
}

public void TakeDamage(int damage)
{
currentHealth -= damage;
StartCoroutine(FlashRed());
if (currentHealth <= 0)
GameManager.Instance.GameOver();
}

private IEnumerator FlashRed()
{
// 受击闪烁效果
GetComponentInChildren<Renderer>().material.color = Color.red;
yield return new WaitForSeconds(0.1f);
GetComponentInChildren<Renderer>().material.color = Color.white;
}
}

2.3 EnemyController——使用状态机的敌人 AI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public enum EnemyState { Idle, Chase, Attack, Dead }

public class EnemyController : MonoBehaviour
{
[Header("AI 参数")]
[SerializeField] private float chaseRange = 15f;
[SerializeField] private float attackRange = 5f;
[SerializeField] private float moveSpeed = 3.5f;
[SerializeField] private int maxHealth = 20;
[SerializeField] private int damage = 10;
[SerializeField] private float attackCooldown = 1.5f;

private EnemyState currentState;
private NavMeshAgent agent;
private Transform player;
private int currentHealth;
private float lastAttackTime;

private void Awake()
{
agent = GetComponent<NavMeshAgent>();
currentHealth = maxHealth;
agent.speed = moveSpeed;
}

private void Start()
{
player = GameObject.FindWithTag("Player").transform;
ChangeState(EnemyState.Idle);
}

private void Update()
{
switch (currentState)
{
case EnemyState.Idle: UpdateIdle(); break;
case EnemyState.Chase: UpdateChase(); break;
case EnemyState.Attack: UpdateAttack(); break;
}
}

private void UpdateIdle()
{
float distance = Vector3.Distance(transform.position, player.position);
if (distance <= chaseRange)
ChangeState(EnemyState.Chase);
}

private void UpdateChase()
{
agent.SetDestination(player.position);
float distance = Vector3.Distance(transform.position, player.position);
if (distance <= attackRange)
ChangeState(EnemyState.Attack);
else if (distance > chaseRange * 1.5f)
ChangeState(EnemyState.Idle);
}

private void UpdateAttack()
{
agent.ResetPath();
agent.isStopped = true;
float distance = Vector3.Distance(transform.position, player.position);
if (distance > attackRange * 1.2f)
{
ChangeState(EnemyState.Chase);
return;
}

if (Time.time - lastAttackTime >= attackCooldown)
{
player.GetComponent<PlayerController>()?.TakeDamage(damage);
lastAttackTime = Time.time;
}
}

public void TakeDamage(int damage)
{
currentHealth -= damage;
if (currentHealth <= 0) Die();
}

private void Die()
{
ChangeState(EnemyState.Dead);
agent.enabled = false;
GetComponent<Collider>().enabled = false;
Destroy(gameObject, 2f);
GameManager.Instance?.OnEnemyKilled();
}

private void ChangeState(EnemyState newState)
{
currentState = newState;
agent.isStopped = newState == EnemyState.Idle || newState == EnemyState.Dead;
}
}

2.4 Spawner——波次生成系统

1
2
3
4
5
6
7
创建敌人波次生成器:
- 每波生成 5 个敌人
- 敌人从 3 个预设生成点随机生成
- 击杀完当前波次后等待 5 秒开始下一波
- 每波敌人的生命值递增 10%
- 每 5 波出现一个精英敌人(双倍属性)
- 生成时播放特效

三、GameManager 游戏循环

3.1 单例 GameManager

1
2
3
4
5
6
7
实现 GameManager 单例:
- 游戏状态:Menu、Playing、Paused、GameOver
- 分数系统:击杀敌人 +10 分
- 当前波次显示
- 生命值归零时切换至 GameOver 状态
- 提供 Pause/Resume 方法
- 支持重新开始游戏

GameManager 是游戏循环的核心,控制状态流转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }

public enum GameState { Menu, Playing, Paused, GameOver }
public GameState CurrentState { get; private set; }

public int Score { get; private set; }
public int CurrentWave { get; private set; }

private void Awake()
{
if (Instance != null) Destroy(gameObject);
Instance = this;
DontDestroyOnLoad(gameObject);
}

public void StartGame()
{
Score = 0;
CurrentWave = 1;
CurrentState = GameState.Playing;
}

public void OnEnemyKilled()
{
Score += 10;
UIManager.Instance?.UpdateScore(Score);
}

public void GameOver()
{
CurrentState = GameState.GameOver;
UIManager.Instance?.ShowGameOver(Score);
}

public void Pause() { CurrentState = GameState.Paused; }
public void Resume() { CurrentState = GameState.Playing; }
}

3.2 UI 系统

1
2
3
4
5
6
创建 UIManager:
- 血量条(Slider,实时反映玩家血量)
- 弹药数显示(当前弹夹/总备弹)
- 分数显示(顶部居中)
- 波次提示(每波开始时闪烁显示)
- 游戏结束面板(显示最终分数 + 重新开始按钮)

四、Bullet 子弹与对象池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Bullet : MonoBehaviour
{
[SerializeField] private float speed = 30f;
[SerializeField] private int damage = 5;
[SerializeField] private float lifetime = 3f;

private ObjectPool<Bullet> pool;
private float spawnTime;

public void Initialize(ObjectPool<Bullet> ownerPool)
{
pool = ownerPool;
spawnTime = Time.time;
}

private void Update()
{
transform.position += transform.forward * speed * Time.deltaTime;

if (Time.time - spawnTime > lifetime)
pool.Return(this);
}

private void OnTriggerEnter(Collider other)
{
if (other.TryGetComponent<EnemyController>(out var enemy))
{
enemy.TakeDamage(damage);
pool.Return(this);
}
}
}

五、性能优化与 AI 审查

游戏原型完成后,让 AI 审查代码:

1
2
3
4
5
审查这个 Unity 项目的性能问题:
1. 哪些代码在 Update 中产生了 GC Alloc?
2. 对象池是否合理?
3. 有没有不必要的 GetComponent 调用?
4. Update 中是否有过多距离计算?

AI 通常指出的问题及优化方案:

常见问题 优化方案 代码体现
Update 中的字符串拼接 使用 StringBuilder 或缓存字符串 避免 "Score: " + score
频繁的 Instantiate/Destroy 使用对象池 Bullet 对象池复用
每帧调用 GetComponent 在 Awake/Start 中缓存引用 所有 GetComponent 都在 Awake
未限制的协程 添加生命周期管理 对象回收时停止协程
每帧距离计算 使用 sqrMagnitude 替代 Distance (a-b).sqrMagnitude < range * range

本章小结

  • AI 可以从零构建完整的游戏原型:Player 控制器、Enemy AI、Spawner 波次系统、GameManager 循环、UI 系统、子弹对象池
  • 分步确认结构 → 逐个模块实现 → 集成测试的流程最高效
  • 状态机模式的 Enemy AI 是 AI 生成的高质量场景
  • 对象池管理 + GetComponent 缓存是 AI 生成的 Unity 代码中最常用的优化模式
  • 性能审查建议:使用 sqrMagnitude 替代 Distance、避免 Update 字符串拼接、Awake 中缓存引用
  • 游戏”手感”、关卡设计、数值平衡仍需开发者把控,AI 负责执行层面的代码生成

下一篇从游戏开发回到通用编程,看 AI 如何助力代码审查与重构。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: