目标:可参与团队商业项目开发
第12章:脚本架构设计
12.1 SOLID 设计原则
| 原则 | 含义 | Unity示例 |
|---|---|---|
| S - 单一职责 | 一个类只做一件事 | PlayerMovement只管移动 |
| O - 开闭原则 | 对扩展开放,对修改关闭 | 用interface定义IWeapon |
| L - 里式替换 | 子类可替换父类 | Enemy继承Character |
| I - 接口隔离 | 接口小而专 | IDamageable/IHealable分离 |
| D - 依赖反转 | 依赖抽象不依赖具体 | 依赖IDataService接口 |
12.2 常用设计模式
csharp
// ═══ 单例模式 Singleton ═══
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake()
{
if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); }
else Destroy(gameObject);
}
}
// ═══ 观察者模式 Observer ═══
public class HealthSystem : MonoBehaviour
{
public event Action<float> OnHealthChanged;
public event Action OnDeath;
private float health;
public void TakeDamage(float dmg)
{
health -= dmg;
OnHealthChanged?.Invoke(health);
if (health <= 0) OnDeath?.Invoke();
}
}
// ═══ 命令模式 Command ═══
public interface ICommand { void Execute(); void Undo(); }
public class MoveCommand : ICommand
{
Transform target; Vector3 from, to;
public MoveCommand(Transform t, Vector3 dest)
{ target = t; from = t.position; to = dest; }
public void Execute() => target.position = to;
public void Undo() => target.position = from;
}
// 用途:技能释放、操作撤销、录像回放
// ═══ 状态机模式 State ═══
public interface IState { void Enter(); void Update(); void Exit(); }
public class StateMachine
{
private IState current;
public void ChangeState(IState newState)
{
current?.Exit();
current = newState;
current.Enter();
}
public void Update() => current?.Update();
}
// ═══ 工厂模式 Factory ═══
public static class EnemyFactory
{
public static Enemy Create(EnemyType type) => type switch
{
EnemyType.Goblin => new Goblin(),
EnemyType.Dragon => new Dragon(),
_ => throw new ArgumentException()
};
}
// ═══ 策略模式 Strategy ═══
public interface IAttackStrategy { void Attack(Transform attacker, Transform target); }
public class MeleeAttack : IAttackStrategy { public void Attack(...) { /* 近战 */ } }
public class RangedAttack : IAttackStrategy { public void Attack(...) { /* 远程 */ } }
12.3 Unity 常用架构
Manager模式(最常用):
├── GameManager ← 游戏流程控制
├── UIManager ← UI管理
├── AudioManager ← 音频管理
├── PoolManager ← 对象池管理
├── SaveManager ← 存档管理
└── EventManager ← 事件管理
推荐框架:
├── QFramework ← 国内主流,轻量级
├── Zenject ← 依赖注入框架
├── UniTask ← 异步方案(替代协程)
└── UniRx ← 响应式编程
第13章:事件系统与消息通信
13.1 三种事件实现方式
csharp
// ═══ 方式1:C# event(性能最好)═══
public class Player : MonoBehaviour
{
public event Action<int> OnHPChanged;
public event Action OnDeath;
public void TakeDamage(int dmg) { hp -= dmg; OnHPChanged?.Invoke(hp); }
}
// 订阅:player.OnHPChanged += UpdateHPBar;
// 取消:player.OnHPChanged -= UpdateHPBar;
// ═══ 方式2:UnityEvent(Inspector可视化绑定)═══
[SerializeField] UnityEvent onDeath;
[SerializeField] UnityEvent<int> onScoreChanged;
// 代码触发:onDeath.Invoke();
// ═══ 方式3:EventBus(全局解耦通信)═══
public static class EventBus
{
static Dictionary<Type, List<object>> handlers = new();
public static void Subscribe<T>(Action<T> handler) { ... }
public static void Unsubscribe<T>(Action<T> handler) { ... }
public static void Publish<T>(T eventData) { ... }
}
// 发布:EventBus.Publish(new EnemyDeadEvent { enemy = this });
// 订阅:EventBus.Subscribe<EnemyDeadEvent>(OnEnemyDead);
// 对比:
// C# event:耦合度中,性能最好,适合组件间通信
// UnityEvent:可视化配置,适合UI交互
// EventBus:完全解耦,适合跨系统通信
第14章:对象池系统
14.1 为什么需要对象池
Instantiate/Destroy 的问题:
1. 每次创建都分配内存 → GC频繁 → 卡顿
2. 内存碎片化 → 内存效率下降
3. 创建/销毁开销 → 帧率下降
对象池方案:
预先创建 → 用时激活 → 用完回收 → 循环复用
14.2 Unity 内置 ObjectPool
csharp
using UnityEngine.Pool;
// Unity 2021+ 内置对象池
ObjectPool<GameObject> bulletPool = new ObjectPool<GameObject>(
createFunc: () => Instantiate(bulletPrefab), // 创建
actionOnGet: obj => obj.SetActive(true), // 取出时
actionOnRelease: obj => obj.SetActive(false), // 归还时
actionOnDestroy: obj => Destroy(obj), // 销毁时
collectionCheck: true, // 防重复归还
defaultCapacity: 20, // 默认容量
maxSize: 50 // 最大容量
);
// 使用
GameObject bullet = bulletPool.Get(); // 取出
bulletPool.Release(bullet); // 归还
14.3 自定义通用对象池
csharp
public class PoolManager : MonoBehaviour
{
public static PoolManager Instance;
private Dictionary<string, Queue<GameObject>> pools = new();
private Dictionary<string, GameObject> prefabs = new();
public void RegisterPool(string key, GameObject prefab, int initSize)
{
prefabs[key] = prefab;
pools[key] = new Queue<GameObject>();
for (int i = 0; i < initSize; i++)
{
var obj = Instantiate(prefab);
obj.SetActive(false);
pools[key].Enqueue(obj);
}
}
public GameObject Spawn(string key, Vector3 pos, Quaternion rot)
{
GameObject obj;
if (pools[key].Count > 0)
obj = pools[key].Dequeue();
else
obj = Instantiate(prefabs[key]);
obj.transform.SetPositionAndRotation(pos, rot);
obj.SetActive(true);
return obj;
}
public void Recycle(string key, GameObject obj)
{
obj.SetActive(false);
pools[key].Enqueue(obj);
}
}
第15章:存档与数据持久化
15.1 PlayerPrefs(简单存储)
csharp
// 存储(只支持 int/float/string)
PlayerPrefs.SetInt("HighScore", 9999);
PlayerPrefs.SetFloat("MusicVolume", 0.8f);
PlayerPrefs.SetString("PlayerName", "Alice");
PlayerPrefs.Save(); // 确保写入磁盘
// 读取(带默认值)
int score = PlayerPrefs.GetInt("HighScore", 0);
// 删除
PlayerPrefs.DeleteKey("HighScore");
PlayerPrefs.DeleteAll();
// ⚠️ 缺点:无加密、容量小、不适合复杂数据
15.2 JSON 存档
csharp
[System.Serializable]
public class SaveData
{
public string playerName;
public int level;
public float hp;
public List<string> inventory;
public Vector3Serializable position;
}
// Vector3不能直接序列化,需要包装
[System.Serializable]
public struct Vector3Serializable
{
public float x, y, z;
public Vector3Serializable(Vector3 v) { x=v.x; y=v.y; z=v.z; }
public Vector3 ToVector3() => new Vector3(x, y, z);
}
// 保存
public static void Save(SaveData data)
{
string json = JsonUtility.ToJson(data, true); // true=格式化
string path = Path.Combine(Application.persistentDataPath, "save.json");
File.WriteAllText(path, json);
}
// 加载
public static SaveData Load()
{
string path = Path.Combine(Application.persistentDataPath, "save.json");
if (!File.Exists(path)) return new SaveData();
string json = File.ReadAllText(path);
return JsonUtility.FromJson<SaveData>(json);
}
15.3 AES 加密存档
csharp
using System.Security.Cryptography;
using System.IO;
using System.Text;
public static class EncryptedSave
{
private static readonly string key = "1234567890ABCDEF"; // 16字节密钥
private static readonly string iv = "ABCDEF1234567890"; // 16字节IV
public static void SaveEncrypted(SaveData data)
{
string json = JsonUtility.ToJson(data);
byte[] encrypted = Encrypt(Encoding.UTF8.GetBytes(json));
string path = Path.Combine(Application.persistentDataPath, "save.dat");
File.WriteAllBytes(path, encrypted);
}
static byte[] Encrypt(byte[] data)
{
using var aes = Aes.Create();
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = Encoding.UTF8.GetBytes(iv);
using var enc = aes.CreateEncryptor();
return enc.TransformFinalBlock(data, 0, data.Length);
}
}
第16章:AI 与寻路系统
16.1 NavMesh 导航
csharp
using UnityEngine.AI;
// NavMeshAgent 组件
NavMeshAgent agent = GetComponent<NavMeshAgent>();
agent.speed = 5f;
agent.stoppingDistance = 1f; // 停止距离
agent.destination = targetPos; // 设置目标位置
// 检查是否到达
if (!agent.pathPending && agent.remainingDistance < agent.stoppingDistance)
Debug.Log("到达目标!");
// 烘焙NavMesh:Window → AI → Navigation → Bake
16.2 有限状态机 FSM
csharp
public class EnemyAI : MonoBehaviour
{
enum AIState { Patrol, Chase, Attack, Flee }
AIState state = AIState.Patrol;
void Update()
{
float distToPlayer = Vector3.Distance(transform.position, player.position);
switch (state)
{
case AIState.Patrol:
PatrolBehavior();
if (distToPlayer < detectRange) state = AIState.Chase;
break;
case AIState.Chase:
agent.destination = player.position;
if (distToPlayer < attackRange) state = AIState.Attack;
if (distToPlayer > loseRange) state = AIState.Patrol;
break;
case AIState.Attack:
AttackBehavior();
if (distToPlayer > attackRange) state = AIState.Chase;
if (hp < maxHP * 0.2f) state = AIState.Flee;
break;
case AIState.Flee:
Vector3 fleeDir = (transform.position - player.position).normalized;
agent.destination = transform.position + fleeDir * 10f;
break;
}
}
}
16.3 行为树概念
行为树节点类型:
├── Composite(组合节点)
│ ├── Sequence:顺序执行,全成功才成功(AND)
│ ├── Selector:选择执行,一个成功就成功(OR)
│ └── Parallel:并行执行
├── Decorator(装饰节点)
│ ├── Inverter:结果取反
│ └── Repeater:重复执行
└── Leaf(叶节点)
├── Action:执行动作(移动/攻击/拾取)
└── Condition:判断条件(是否看到敌人/HP是否低)
第17章:Editor 扩展开发
17.1 Custom Inspector
csharp
using UnityEditor;
[CustomEditor(typeof(EnemyConfig))]
public class EnemyConfigEditor : Editor
{
public override void OnInspectorGUI()
{
EnemyConfig config = (EnemyConfig)target;
EditorGUILayout.LabelField("敌人配置", EditorStyles.boldLabel);
config.enemyName = EditorGUILayout.TextField("名称", config.enemyName);
config.hp = EditorGUILayout.IntSlider("HP", config.hp, 1, 1000);
if (config.hp < 100)
EditorGUILayout.HelpBox("HP过低!", MessageType.Warning);
if (GUILayout.Button("重置默认值"))
config.ResetDefaults();
if (GUI.changed) EditorUtility.SetDirty(config);
}
}
17.2 Editor Window
csharp
public class MyToolWindow : EditorWindow
{
[MenuItem("Tools/My Tool Window")]
public static void ShowWindow()
{
GetWindow<MyToolWindow>("我的工具");
}
void OnGUI()
{
GUILayout.Label("自定义工具窗口", EditorStyles.boldLabel);
if (GUILayout.Button("执行操作"))
Debug.Log("按钮被点击");
}
}
17.3 Gizmos
csharp
// 在Scene视图中绘制调试信息
void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange); // 攻击范围
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, detectRange); // 检测范围
}