返回

揭秘 Unity 中单例模式的六种写法,助力你的游戏开发

前端

单例模式是软件设计中一种常见的模式,它确保一个类只有一个实例。在 Unity 游戏开发中,单例模式经常用于管理和控制游戏中的全局资源或组件,例如音频管理模块、UI 管理模块和对象池。

本篇文章将深入探讨 Unity 中单例模式的六种不同写法,深入剖析它们的优缺点,并提供实际应用的示例。通过掌握这些写法,开发人员可以构建更健壮、更易维护的 Unity 游戏。

传统方法

最常见的单例模式写法是使用公共静态属性:

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

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

        Instance = this;
    }
}

优点:

  • 简单易懂
  • 可以在场景加载时自动初始化

缺点:

  • 如果 Awake() 方法执行多次,可能会创建多个实例
  • 不适合在脚本热重载时使用

静态类

另一种方法是使用静态类:

public static class GameManager
{
    private static GameManager instance;

    public static GameManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new GameManager();
            }

            return instance;
        }
    }
}

优点:

  • 确保只有一个实例
  • 适用于脚本热重载

缺点:

  • 需要手动初始化
  • 无法访问组件或脚本变量

Lazy 初始化

Lazy 初始化模式使用委托延迟实例化,只有在第一次访问实例时才创建实例:

public class GameManager : MonoBehaviour
{
    private static readonly Lazy<GameManager> instance = new Lazy<GameManager>(() => FindObjectOfType<GameManager>());

    public static GameManager Instance => instance.Value;
}

优点:

  • 仅在需要时创建实例,提高性能
  • 适用于脚本热重载

缺点:

  • 需要 FindObjectOfType() 方法,可能对性能造成影响
  • 无法访问组件或脚本变量

非单例变体

非单例变体允许在场景中存在多个实例,但仍然确保只有一个实例处于活动状态:

public class GameManager : MonoBehaviour
{
    public static GameManager ActiveInstance { get; private set; }

    private void Awake()
    {
        if (ActiveInstance == null)
        {
            ActiveInstance = this;
        }
    }

    private void OnDisable()
    {
        if (ActiveInstance == this)
        {
            ActiveInstance = null;
        }
    }
}

优点:

  • 允许在场景中存在多个实例,但仅有一个活动
  • 适用于需要多个管理器的场景

缺点:

  • 必须手动设置 ActiveInstance 属性
  • 可能会导致混乱,需要谨慎使用

服务定位器

服务定位器模式使用一个集中存储和访问服务(即单例)的注册表:

public class ServiceLocator
{
    private static Dictionary<Type, object> services = new Dictionary<Type, object>();

    public static T GetService<T>() where T : class
    {
        if (!services.ContainsKey(typeof(T)))
        {
            services[typeof(T)] = FindObjectOfType<T>();
        }

        return services[typeof(T)] as T;
    }
}

优点:

  • 提供了一个集中式位置来访问服务
  • 允许动态注册和注销服务

缺点:

  • 依赖于 FindObjectOfType() 方法,可能对性能造成影响
  • 可能会导致难以调试的问题

自定义属性

最后,可以使用自定义属性来实现单例模式:

public class GameManager : MonoBehaviour
{
    [Singleton]
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        Instance = this;
    }
}

优点:

  • 提供了一个优雅的解决方案,无需编写大量代码
  • 可以与其他属性(如序列号)结合使用

缺点:

  • 需要使用第三方库(例如 SingletonAttribute)
  • 可能不适用于所有项目

结语

单例模式在 Unity 游戏开发中有着广泛的应用。通过了解和掌握这六种不同的写法,开发人员可以根据特定需求选择最合适的方法。每种方法都有其优缺点,因此仔细考虑它们的优点和缺点至关重要。