常见坑

目录

10. 常见坑

Unity C# 开发中的常见问题和解决方案 - 30+个常见坑和解决方案


📌 本章导航


性能相关问题

1. 频繁的GetComponent调用

问题: 在Update方法中频繁调用GetComponent会导致性能下降。

// ❌ 错误示例 - 性能差
public class BadExample : MonoBehaviour
{
    void Update()
    {
        // 每帧都调用GetComponent - 很慢!
        Renderer renderer = GetComponent<Renderer>();
        if (renderer != null)
        {
            renderer.material.color = Color.red;
        }
    }
}

// ✅ 正确示例 - 性能好
public class GoodExample : MonoBehaviour
{
    private Renderer cachedRenderer;

    void Start()
    {
        // 只在Start时获取一次
        cachedRenderer = GetComponent<Renderer>();
    }

    void Update()
    {
        // 使用缓存的引用
        if (cachedRenderer != null)
        {
            cachedRenderer.material.color = Color.red;
        }
    }
}

解决方案:

  • 在Start或Awake中缓存组件引用
  • 使用字段存储引用而不是重复获取
  • 对于子对象,缓存Transform引用

2. GameObject.Find的滥用

问题: 在Update中使用GameObject.Find会严重影响性能。

// ❌ 错误示例 - 性能极差
public class BadFindExample : MonoBehaviour
{
    void Update()
    {
        // 每帧都在整个场景中查找对象 - 非常慢!
        GameObject player = GameObject.Find("Player");
        if (player != null)
        {
            transform.LookAt(player.transform);
        }
    }
}

// ✅ 正确示例 - 性能好
public class GoodFindExample : MonoBehaviour
{
    private Transform playerTransform;

    void Start()
    {
        // 只在Start时查找一次
        GameObject player = GameObject.Find("Player");
        if (player != null)
        {
            playerTransform = player.transform;
        }
    }

    void Update()
    {
        // 使用缓存的引用
        if (playerTransform != null)
        {
            transform.LookAt(playerTransform);
        }
    }
}

// ✅ 更好的解决方案 - 使用标签
public class BetterTagExample : MonoBehaviour
{
    private Transform playerTransform;

    void Start()
    {
        // 使用标签查找比名称查找稍快
        GameObject player = GameObject.FindGameObjectWithTag("Player");
        if (player != null)
        {
            playerTransform = player.transform;
        }
    }

    void Update()
    {
        if (playerTransform != null)
        {
            transform.LookAt(playerTransform);
        }
    }
}

3. 频繁的对象创建和销毁

问题: 在Update中频繁创建和销毁对象会导致GC压力。

// ❌ 错误示例 - 导致GC压力
public class BadPoolingExample : MonoBehaviour
{
    public GameObject bulletPrefab;

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // 每次射击都创建新对象
            GameObject bullet = Instantiate(bulletPrefab, transform.position, transform.rotation);
            
            // 5秒后销毁
            Destroy(bullet, 5f);
        }
    }
}

// ✅ 正确示例 - 使用对象池
public class GoodPoolingExample : MonoBehaviour
{
    public GameObject bulletPrefab;
    private Queue<GameObject> bulletPool = new Queue<GameObject>();
    private int poolSize = 20;

    void Start()
    {
        // 预先创建对象池
        for (int i = 0; i < poolSize; i++)
        {
            GameObject bullet = Instantiate(bulletPrefab);
            bullet.SetActive(false);
            bulletPool.Enqueue(bullet);
        }
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Shoot();
        }
    }

    void Shoot()
    {
        GameObject bullet;
        if (bulletPool.Count > 0)
        {
            bullet = bulletPool.Dequeue();
            bullet.SetActive(true);
        }
        else
        {
            // 如果池空了,创建新对象(应该避免这种情况)
            bullet = Instantiate(bulletPrefab);
        }

        // 设置子弹属性
        bullet.transform.position = transform.position;
        bullet.transform.rotation = transform.rotation;

        // 子弹使用后返回池中(通过子弹脚本实现)
        BulletController bulletCtrl = bullet.GetComponent<BulletController>();
        if (bulletCtrl != null)
        {
            bulletCtrl.SetPool(bulletPool);
        }
    }
}

// 子弹控制器示例
public class BulletController : MonoBehaviour
{
    private Queue<GameObject> pool;
    private float lifeTime = 5f;
    private float timer = 0f;

    public void SetPool(Queue<GameObject> bulletPool)
    {
        pool = bulletPool;
        timer = 0f;
    }

    void Update()
    {
        timer += Time.deltaTime;
        
        if (timer >= lifeTime)
        {
            ReturnToPool();
        }
    }

