上篇我们看了 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 如何助力代码审查与重构。