Návrhový vzor singleton je užitečný při psaní komponent v Unity jako je game manager nebo localization manager. Rád bych se s vámi podělil o implementaci, kterou nyní používám ve svém projektu.
Inspirace
Při návrhu své implementace jsem se inspiroval několika zdroji:
- Localization Manager sample – přístup používající metodu
Awake()
, která po vytvoření první instance všechny nové zničí. - Unity Community Wiki – tato implementace zaručeně vytvoří pouze jednu instanci a je thread-safe díky zámkům.
- Singleton pattern od Jona Skeeta – již poněkolikáté jsem zavítal na tento excelentní blog post o implementaci singletonu v C#, vřele doporučuji přečíst!
Kód
public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour | |
{ | |
private static readonly Lazy<T> LazyInstance = new Lazy<T>(CreateSingleton); | |
public static T Instance => LazyInstance.Value; | |
private static T CreateSingleton() | |
{ | |
var ownerObject = new GameObject($"{typeof(T).Name} (singleton)"); | |
var instance = ownerObject.AddComponent<T>(); | |
DontDestroyOnLoad(ownerObject); | |
return instance; | |
} | |
} | |
//usage: | |
public class GameManager : Singleton<GameManager> | |
{ | |
… | |
} |
Poznámka: Tento kód vyžaduje kvůli typu Lazy<T>
povolení .NET 4 scripting runtime.
- Otevřte nastavení Edit > Project Settings > Player.
- V záložce Configuration klikněte na drop-down Scripting Runtime Version a vyberte .NET 4.x Equivalent. Pro použití změn bude nutné restartovat Unity.
Vysvětlení
Má verze singletonu je implementovaná jako generická třída která dědí od MonoBehaviour
. To je nutné pro přístup k metodě DontDestroyOnLoad
kterou využíváme v CreateSingleton
.
Typový argument také dědí od MonoBehaviour
abychom dostávali všechny zprávy jako je Awake
, Start
a Update
. Při použití ve zděděné tříde předáváme ji samotnou jako typový argument. To se může zdát “zacyklené” ale jednoduše tím říkáme “třída X je singleton typu X”.
Pro vzor samotný jsem zvolil verzi s použitím Lazy<T>
kterou doporučuje Jon Skeet ve svém článku. Lazy<T>
se za nás postará o thread-safety a tak se vyhneme nutnosti používat v kódu zámky.
Inicializace instance singletonu je přímočará – vytvoříme si nový prázdný GameObject
, připojíme k němu naši komponentu a označíme jej tak, aby nebyl při načtení nové scény odstraněn.