    void ReturnToPool()
    {
        if (pool != null)
        {
            gameObject.SetActive(false);
            transform.SetParent(null); // 移除父对象
            pool.Enqueue(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

4. 不必要的字符串操作

问题: 频繁的字符串拼接和操作会导致内存分配。

// ❌ 错误示例 - 频繁字符串操作
public class BadStringExample : MonoBehaviour
{
    private int score = 0;

    void Update()
    {
        // 每帧都创建新的字符串对象
        string scoreText = "Score: " + score + " - Level: " + GetLevel() + " - Time: " + Time.time;
        Debug.Log(scoreText);
    }

    int GetLevel() { return 1; }
}

// ✅ 正确示例 - 使用StringBuilder
public class GoodStringExample : MonoBehaviour
{
    private int score = 0;
    private System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
    private string cachedScoreText = "";

    void Update()
    {
        // 只在必要时更新字符串
        if (ShouldUpdateScoreText())
        {
            UpdateScoreText();
        }
        
        Debug.Log(cachedScoreText);
    }

    bool ShouldUpdateScoreText()
    {
        // 只在分数改变时更新
        return true; // 简化示例
    }

    void UpdateScoreText()
    {
        stringBuilder.Length = 0; // 清空但不重新分配内存
        stringBuilder.Append("Score: ");
        stringBuilder.Append(score);
        stringBuilder.Append(" - Level: ");
        stringBuilder.Append(GetLevel());
        stringBuilder.Append(" - Time: ");
        stringBuilder.Append(Time.time);
        
        cachedScoreText = stringBuilder.ToString();
    }

    int GetLevel() { return 1; }
}

// ✅ 更好的解决方案 - 格式化字符串缓存
public class BetterStringExample : MonoBehaviour
{
    private int score = 0;
    private string cachedScoreText = "";
    private float lastScoreTime = 0f;
    private float updateInterval = 0.5f; // 0.5秒更新一次

    void Update()
    {
        if (Time.time - lastScoreTime >= updateInterval)
        {
            cachedScoreText = string.Format("Score: {0} - Level: {1} - Time: {2:F1}", 
                score, GetLevel(), Time.time);
            lastScoreTime = Time.time;
        }
        
        Debug.Log(cachedScoreText);
    }

    int GetLevel() { return 1; }
}

5. 空引用检查不当

问题: 忘记检查空引用会导致NullReferenceException。

// ❌ 错误示例 - 没有空引用检查
public class BadNullCheckExample : MonoBehaviour
{
    public GameObject target;
    private Renderer renderer;

    void Start()
    {
        // renderer可能为null
        renderer = target.GetComponent<Renderer>();
    }

    void Update()
    {
        // 如果renderer为null,这里会抛出异常
        renderer.material.color = Color.red;
    }
}

// ✅ 正确示例 - 正确的空引用检查
public class GoodNullCheckExample : MonoBehaviour
{
    public GameObject target;
    private Renderer renderer;

    void Start()
    {
        if (target != null)
        {
            renderer = target.GetComponent<Renderer>();
        }
    }

    void Update()
    {
        // 检查renderer是否为null
        if (renderer != null && renderer.material != null)
        {
            renderer.material.color = Color.red;
        }
    }
}

// ✅ 更好的解决方案 - 使用扩展方法
public static class UnityObjectExtensions
{
    public static bool IsNull(this UnityEngine.Object obj)
    {
        return obj == null;
    }

    public static bool IsNotNull(this UnityEngine.Object obj)
    {
        return obj != null;
    }
}

public class BetterNullCheckExample : MonoBehaviour
{
    public GameObject target;
    private Renderer renderer;

    void Start()
    {
        if (target.IsNotNull())
        {
            renderer = target.GetComponent<Renderer>();
        }
    }

    void Update()
    {
        if (renderer.IsNotNull() && renderer.material.IsNotNull())
        {
            renderer.material.color = Color.red;
        }
    }
}

内存管理问题

1. 内存泄漏

问题: 事件订阅后没有取消订阅导致内存泄漏。

// ❌ 错误示例 - 事件订阅导致内存泄漏
public class BadEventExample : MonoBehaviour
{
    void Start()
    {
        // 订阅事件但没有取消订阅
        GameEvents.OnScoreChanged += OnScoreChanged;
    }

    void OnScoreChanged(int newScore)
    {
        Debug.Log($"New score: {newScore}");
    }

    // 没有在OnDestroy中取消订阅!
}

// ✅ 正确示例 - 正确处理事件订阅
public class GoodEventExample : MonoBehaviour
{
    void Start()
    {
        GameEvents.OnScoreChanged += OnScoreChanged;
    }

    void OnScoreChanged(int newScore)
    {
        Debug.Log($"New score: {newScore}");
    }

    void OnDestroy()
    {
        // 重要:在对象销毁时取消事件订阅
        GameEvents.OnScoreChanged -= OnScoreChanged;
    }
}

// ✅ 更好的解决方案 - 使用Action字段
public class BetterEventExample : MonoBehaviour
{
    private System.Action<int> scoreChangedCallback;

    void Start()
    {
        scoreChangedCallback = OnScoreChanged;
        GameEvents.OnScoreChanged += scoreChangedCallback;
    }

    void OnScoreChanged(int newScore)
    {
        Debug.Log($"New score: {newScore}");
    }

    void OnDestroy()
    {
        // 取消订阅
        if (GameEvents.OnScoreChanged != null)
        {
            GameEvents.OnScoreChanged -= scoreChangedCallback;
        }
    }
}

// 全局事件系统示例
public static class GameEvents
{
    public static System.Action<int> OnScoreChanged;
    public static System.Action<string> OnPlayerDeath;
    public static System.Action OnGameStart;
    public static System.Action OnGameEnd;
}

2. 协程没有正确停止

问题: 协程启动后没有正确停止导致内存泄漏。

// ❌ 错误示例 - 协程没有正确停止
public class BadCoroutineExample : MonoBehaviour
{
    void Start()
    {
        // 启动协程但没有保存引用
        StartCoroutine(RunForever());
    }

    System.Collections.IEnumerator RunForever()
    {
        while (true)
        {
            Debug.Log("Running...");
            yield return new WaitForSeconds(1f);
        }
    }

    // 没有停止协程!
}

// ✅ 正确示例 - 正确管理协程
public class GoodCoroutineExample : MonoBehaviour
{
    private System.Collections.IEnumerator runningCoroutine;

    void Start()
    {
        // 保存协程引用
        runningCoroutine = RunForever();
        StartCoroutine(runningCoroutine);
    }

    System.Collections.IEnumerator RunForever()
    {
        while (true)
        {
            Debug.Log("Running...");
            yield return new WaitForSeconds(1f);
        }
    }

    void OnDestroy()
    {
        // 停止协程
        if (runningCoroutine != null)
        {
            StopCoroutine(runningCoroutine);
        }
    }
}

// ✅ 更好的解决方案 - 使用Coroutine管理器
public class BetterCoroutineExample : MonoBehaviour
{
    private List<Coroutine> activeCoroutines = new List<Coroutine>();

    void Start()
    {
        StartManagedCoroutine(RunForever());
    }

    System.Collections.IEnumerator RunForever()
    {
        while (true)
        {
            Debug.Log("Running...");
            yield return new WaitForSeconds(1f);
        }
    }

    Coroutine StartManagedCoroutine(System.Collections.IEnumerator routine)
    {
        Coroutine coroutine = StartCoroutine(routine);
        activeCoroutines.Add(coroutine);
        return coroutine;
    }

    void StopManagedCoroutine(Coroutine coroutine)
    {
        if (activeCoroutines.Contains(coroutine))
        {
            StopCoroutine(coroutine);
            activeCoroutines.Remove(coroutine);
        }
    }

    void OnDestroy()
    {
        // 停止所有协程
        foreach (Coroutine coroutine in activeCoroutines.ToArray())
        {
            if (coroutine != null)
            {
                StopCoroutine(coroutine);
            }
        }
        activeCoroutines.Clear();
    }
}

3. 资源没有正确释放

问题: 加载的资源没有正确释放导致内存泄漏。

// ❌ 错误示例 - 资源没有释放
public class BadResourceExample : MonoBehaviour
{
    void Start()
    {
        // 加载资源但没有保存引用以供释放
        Texture2D texture = Resources.Load<Texture2D>("Textures/MyTexture");
        Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 0.5f);
    }

    // 没有释放资源!
}

// ✅ 正确示例 - 正确管理资源
public class GoodResourceExample : MonoBehaviour
{
    private List<UnityEngine.Object> loadedAssets = new List<UnityEngine.Object>();

    void Start()
    {
        LoadResources();
    }

    void LoadResources()
    {
        Texture2D texture = Resources.Load<Texture2D>("Textures/MyTexture");
        if (texture != null)
        {
            loadedAssets.Add(texture);
            Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 0.5f);
            loadedAssets.Add(sprite);
        }
    }

    void OnDestroy()
    {
        // 释放所有加载的资源
        foreach (UnityEngine.Object asset in loadedAssets)
        {
            if (asset != null)
            {
                Resources.UnloadAsset(asset);
            }
        }
        loadedAssets.Clear();
    }
}

// ✅ 更好的解决方案 - 资源管理器
public class ResourceManager : MonoBehaviour
{
    private Dictionary<string, UnityEngine.Object> loadedAssets = new Dictionary<string, UnityEngine.Object>();
    private Dictionary<string, int> referenceCounts = new Dictionary<string, int>();

    public T LoadAsset<T>(string path) where T : UnityEngine.Object
    {
        if (loadedAssets.ContainsKey(path))
        {
            // 增加引用计数
            referenceCounts[path]++;
            return loadedAssets[path] as T;
        }

        T asset = Resources.Load<T>(path);
        if (asset != null)
        {
            loadedAssets[path] = asset;
            referenceCounts[path] = 1;
        }

        return asset;
    }

    public void UnloadAsset(string path)
    {
        if (referenceCounts.ContainsKey(path))
        {
            referenceCounts[path]--;
            
            if (referenceCounts[path] <= 0)
            {
                if (loadedAssets.ContainsKey(path))
                {
                    Resources.UnloadAsset(loadedAssets[path]);
                    loadedAssets.Remove(path);
                    referenceCounts.Remove(path);
                }
            }
        }
    }

    void OnDestroy()
    {
        // 清理所有资源
        foreach (var asset in loadedAssets.Values)
        {
            if (asset != null)
            {
                Resources.UnloadAsset(asset);
            }
        }
        loadedAssets.Clear();
        referenceCounts.Clear();
    }
}

4. 装箱和拆箱问题

问题: 频繁的装箱和拆箱操作影响性能。

// ❌ 错误示例 - 频繁装箱
public class BadBoxingExample : MonoBehaviour
{
    void Start()
    {
        // 装箱操作 - 性能差
        List<object> mixedList = new List<object>();
        mixedList.Add(42);        // int装箱为object
        mixedList.Add(3.14f);     // float装箱为object
        mixedList.Add("string");  // string不装箱,但列表是object类型

        // 拆箱操作 - 性能差
        int value = (int)mixedList[0]; // 拆箱
    }
}

// ✅ 正确示例 - 使用泛型避免装箱
public class GoodBoxingExample : MonoBehaviour
{
    void Start()
    {
        // 使用泛型避免装箱
        List<int> intList = new List<int>();
        intList.Add(42);    // 不装箱
        intList.Add(100);   // 不装箱

        List<float> floatList = new List<float>();
        floatList.Add(3.14f);   // 不装箱

        List<string> stringList = new List<string>();
        stringList.Add("string");   // 不装箱
    }
}

// ✅ 使用结构体避免装箱(适用于小对象)
[System.Serializable]
public struct PositionData
{
    public Vector3 position;
    public Quaternion rotation;
    public int id;

    public PositionData(Vector3 pos, Quaternion rot, int id)
    {
        position = pos;
        rotation = rot;
        this.id = id;
    }
}

public class StructExample : MonoBehaviour
{
    void Start()
    {
        // 结构体存储在栈上,避免装箱
        PositionData data = new PositionData(Vector3.zero, Quaternion.identity, 1);
        List<PositionData> dataList = new List<PositionData>();
        dataList.Add(data); // 不装箱
    }
}

Unity特定问题

1. Transform.position vs Transform.localPosition

问题: 混淆世界坐标和本地坐标。

// ❌ 错误示例 - 混淆坐标系统
public class BadTransformExample : MonoBehaviour
{
    public Transform parent;
    public Vector3 localOffset = Vector3.one;

    void Update()
    {
        // 错误:试图在父对象旋转时保持本地偏移
        // 这会导致位置计算错误
        transform.position = parent.position + localOffset;
    }
}

// ✅ 正确示例 - 正确使用本地坐标
public class GoodTransformExample : MonoBehaviour
{
    public Transform parent;
    public Vector3 localOffset = Vector3.one;

    void Update()
    {
        // 正确:使用本地坐标
        transform.localPosition = localOffset;
    }
}

// ✅ 更好的解决方案 - 动态本地偏移
public class BetterTransformExample : MonoBehaviour
{
    public Transform parent;
    public Vector3 localOffset = Vector3.one;

    void Update()
    {
        // 如果需要动态计算本地偏移
        transform.position = parent.TransformPoint(localOffset);
    }
}

2. Update vs FixedUpdate vs LateUpdate

问题: 在错误的更新方法中执行操作。

// ❌ 错误示例 - 在Update中处理物理
public class BadPhysicsExample : MonoBehaviour
{
    public Rigidbody rb;
    public float force = 10f;

    void Update()
    {
        // 错误:在Update中应用物理力
        // Update的帧率不稳定,物理计算会不准确
        if (Input.GetKeyDown(KeyCode.Space))
        {
            rb.AddForce(Vector3.up * force, ForceMode.Impulse);
        }
    }
}

// ✅ 正确示例 - 在FixedUpdate中处理物理
public class GoodPhysicsExample : MonoBehaviour
{
    public Rigidbody rb;
    public float force = 10f;

    void FixedUpdate()
    {
        // 正确:在FixedUpdate中应用物理力
        // FixedUpdate以固定时间间隔调用,物理计算更准确
        if (Input.GetKeyDown(KeyCode.Space))
        {
            rb.AddForce(Vector3.up * force, ForceMode.Impulse);
        }
    }
}

// ✅ Update、FixedUpdate、LateUpdate的正确使用
public class UpdateTimingExample : MonoBehaviour
{
    private Transform playerTransform;
    private Camera mainCamera;

    void Start()
    {
        mainCamera = Camera.main;
    }

    void Update()
    {
        // Update: 处理输入、动画、UI更新等
        // 帧率可变,适合处理与帧率相关的逻辑
        HandleInput();
        UpdateAnimation();
    }

    void FixedUpdate()
    {
        // FixedUpdate: 处理物理、刚体操作等
        // 固定时间间隔,适合物理计算
        ApplyPhysicsForces();
    }

    void LateUpdate()
    {
        // LateUpdate: 处理跟随相机、后处理等
        // 在所有Update和FixedUpdate之后执行
        FollowPlayer();
    }

    void HandleInput()
    {
        // 处理玩家输入
    }

    void UpdateAnimation()
    {
        // 更新动画参数
    }

    void ApplyPhysicsForces()
    {
        // 应用力和扭矩
    }

    void FollowPlayer()
    {
        // 相机跟随逻辑
        if (mainCamera != null && playerTransform != null)
        {
            mainCamera.transform.position = playerTransform.position + Vector3.back * 10f;
        }
    }
}

3. 协程中的Time.time vs Time.deltaTime

问题: 在协程中错误使用时间函数。

// ❌ 错误示例 - 在协程中使用Time.time进行等待
public class BadCoroutineTimeExample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(WaitUsingTime());
    }

    System.Collections.IEnumerator WaitUsingTime()
    {
        float startTime = Time.time;
        float waitDuration = 2f;

        // 错误:这种等待方式不够精确
        while (Time.time - startTime < waitDuration)
        {
            // 空循环,效率低
            yield return null;
        }

        Debug.Log("Wait completed");
    }
}

// ✅ 正确示例 - 在协程中使用WaitForSeconds
public class GoodCoroutineTimeExample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(WaitUsingWaitForSeconds());
    }

    System.Collections.IEnumerator WaitUsingWaitForSeconds()
    {
        // 正确:使用Unity内置的等待函数
        yield return new WaitForSeconds(2f);
        Debug.Log("Wait completed");
    }
}

// ✅ 更好的解决方案 - 使用时间累积
public class BetterCoroutineTimeExample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(WaitWithAccumulatedTime());
    }

    System.Collections.IEnumerator WaitWithAccumulatedTime()
    {
        float elapsedTime = 0f;
        float waitDuration = 2f;

        while (elapsedTime < waitDuration)
        {
            elapsedTime += Time.deltaTime;
            yield return null;
        }

        Debug.Log("Wait completed");
    }
}

4. 单例模式的常见错误

问题: 单例模式实现不当导致问题。

// ❌ 错误示例 - 简单的单例实现
public class BadSingletonExample : MonoBehaviour
{
    public static BadSingletonExample Instance;

    void Awake()
    {
        // 问题1: 没有检查是否已存在实例
        // 问题2: 没有防止重复创建
        Instance = this;
    }
}

// ❌ 另一个错误示例 - 没有防止重复创建
public class AnotherBadSingletonExample : MonoBehaviour
{
    public static AnotherBadSingletonExample Instance;

    void Awake()
    {
        // 如果场景中有多个该组件的实例,都会执行这个
        Instance = this;
        // 这会导致后面的实例覆盖前面的实例
    }
}

// ✅ 正确示例 - 线程安全的单例
public class GoodSingletonExample : MonoBehaviour
{
    private static GoodSingletonExample _instance;
    private static readonly object _lock = new object();
    private static bool _applicationIsQuitting = false;

    public static GoodSingletonExample Instance
    {
        get
        {
            if (_applicationIsQuitting)
            {
                Debug.LogWarning("单例在应用程序退出时被访问!");
                return null;
            }

            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = FindObjectOfType<GoodSingletonExample>();
                    
                    if (_instance == null)
                    {
                        GameObject singletonObject = new GameObject("GoodSingletonExample");
                        _instance = singletonObject.AddComponent<GoodSingletonExample>();
                    }
                    
                    if (_instance != null)
                    {
                        DontDestroyOnLoad(_instance);
                    }
                }
                
                return _instance;
            }
        }
    }

    void Awake()
    {
        if (_instance != null && _instance != this)
        {
            // 如果已经存在实例,销毁新的实例
            Destroy(gameObject);
            return;
        }

        _instance = this;
        DontDestroyOnLoad(gameObject);
    }

    void OnDestroy()
    {
        if (_instance == this)
        {
            _applicationIsQuitting = true;
        }
    }
}

// ✅ 泛型单例基类 - 可复用的单例实现
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    private static readonly object _lock = new object();
    private static bool _applicationIsQuitting = false;

    public static T Instance
    {
        get
        {
            if (_applicationIsQuitting)
            {
                Debug.LogWarning($"单例 {typeof(T)} 在应用程序退出时被访问!");
                return null;
            }

            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = FindObjectOfType<T>();
                    
                    if (_instance == null)
                    {
                        GameObject singletonObject = new GameObject(typeof(T).Name);
                        _instance = singletonObject.AddComponent<T>();
                    }
                    
                    if (_instance != null)
                    {
                        DontDestroyOnLoad(_instance);
                    }
                }
                
                return _instance;
            }
        }
    }

    protected virtual void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(gameObject);
            return;
        }

        _instance = this;
        DontDestroyOnLoad(gameObject);
    }

    protected virtual void OnDestroy()
    {
        if (_instance == this)
        {
            _applicationIsQuitting = true;
        }
    }
}

// 使用泛型单例
public class GameManager : Singleton<GameManager>
{
    public int score = 0;
    public int lives = 3;

    public void AddScore(int points)
    {
        score += points;
    }
}

C#语言陷阱

1. 值类型 vs 引用类型

问题: 混淆值类型和引用类型的行为。

// ❌ 错误示例 - 对值类型的误解
public class ValueTypeMistakeExample : MonoBehaviour
{
    void Start()
    {
        int x = 10;
        int y = x;  // 值复制
        y = 20;     // 只改变y,不影响x
        
        Debug.Log($"x: {x}, y: {y}"); // x: 10, y: 20 - 正确

        // 但是对引用类型:
        List<int> list1 = new List<int> { 1, 2, 3 };
        List<int> list2 = list1;  // 引用复制,指向同一个对象
        list2.Add(4);  // 同时影响list1和list2
        
        Debug.Log($"list1: {string.Join(",", list1)}"); // list1: 1,2,3,4
        Debug.Log($"list2: {string.Join(",", list2)}"); // list2: 1,2,3,4
    }
}

// ✅ 正确示例 - 理解值类型和引用类型
public class ValueTypeCorrectExample : MonoBehaviour
{
    void Start()
    {
        // 值类型示例
        int x = 10;
        int y = x;  // 完全独立的副本
        y = 20;
        Debug.Log($"x: {x}, y: {y}"); // x: 10, y: 20

        // 引用类型示例 - 创建独立副本
        List<int> list1 = new List<int> { 1, 2, 3 };
        List<int> list2 = new List<int>(list1);  // 创建新列表,复制元素
        list2.Add(4);  // 只影响list2
        
        Debug.Log($"list1: {string.Join(",", list1)}"); // list1: 1,2,3
        Debug.Log($"list2: {string.Join(",", list2)}"); // list2: 1,2,3,4
    }
}

// ✅ 结构体作为值类型
public struct Point
{
    public int x, y;

    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

public class StructExample : MonoBehaviour
{
    void Start()
    {
        Point p1 = new Point(1, 2);
        Point p2 = p1;  // 值复制
        p2.x = 10;      // 只改变p2,不影响p1
        
        Debug.Log($"p1: ({p1.x}, {p1.y})"); // p1: (1, 2)
        Debug.Log($"p2: ({p2.x}, {p2.y})"); // p2: (10, 2)
    }
}

2. 字符串不可变性

问题: 忽略字符串的不可变性导致性能问题。

// ❌ 错误示例 - 频繁字符串拼接
public class BadStringExample : MonoBehaviour
{
    void Start()
    {
        string result = "";
        
        // 每次拼接都创建新的字符串对象
        for (int i = 0; i < 1000; i++)
        {
            result += "Item " + i + ", ";  // 每次都创建新字符串
        }
        
        Debug.Log(result);
    }
}

// ✅ 正确示例 - 使用StringBuilder
public class GoodStringExample : MonoBehaviour
{
    void Start()
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        
        for (int i = 0; i < 1000; i++)
        {
            sb.Append("Item ");
            sb.Append(i);
            sb.Append(", ");
        }
        
        string result = sb.ToString();
        Debug.Log(result);
    }
}

// ✅ 使用string.Join替代循环拼接
public class BetterStringExample : MonoBehaviour
{
    void Start()
    {
        List<string> items = new List<string>();
        
        for (int i = 0; i < 1000; i++)
        {
            items.Add($"Item {i}");
        }
        
        string result = string.Join(", ", items);
        Debug.Log(result);
    }
}

3. Lambda表达式和闭包陷阱

问题: Lambda表达式中的闭包陷阱。

// ❌ 错误示例 - 循环中的闭包陷阱
public class BadClosureExample : MonoBehaviour
{
    void Start()
    {
        List<System.Action> actions = new List<System.Action>();

        // 问题:所有Lambda都捕获同一个变量i
        for (int i = 0; i < 5; i++)
        {
            actions.Add(() => Debug.Log($"Value: {i}"));  // 所有Lambda都引用同一个i
        }

        // 执行时,i的值是5(循环结束后的值)
        foreach (var action in actions)
        {
            action();  // 输出5次 "Value: 5"
        }
    }
}

// ✅ 正确示例 - 解决闭包陷阱
public class GoodClosureExample : MonoBehaviour
{
    void Start()
    {
        List<System.Action> actions = new List<System.Action>();

        for (int i = 0; i < 5; i++)
        {
            int localCopy = i;  // 创建局部副本
            actions.Add(() => Debug.Log($"Value: {localCopy}"));  // 捕获局部副本
        }

        foreach (var action in actions)
        {
            action();  // 正确输出 "Value: 0", "Value: 1", ...
        }
    }
}

// ✅ 使用方法组避免闭包
public class BetterClosureExample : MonoBehaviour
{
    void Start()
    {
        List<System.Action> actions = new List<System.Action>();

        for (int i = 0; i < 5; i++)
        {
            actions.Add(() => LogValue(i));  // 传递i的当前值
        }

        foreach (var action in actions)
        {
            action();
        }
    }

    void LogValue(int value)
    {
        Debug.Log($"Value: {value}");
    }
}

4. 数组越界和空引用

问题: 没有正确处理数组边界和空引用。

// ❌ 错误示例 - 没有边界检查
public class BadArrayExample : MonoBehaviour
{
    public int[] numbers = { 1, 2, 3 };

    void Start()
    {
        // 问题1: 可能的数组越界
        Debug.Log(numbers[5]);  // 索引超出范围,抛出异常

        // 问题2: 没有检查空引用
        int[] nullArray = null;
        Debug.Log(nullArray.Length);  // 空引用异常
    }
}

// ✅ 正确示例 - 安全的数组访问
public class GoodArrayExample : MonoBehaviour
{
    public int[] numbers = { 1, 2, 3 };

    void Start()
    {
        // 安全访问数组
        if (numbers != null && numbers.Length > 0)
        {
            if (numbers.Length > 5)
            {
                Debug.Log(numbers[5]);
            }
            else
            {
                Debug.LogWarning("数组长度不足,无法访问索引5");
            }
        }

        // 安全处理可能为空的数组
        int[] arrayToCheck = GetArray();
        if (arrayToCheck != null)
        {
            Debug.Log($"数组长度: {arrayToCheck.Length}");
        }
        else
        {
            Debug.LogWarning("数组为空");
        }
    }

    int[] GetArray()
    {
        // 某些条件下可能返回null
        return null;
    }
}

// ✅ 使用扩展方法提供安全访问
public static class ArrayExtensions
{
    public static T SafeGet<T>(this T[] array, int index, T defaultValue = default(T))
    {
        if (array != null && index >= 0 && index < array.Length)
        {
            return array[index];
        }
        return defaultValue;
    }

    public static bool IsValidIndex<T>(this T[] array, int index)
    {
        return array != null && index >= 0 && index < array.Length;
    }
}

public class SafeArrayExample : MonoBehaviour
{
    void Start()
    {
        int[] numbers = { 1, 2, 3 };

        // 安全访问
        int value = numbers.SafeGet(5, -1);  // 返回默认值-1
        Debug.Log($"安全访问结果: {value}");

        // 检查索引有效性
        if (numbers.IsValidIndex(2))
        {
            Debug.Log($"有效索引访问: {numbers[2]}");
        }
    }
}

多线程和异步问题

1. Unity主线程限制

问题: 在非主线程访问Unity API。

// ❌ 错误示例 - 在后台线程访问Unity API
public class BadThreadingExample : MonoBehaviour
{
    void Start()
    {
        System.Threading.Thread thread = new System.Threading.Thread(BackgroundWork);
        thread.Start();
    }

    void BackgroundWork()
    {
        // 错误:在后台线程访问Unity API
        // 这会导致未定义行为和崩溃
        transform.position = Vector3.one;  // 不能在后台线程执行
        Debug.Log("This is dangerous!");   // Debug.Log也不是线程安全的
    }
}

// ✅ 正确示例 - 使用协程或主线程回调
public class GoodThreadingExample : MonoBehaviour
{
    private Queue<System.Action> mainThreadActions = new Queue<System.Action>();
    private object lockObject = new object();

    void Start()
    {
        System.Threading.Thread thread = new System.Threading.Thread(BackgroundWork);
        thread.Start();
    }

    void BackgroundWork()
    {
        // 在后台线程执行耗时操作
        string result = ExpensiveOperation();

        // 将需要在主线程执行的代码放入队列
        lock (lockObject)
        {
            mainThreadActions.Enqueue(() => {
                // 在主线程执行Unity API调用
                transform.position = Vector3.one;
                Debug.Log($"Result: {result}");
            });
        }
    }

    void Update()
    {
        // 在主线程处理后台线程的结果
        lock (lockObject)
        {
            while (mainThreadActions.Count > 0)
            {
                System.Action action = mainThreadActions.Dequeue();
                action?.Invoke();
            }
        }
    }

    string ExpensiveOperation()
    {
        // 模拟耗时操作
        System.Threading.Thread.Sleep(1000);
        return "Operation completed";
    }
}

// ✅ 使用UnityWebRequest异步操作
public class AsyncWebRequestExample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(FetchDataAsync());
    }

    System.Collections.IEnumerator FetchDataAsync()
    {
        using (UnityWebRequest request = UnityWebRequest.Get("https://api.example.com/data"))
        {
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.Success)
            {
                // 在主线程处理结果
                string result = request.downloadHandler.text;
                ProcessResult(result);
            }
            else
            {
                Debug.LogError($"Request failed: {request.error}");
            }
        }
    }

    void ProcessResult(string result)
    {
        // 在主线程处理结果
        Debug.Log($"Received: {result}");
    }
}

2. 异步操作的正确处理

问题: 异步操作没有正确处理完成状态。

// ❌ 错误示例 - 没有正确处理异步操作
public class BadAsyncExample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(LoadDataAsync());
        // 立即使用数据,但异步操作可能还没完成
        UseData();  // 数据可能还没加载完成!
    }

    System.Collections.IEnumerator LoadDataAsync()
    {
        // 模拟异步加载
        yield return new WaitForSeconds(2f);
        Debug.Log("Data loaded");
    }

    void UseData()
    {
        // 这里使用数据,但数据可能还没准备好
        Debug.Log("Using data...");
    }
}

// ✅ 正确示例 - 正确处理异步操作
public class GoodAsyncExample : MonoBehaviour
{
    private bool dataLoaded = false;
    private string loadedData = "";

    void Start()
    {
        StartCoroutine(LoadDataAsync());
    }

    System.Collections.IEnumerator LoadDataAsync()
    {
        // 模拟异步加载
        yield return new WaitForSeconds(2f);
        loadedData = "Important data";
        dataLoaded = true;
        
        // 数据加载完成后执行依赖操作
        OnDataLoaded();
    }

    void OnDataLoaded()
    {
        // 数据加载完成后的回调
        UseData();
    }

    void UseData()
    {
        if (dataLoaded)
        {
            Debug.Log($"Using data: {loadedData}");
        }
        else
        {
            Debug.LogWarning("Data not loaded yet!");
        }
    }
}

// ✅ 使用async/await (需要Unity 2018.3+和.NET 4.x)
public class ModernAsyncExample : MonoBehaviour
{
    async void Start()
    {
        string data = await LoadDataAsync();
        UseData(data);
    }

    async System.Threading.Tasks.Task<string> LoadDataAsync()
    {
        // 模拟异步操作
        await System.Threading.Tasks.Task.Delay(2000);
        return "Async data loaded";
    }

    void UseData(string data)
    {
        Debug.Log($"Using async data: {data}");
    }
}

UI系统问题

1. UI更新性能问题

问题: 频繁更新UI组件导致性能下降。

// ❌ 错误示例 - 频繁更新UI
public class BadUIUpdateExample : MonoBehaviour
{
    public Text scoreText;
    public Slider healthSlider;
    public Image healthBarFill;

    void Update()
    {
        // 每帧都更新UI组件,即使值没有改变
        scoreText.text = "Score: " + GetScore();  // 每帧创建新字符串
        healthSlider.value = GetHealthPercentage();
        healthBarFill.fillAmount = GetHealthPercentage();
    }

    int GetScore() { return 100; }
    float GetHealthPercentage() { return 0.5f; }
}

// ✅ 正确示例 - 优化UI更新
public class GoodUIUpdateExample : MonoBehaviour
{
    public Text scoreText;
    public Slider healthSlider;
    public Image healthBarFill;

    private int lastScore = -1;
    private float lastHealthPercentage = -1f;
    private string cachedScoreText = "";

    void Update()
    {
        UpdateScoreUI();
        UpdateHealthUI();
    }

    void UpdateScoreUI()
    {
        int currentScore = GetScore();
        if (lastScore != currentScore)
        {
            lastScore = currentScore;
            cachedScoreText = "Score: " + currentScore;
            scoreText.text = cachedScoreText;
        }
    }

    void UpdateHealthUI()
    {
        float currentHealth = GetHealthPercentage();
        if (Mathf.Abs(lastHealthPercentage - currentHealth) > 0.01f) // 1%的变化阈值
        {
            lastHealthPercentage = currentHealth;
            healthSlider.value = currentHealth;
            healthBarFill.fillAmount = currentHealth;
        }
    }

    int GetScore() { return 100; }
    float GetHealthPercentage() { return 0.5f; }
}

// ✅ 使用UI更新管理器
public class UIUpdateManager : MonoBehaviour
{
    private List<IUIUpdatable> updatableComponents = new List<IUIUpdatable>();
    private float updateInterval = 0.1f; // 100ms更新一次
    private float lastUpdateTime = 0f;

    void Update()
    {
        if (Time.time - lastUpdateTime >= updateInterval)
        {
            UpdateAllUI();
            lastUpdateTime = Time.time;
        }
    }

    void UpdateAllUI()
    {
        foreach (IUIUpdatable updatable in updatableComponents)
        {
            updatable.UpdateUI();
        }
    }

    public void RegisterUpdatable(IUIUpdatable updatable)
    {
        if (!updatableComponents.Contains(updatable))
        {
            updatableComponents.Add(updatable);
        }
    }

    public void UnregisterUpdatable(IUIUpdatable updatable)
    {
        updatableComponents.Remove(updatable);
    }
}

public interface IUIUpdatable
{
    void UpdateUI();
}

public class OptimizedScoreUI : MonoBehaviour, IUIUpdatable
{
    public Text scoreText;
    private int currentScore = 0;
    private int lastDisplayedScore = -1;

    void Start()
    {
        UIUpdateManager manager = FindObjectOfType<UIUpdateManager>();
        if (manager != null)
        {
            manager.RegisterUpdatable(this);
        }
    }

    public void SetScore(int score)
    {
        currentScore = score;
    }

    public void UpdateUI()
    {
        if (lastDisplayedScore != currentScore)
        {
            lastDisplayedScore = currentScore;
            if (scoreText != null)
            {
                scoreText.text = "Score: " + currentScore;
            }
        }
    }

    void OnDestroy()
    {
        UIUpdateManager manager = FindObjectOfType<UIUpdateManager>();
        if (manager != null)
        {
            manager.UnregisterUpdatable(this);
        }
    }
}

2. UI事件处理陷阱

问题: UI事件处理不当导致内存泄漏或性能问题。

// ❌ 错误示例 - UI事件处理陷阱
public class BadUIEventExample : MonoBehaviour
{
    public Button myButton;

    void Start()
    {
        // 问题1: 多次添加相同事件处理器
        myButton.onClick.AddListener(OnClick);
        myButton.onClick.AddListener(OnClick); // 重复添加!
    }

    void OnClick()
    {
        Debug.Log("Button clicked");
    }

    void OnDestroy()
    {
        // 问题2: 没有移除事件监听器
        // 这可能导致内存泄漏
    }
}

// ✅ 正确示例 - 正确处理UI事件
public class GoodUIEventExample : MonoBehaviour
{
    public Button myButton;
    private bool eventListenerAdded = false;

    void Start()
    {
        if (!eventListenerAdded)
        {
            myButton.onClick.AddListener(OnClick);
            eventListenerAdded = true;
        }
    }

    void OnClick()
    {
        Debug.Log("Button clicked");
    }

    void OnDestroy()
    {
        // 移除事件监听器防止内存泄漏
        if (eventListenerAdded)
        {
            myButton.onClick.RemoveListener(OnClick);
            eventListenerAdded = false;
        }
    }
}

// ✅ 使用事件管理器
public class UIEventManager : MonoBehaviour
{
    private Dictionary<GameObject, List<System.Action>> eventHandlers = 
        new Dictionary<GameObject, List<System.Action>>();

    public void AddButtonListener(Button button, System.Action onClick)
    {
        if (!eventHandlers.ContainsKey(button.gameObject))
        {
            eventHandlers[button.gameObject] = new List<System.Action>();
        }

        if (!eventHandlers[button.gameObject].Contains(onClick))
        {
            button.onClick.AddListener(onClick);
            eventHandlers[button.gameObject].Add(onClick);
        }
    }

    public void RemoveButtonListener(Button button, System.Action onClick)
    {
        if (eventHandlers.ContainsKey(button.gameObject))
        {
            button.onClick.RemoveListener(onClick);
            eventHandlers[button.gameObject].Remove(onClick);
        }
    }

    void OnDestroy()
    {
        // 清理所有事件监听器
        foreach (var pair in eventHandlers)
        {
            Button button = pair.Key.GetComponent<Button>();
            if (button != null)
            {
                foreach (System.Action handler in pair.Value)
                {
                    button.onClick.RemoveListener(handler);
                }
            }
        }
        eventHandlers.Clear();
    }
}

音频系统问题

1. 音频播放性能问题

问题: 频繁播放音频导致性能下降。

// ❌ 错误示例 - 频繁播放音频
public class BadAudioExample : MonoBehaviour
{
    public AudioClip footstepSound;

    void Update()
    {
        // 每帧都检查并可能播放音频
        if (Input.GetKeyDown(KeyCode.Space))
        {
            AudioSource.PlayClipAtPoint(footstepSound, transform.position); // 每次都创建新的AudioSource
        }
    }
}

// ✅ 正确示例 - 优化音频播放
public class GoodAudioExample : MonoBehaviour
{
    public AudioClip footstepSound;
    private AudioSource audioSource;

    void Start()
    {
        // 预先创建AudioSource
        audioSource = gameObject.AddComponent<AudioSource>();
        audioSource.playOnAwake = false;
        audioSource.spatialBlend = 1f; // 3D音频
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            PlayAudio();
        }
    }

    void PlayAudio()
    {
        if (audioSource != null && footstepSound != null)
        {
            // 重用AudioSource
            audioSource.clip = footstepSound;
            audioSource.Play();
        }
    }
}

// ✅ 使用音频对象池
public class AudioPoolExample : MonoBehaviour
{
    private Queue<AudioSource> audioSourcePool = new Queue<AudioSource>();
    private int poolSize = 10;

    void Start()
    {
        // 预创建音频源池
        for (int i = 0; i < poolSize; i++)
        {
            GameObject audioGO = new GameObject($"AudioSource_{i}");
            audioGO.transform.SetParent(transform);
            AudioSource audioSource = audioGO.AddComponent<AudioSource>();
            audioSource.playOnAwake = false;
            audioSourcePool.Enqueue(audioSource);
        }
    }

    public void PlaySound(AudioClip clip, Vector3 position)
    {
        AudioSource audioSource;
        
        if (audioSourcePool.Count > 0)
        {
            audioSource = audioSourcePool.Dequeue();
            audioSource.transform.position = position;
        }
        else
        {
            // 如果池空了,创建新的(应该避免这种情况)
            GameObject audioGO = new GameObject("AudioSource_Dynamic");
            audioGO.transform.SetParent(transform);
            audioSource = audioGO.AddComponent<AudioSource>();
        }

        audioSource.clip = clip;
        audioSource.Play();

        // 音频播放完成后返回池中
        StartCoroutine(ReturnToPoolAfterPlay(audioSource, clip.length));
    }

    System.Collections.IEnumerator ReturnToPoolAfterPlay(AudioSource audioSource, float clipLength)
    {
        yield return new WaitForSeconds(clipLength + 0.1f); // 额外等待确保播放完成
        
        audioSource.clip = null;
        audioSource.Stop();
        audioSource.transform.SetParent(transform); // 重置父对象
        audioSourcePool.Enqueue(audioSource);
    }
}

2. 音频资源管理

问题: 音频资源没有正确管理导致内存泄漏。

// ❌ 错误示例 - 音频资源管理不当
public class BadAudioResourceExample : MonoBehaviour
{
    void Start()
    {
        // 直接加载音频资源,没有引用管理
        AudioClip clip = Resources.Load<AudioClip>("Audio/MySound");
        // clip没有被保存引用,可能被GC回收
    }
}

// ✅ 正确示例 - 正确管理音频资源
public class GoodAudioResourceExample : MonoBehaviour
{
    private AudioClip loadedClip;
    private List<AudioClip> audioClips = new List<AudioClip>();

    void Start()
    {
        // 正确加载并保存引用
        loadedClip = Resources.Load<AudioClip>("Audio/MySound");
        if (loadedClip != null)
        {
            audioClips.Add(loadedClip);
        }
    }

    void OnDestroy()
    {
        // 释放音频资源
        foreach (AudioClip clip in audioClips)
        {
            if (clip != null)
            {
                Resources.UnloadAsset(clip);
            }
        }
        audioClips.Clear();
    }
}

动画系统问题

1. 动画状态机陷阱

问题: 动画状态机配置不当导致问题。

// ❌ 错误示例 - 动画参数使用字符串
public class BadAnimationExample : MonoBehaviour
{
    private Animator animator;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        // 使用字符串进行动画参数设置 - 性能较差
        float speed = Input.GetAxis("Vertical");
        animator.SetFloat("Speed", speed);  // 每次都要计算字符串的哈希值
    }
}

// ✅ 正确示例 - 使用哈希值优化
public class GoodAnimationExample : MonoBehaviour
{
    private Animator animator;
    private int speedHash;  // 预计算哈希值

    void Start()
    {
        animator = GetComponent<Animator>();
        speedHash = Animator.StringToHash("Speed");  // 预计算哈希值
    }

    void Update()
    {
        float speed = Input.GetAxis("Vertical");
        // 使用预计算的哈希值 - 性能更好
        animator.SetFloat(speedHash, speed);
    }
}

// ✅ 动画事件优化
public class OptimizedAnimationExample : MonoBehaviour
{
    private Animator animator;
    private int speedHash;
    private int directionHash;
    private int isRunningHash;

    void Start()
    {
        animator = GetComponent<Animator>();
        
        // 预计算所有动画参数的哈希值
        speedHash = Animator.StringToHash("Speed");
        directionHash = Animator.StringToHash("Direction");
        isRunningHash = Animator.StringToHash("IsRunning");
    }

    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        float speed = new Vector2(horizontal, vertical).magnitude;

        // 使用预计算的哈希值设置参数
        animator.SetFloat(speedHash, speed);
        animator.SetFloat(directionHash, horizontal);
        animator.SetBool(isRunningHash, speed > 0.5f);
    }

    // 动画事件处理器
    void OnFootstep()
    {
        // 播放脚步声
        PlayFootstepSound();
    }

    void OnAttack()
    {
        // 处理攻击逻辑
        PerformAttack();
    }

    void PlayFootstepSound()
    {
        // 播放脚步声逻辑
    }

    void PerformAttack()
    {
        // 攻击逻辑
    }
}

2. 动画性能优化

问题: 动画更新频率过高或动画复杂度过高。

// ❌ 错误示例 - 动画更新过于频繁
public class BadAnimationUpdateExample : MonoBehaviour
{
    private Animator animator;
    private float lastAnimationUpdateTime = 0f;
    private float animationUpdateInterval = 0.016f; // 每帧更新

    void Update()
    {
        // 每帧都更新动画参数,过于频繁
        UpdateAnimationParameters();
    }

    void UpdateAnimationParameters()
    {
        if (Time.time - lastAnimationUpdateTime >= animationUpdateInterval)
        {
            // 更新动画参数
            float speed = CalculateSpeed();
            animator.SetFloat("Speed", speed);
            lastAnimationUpdateTime = Time.time;
        }
    }

    float CalculateSpeed()
    {
        // 复杂的速度计算
        return 1f;
    }
}

// ✅ 正确示例 - 优化动画更新频率
public class GoodAnimationUpdateExample : MonoBehaviour
{
    private Animator animator;
    private float lastAnimationUpdateTime = 0f;
    private float animationUpdateInterval = 0.1f; // 100ms更新一次,而不是每帧

    void Update()
    {
        // 降低动画参数更新频率
        if (Time.time - lastAnimationUpdateTime >= animationUpdateInterval)
        {
            UpdateAnimationParameters();
            lastAnimationUpdateTime = Time.time;
        }
    }

    void UpdateAnimationParameters()
    {
        float speed = CalculateSpeed();
        animator.SetFloat("Speed", speed);
    }

    float CalculateSpeed()
    {
        // 速度计算
        return 1f;
    }
}

// ✅ 动画LOD系统
public class AnimationLODExample : MonoBehaviour
{
    private Animator animator;
    private Camera mainCamera;
    private float lodDistance = 20f; // LOD距离
    private bool isLODActive = false;

    void Start()
    {
        animator = GetComponent<Animator>();
        mainCamera = Camera.main;
    }

    void Update()
    {
        UpdateAnimationLOD();
    }

    void UpdateAnimationLOD()
    {
        if (mainCamera != null)
        {
            float distance = Vector3.Distance(transform.position, mainCamera.transform.position);
            
            bool shouldUseLOD = distance > lodDistance;
            
            if (shouldUseLOD != isLODActive)
            {
                isLODActive = shouldUseLOD;
                
                if (isLODActive)
                {
                    // 远距离时简化动画
                    animator.cullingMode = AnimatorCullingMode.CullCompletely;
                }
                else
                {
                    // 近距离时正常动画
                    animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
                }
            }
        }
    }
}

物理系统问题

1. 碰撞检测性能问题

问题: 频繁的碰撞检测影响性能。

// ❌ 错误示例 - 频繁的射线检测
public class BadPhysicsExample : MonoBehaviour
{
    void Update()
    {
        // 每帧都进行射线检测 - 性能差
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        
        if (Physics.Raycast(ray, out hit))
        {
            Debug.Log($"Hit: {hit.collider.name}");
        }
    }
}

// ✅ 正确示例 - 优化碰撞检测频率
public class GoodPhysicsExample : MonoBehaviour
{
    private float lastRaycastTime = 0f;
    private float raycastInterval = 0.1f; // 100ms检测一次

    void Update()
    {
        if (Time.time - lastRaycastTime >= raycastInterval)
        {
            PerformRaycast();
            lastRaycastTime = Time.time;
        }
    }

    void PerformRaycast()
    {
        if (Camera.main != null)
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            
            if (Physics.Raycast(ray, out hit))
            {
                Debug.Log($"Hit: {hit.collider.name}");
            }
        }
    }
}

// ✅ 使用Physics.RaycastNonAlloc优化
public class OptimizedPhysicsExample : MonoBehaviour
{
    private RaycastHit[] raycastHits = new RaycastHit[10]; // 重用数组
    private float lastRaycastTime = 0f;
    private float raycastInterval = 0.1f;

    void Update()
    {
        if (Time.time - lastRaycastTime >= raycastInterval)
        {
            PerformOptimizedRaycast();
            lastRaycastTime = Time.time;
        }
    }

    void PerformOptimizedRaycast()
    {
        if (Camera.main != null)
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            
            // 使用NonAlloc版本避免内存分配
            int hitCount = Physics.RaycastNonAlloc(ray, raycastHits);
            
            for (int i = 0; i < hitCount; i++)
            {
                Debug.Log($"Hit: {raycastHits[i].collider.name}");
            }
        }
    }
}

2. 刚体配置问题

问题: 刚体配置不当导致物理行为异常。

// ❌ 错误示例 - 刚体配置不当
public class BadRigidbodyExample : MonoBehaviour
{
    public Rigidbody rb;

    void Start()
    {
        // 错误配置:同时启用isKinematic和useGravity
        rb.isKinematic = true;
        rb.useGravity = true;  // 这两个设置冲突
        
        // 错误:在Update中频繁修改物理属性
        rb.drag = 0;      // 阻力为0可能导致物体加速过快
        rb.angularDrag = 0; // 角阻力为0可能导致旋转不稳定
    }

    void Update()
    {
        // 错误:在Update中直接修改position和rotation
        // 这会破坏物理模拟
        rb.position = transform.position + Vector3.forward * Time.deltaTime;
    }
}

// ✅ 正确示例 - 正确配置刚体
public class GoodRigidbodyExample : MonoBehaviour
{
    public Rigidbody rb;
    public float moveForce = 10f;

    void Start()
    {
        // 正确配置刚体属性
        rb.isKinematic = false;  // 不是运动学刚体
        rb.useGravity = true;    // 使用重力
        rb.drag = 1f;           // 适当的阻力
        rb.angularDrag = 1f;    // 适当的角阻力
        rb.interpolation = RigidbodyInterpolation.Interpolate; // 平滑插值
    }

    void FixedUpdate()
    {
        // 在FixedUpdate中应用物理力
        if (Input.GetKey(KeyCode.W))
        {
            rb.AddForce(Vector3.forward * moveForce);
        }
    }
}

// ✅ 运动学刚体的正确使用
public class KinematicRigidbodyExample : MonoBehaviour
{
    public Rigidbody rb;

    void Start()
    {
        // 运动学刚体:不受物理影响,但可以影响其他物理对象
        rb.isKinematic = true;
        rb.useGravity = false;  // 运动学刚体不使用重力
    }

    void Update()
    {
        // 对于运动学刚体,使用MovePosition和MoveRotation
        Vector3 newPosition = transform.position + Vector3.forward * Time.deltaTime;
        rb.MovePosition(newPosition);
    }
}

网络和多人游戏问题

1. 网络同步问题

问题: 网络同步实现不当导致问题。

// ❌ 错误示例 - 简单的网络同步(容易被作弊)
public class BadNetworkSyncExample : MonoBehaviour
{
    public Vector3 networkPosition;
    public float interpolationSpeed = 10f;

    void Update()
    {
        // 直接插值到网络位置 - 容易出现延迟和抖动
        transform.position = Vector3.Lerp(transform.position, networkPosition, 
                                        interpolationSpeed * Time.deltaTime);
    }
}

// ✅ 正确示例 - 带有延迟补偿的网络同步
public class GoodNetworkSyncExample : MonoBehaviour
{
    private Vector3 targetPosition;
    private float lastUpdateTime = 0f;
    private float updateInterval = 0.1f; // 100ms更新一次
    private float interpolationSpeed = 5f;

    // 服务器时间戳
    private float serverTimeOffset = 0f;

    void Update()
    {
        // 平滑插值到目标位置
        transform.position = Vector3.Lerp(transform.position, targetPosition, 
                                        interpolationSpeed * Time.deltaTime);
    }

    // 从网络接收位置更新
    public void OnPositionUpdate(Vector3 newPosition, float timestamp)
    {
        // 计算时间差并应用延迟补偿
        float timeDiff = Time.time - timestamp + serverTimeOffset;
        targetPosition = newPosition;

        // 预测未来位置以补偿网络延迟
        if (timeDiff > 0)
        {
            // 这里可以添加预测逻辑
            targetPosition += PredictMovement(timeDiff);
        }
    }

    Vector3 PredictMovement(float timeAhead)
    {
        // 简单的预测逻辑(实际项目中需要更复杂的预测)
        return Vector3.zero;
    }
}

// ✅ 网络对象池
public class NetworkObjectPool : MonoBehaviour
{
    private Queue<GameObject> networkObjectPool = new Queue<GameObject>();
    private GameObject prefab;
    private int poolSize = 20;

    void Start()
    {
        // 预创建网络对象池
        for (int i = 0; i < poolSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            networkObjectPool.Enqueue(obj);
        }
    }

    public GameObject GetNetworkObject()
    {
        if (networkObjectPool.Count > 0)
        {
            GameObject obj = networkObjectPool.Dequeue();
            obj.SetActive(true);
            return obj;
        }
        else
        {
            // 如果池空了,创建新对象(应该避免)
            return Instantiate(prefab);
        }
    }

    public void ReturnNetworkObject(GameObject obj)
    {
        obj.SetActive(false);
        obj.transform.SetParent(transform);
        networkObjectPool.Enqueue(obj);
    }
}

2. 网络消息处理

问题: 网络消息处理不当导致安全问题。

// ❌ 错误示例 - 没有验证的网络消息处理
public class BadNetMessageExample : MonoBehaviour
{
    public int playerHealth = 100;

    // 直接应用网络消息中的值,没有验证
    public void OnHealthUpdate(int newHealth)
    {
        // 安全问题:客户端可以发送任意值
        playerHealth = newHealth;
    }
}

// ✅ 正确示例 - 验证网络消息
public class GoodNetMessageExample : MonoBehaviour
{
    public int playerHealth = 100;
    public int maxHealth = 100;
    private int lastValidHealth = 100;

    public void OnHealthUpdate(int newHealth, int damageSource)
    {
        // 验证健康值的合理性
        if (IsValidHealthUpdate(newHealth, damageSource))
        {
            playerHealth = Mathf.Clamp(newHealth, 0, maxHealth);
            lastValidHealth = playerHealth;
        }
        else
        {
            // 检测到可能的作弊,重置为上一个有效值
            playerHealth = lastValidHealth;
            Debug.LogWarning("Invalid health update detected, resetting to last valid value");
        }
    }

    bool IsValidHealthUpdate(int newHealth, int damageSource)
    {
        // 检查健康值是否在合理范围内
        if (newHealth < 0 || newHealth > maxHealth)
        {
            return false;
        }

        // 检查健康值变化是否合理
        int healthChange = newHealth - lastValidHealth;
        
        // 如果是恢复,检查恢复量是否合理
        if (healthChange > 0 && healthChange > 50) // 假设单次恢复不超过50点
        {
            return false;
        }

        // 可以添加更多验证逻辑
        return true;
    }
}

实践练习

练习1: 性能监控工具

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

// 性能监控工具
public class PerformanceMonitor : MonoBehaviour
{
    [Header("UI References")]
    public Text fpsText;
    public Text memoryText;
    public Text drawCallText;
    public Text objectCountText;
    public Slider fpsSlider;

    [Header("Settings")]
    public float updateInterval = 0.5f;
    public int fpsWarningThreshold = 30;
    public int fpsCriticalThreshold = 20;

    private float lastUpdateTime = 0f;
    private List<float> fpsHistory = new List<float>();
    private const int historySize = 50;

    void Update()
    {
        if (Time.time - lastUpdateTime >= updateInterval)
        {
            UpdatePerformanceInfo();
            lastUpdateTime = Time.time;
        }
    }

    private void UpdatePerformanceInfo()
    {
        // 更新FPS
        float currentFPS = 1.0f / Time.unscaledDeltaTime;
        UpdateFPSInfo(currentFPS);

        // 更新内存信息
        UpdateMemoryInfo();

        // 更新渲染统计
        UpdateRenderStats();

        // 更新对象计数
        UpdateObjectCount();
    }

    private void UpdateFPSInfo(float fps)
    {
        if (fpsText != null)
        {
            fpsText.text = $"FPS: {fps:F1}";
            
            // 根据FPS设置颜色
            if (fps >= fpsWarningThreshold)
            {
                fpsText.color = Color.green;
            }
            else if (fps >= fpsCriticalThreshold)
            {
                fpsText.color = Color.yellow;
            }
            else
            {
                fpsText.color = Color.red;
            }
        }

        // 更新FPS滑块
        if (fpsSlider != null)
        {
            fpsSlider.value = Mathf.Clamp01(fps / 60f);
        }

        // 更新历史记录
        fpsHistory.Add(fps);
        if (fpsHistory.Count > historySize)
        {
            fpsHistory.RemoveAt(0);
        }
    }

    private void UpdateMemoryInfo()
    {
        if (memoryText != null)
        {
            long memoryUsage = System.GC.GetTotalMemory(false);
            string memoryStr = FormatBytes(memoryUsage);
            memoryText.text = $"Memory: {memoryStr}";
        }
    }

    private void UpdateRenderStats()
    {
        if (drawCallText != null)
        {
            // 注意:Unity没有直接API获取draw call数量
            // 这里简化显示
            drawCallText.text = "Draw Calls: N/A";
        }
    }

    private void UpdateObjectCount()
    {
        if (objectCountText != null)
        {
            int objectCount = FindObjectsOfType<GameObject>().Length;
            objectCountText.text = $"Objects: {objectCount}";
        }
    }

    private string FormatBytes(long bytes)
    {
        string[] suffixes = { "B", "KB", "MB", "GB" };
        int counter = 0;
        decimal number = (decimal)bytes;
        
        while (Math.Abs(number) >= 1024m && counter < suffixes.Length - 1)
        {
            counter++;
            number /= 1024m;
        }
        
        return string.Format("{0:n1} {1}", number, suffixes[counter]);
    }

    // 强制垃圾回收
    public void ForceGarbageCollection()
    {
        System.GC.Collect();
        System.GC.WaitForPendingFinalizers();
    }

    // 卸载未使用的资源
    public void UnloadUnusedAssets()
    {
        Resources.UnloadUnusedAssets();
    }
}

练习2: 代码质量检查工具

using UnityEngine;
using System.Collections.Generic;
using System.Text;
using System.Linq;

// 代码质量检查工具
public class CodeQualityChecker : MonoBehaviour
{
    [Header("Check Settings")]
    public bool checkForGODObjects = true;
    public int maxComponentCount = 10;
    public bool checkForDeepNesting = true;
    public int maxNestingLevel = 5;
    public bool checkForExpensiveOperations = true;

    private List<QualityIssue> issues = new List<QualityIssue>();

    [System.Serializable]
    public class QualityIssue
    {
        public string objectName;
        public string componentName;
        public string issueType;
        public string description;
        public int severity; // 1 = Low, 2 = Medium, 3 = High
        public System.DateTime timestamp;
    }

    public void RunQualityCheck()
    {
        issues.Clear();
        Debug.Log("Running code quality check...");

        if (checkForGODObjects)
        {
            CheckForGODObjects();
        }

        if (checkForDeepNesting)
        {
            CheckForDeepNestingIssues();
        }

        GenerateQualityReport();
    }

    private void CheckForGODObjects()
    {
        GameObject[] allObjects = FindObjectsOfType<GameObject>();
        
        foreach (GameObject go in allObjects)
        {
            Component[] components = go.GetComponents<Component>();
            if (components.Length > maxComponentCount)
            {
                AddQualityIssue(go.name, "GOD Object", 
                    $"Object has {components.Length} components, exceeding recommended {maxComponentCount}",
                    2);
            }
        }
    }

    private void CheckForDeepNestingIssues()
    {
        Transform[] allTransforms = FindObjectsOfType<Transform>();
        
        foreach (Transform t in allTransforms)
        {
            int depth = GetTransformDepth(t);
            if (depth > maxNestingLevel)
            {
                AddQualityIssue(t.name, "Deep Nesting", 
                    $"Transform nesting level is {depth}, exceeding recommended {maxNestingLevel}",
                    2);
            }
        }
    }

    private int GetTransformDepth(Transform transform)
    {
        int depth = 0;
        Transform current = transform;
        
        while (current.parent != null)
        {
            depth++;
            current = current.parent;
        }
        
        return depth;
    }

    private void AddQualityIssue(string objectName, string issueType, string description, int severity)
    {
        QualityIssue issue = new QualityIssue
        {
            objectName = objectName,
            componentName = issueType,
            issueType = issueType,
            description = description,
            severity = severity,
            timestamp = System.DateTime.Now
        };
        
        issues.Add(issue);
    }

    private void GenerateQualityReport()
    {
        StringBuilder report = new StringBuilder();
        report.AppendLine("=== Code Quality Report ===");
        report.AppendLine($"Check Time: {System.DateTime.Now}");
        report.AppendLine($"Issues Found: {issues.Count}");
        
        if (issues.Count > 0)
        {
            report.AppendLine("\nIssue Details:");
            
            var sortedIssues = issues.OrderByDescending(i => i.severity).ToList();
            
            foreach (QualityIssue issue in sortedIssues)
            {
                string severityStr = issue.severity == 3 ? "High" : 
                                   issue.severity == 2 ? "Medium" : "Low";
                
                report.AppendLine($"[{severityStr}] {issue.objectName}.{issue.componentName}: {issue.description}");
            }
        }
        else
        {
            report.AppendLine("\n✅ No code quality issues found!");
        }

        Debug.Log(report.ToString());
    }

    public void GenerateFixSuggestions()
    {
        StringBuilder suggestions = new StringBuilder();
        suggestions.AppendLine("=== Fix Suggestions ===");

        int godObjectCount = issues.Count(i => i.issueType == "GOD Object");
        int nestingIssues = issues.Count(i => i.issueType.Contains("Nesting"));

        if (godObjectCount > 0)
        {
            suggestions.AppendLine($"• Split {godObjectCount} GOD objects into smaller components");
            suggestions.AppendLine("  - Use composition over inheritance");
            suggestions.AppendLine("  - Group related functionality into separate scripts");
        }

        if (nestingIssues > 0)
        {
            suggestions.AppendLine($"• Reduce {nestingIssues} deep nesting issues");
            suggestions.AppendLine("  - Consider using object pooling to reduce hierarchy");
            suggestions.AppendLine("  - Redesign scene hierarchy structure");
        }

        Debug.Log(suggestions.ToString());
    }

    public float GetQualityScore()
    {
        if (issues.Count == 0) return 100f;

        int totalSeverity = issues.Sum(i => i.severity);
        float score = Mathf.Clamp(100f - (issues.Count * 5 + totalSeverity * 10), 0f, 100f);
        return score;
    }
}

常见错误总结

性能相关(10个)

  1. GetComponent滥用 - 在Update中频繁调用GetComponent
  2. GameObject.Find滥用 - 在Update中使用GameObject.Find
  3. 频繁对象创建销毁 - 在Update中创建/销毁对象
  4. 字符串操作 - 频繁的字符串拼接和操作
  5. 空引用检查 - 忘记检查空引用导致异常
  6. 数组越界 - 访问数组时没有边界检查
  7. 协程管理 - 协程启动后没有正确停止
  8. 事件订阅 - 事件订阅后没有取消导致内存泄漏
  9. 物理检测 - 频繁的射线检测和碰撞检测
  10. UI更新 - 频繁更新UI组件

内存管理相关(8个)

  1. 资源未释放 - 加载的资源没有正确释放
  2. 装箱拆箱 - 频繁的值类型装箱操作
  3. 对象池 - 没有使用对象池导致GC压力
  4. 事件泄漏 - 事件订阅后没有取消
  5. 协程泄漏 - 协程没有正确停止
  6. 引用保持 - 保持不必要的对象引用
  7. 字符串缓存 - 没有缓存频繁使用的字符串
  8. 集合扩容 - 没有预分配集合大小

Unity特定(7个)

  1. 坐标系统 - 混淆世界坐标和本地坐标
  2. 更新方法 - 在错误的更新方法中执行操作
  3. 单例模式 - 单例实现不当
  4. 生命周期 - 错误的组件生命周期使用
  5. 物理配置 - 刚体配置不当
  6. 动画参数 - 使用字符串而非哈希值
  7. 场景管理 - 场景切换时的资源管理

C#语言(5个)

  1. 值引用类型 - 混淆值类型和引用类型
  2. 字符串不可变 - 忽略字符串的不可变性
  3. 闭包陷阱 - 循环中的闭包陷阱
  4. 数组访问 - 没有边界检查的数组访问
  5. 异常处理 - 不当的异常处理

网络相关(3个)

  1. 消息验证 - 网络消息没有验证
  2. 同步问题 - 网络同步实现不当
  3. 安全问题 - 客户端验证不足

解决方案速查表

问题类型 常见表现 解决方案
性能问题 游戏卡顿、帧率下降 缓存组件引用、使用对象池、减少Update调用
内存泄漏 内存使用持续增长 正确管理事件订阅、资源释放、协程停止
空引用异常 NullReferenceException 始终检查空引用、使用安全访问方法
UI性能 UI响应慢、卡顿 批量更新、减少不必要的更新、使用UI管理器
物理问题 碰撞异常、物理行为不正确 正确配置刚体、使用FixedUpdate、优化碰撞检测

预防措施

  1. 代码审查: 定期进行代码审查,发现潜在问题
  2. 性能测试: 定期进行性能测试,监控关键指标
  3. 单元测试: 编写单元测试,确保代码正确性
  4. 静态分析: 使用静态分析工具检测代码问题
  5. 监控工具: 使用Unity Profiler等工具监控性能
  6. 编码规范: 遵循统一的编码规范
  7. 文档记录: 记录常见问题和解决方案
  8. 持续学习: 关注Unity更新和最佳实践

通过识别和避免这些常见问题,可以显著提高Unity项目的质量和稳定性。