unity3d
단일성의 싱글 톤
수색…
비고
Singletons의 제약없이 사용하는 것이 나쁜 생각 인 이유에 대해 설득력있는 주장을하는 학파가 있지만 (예 : gameprogrammingpatterns.com의 Singleton) , Unity에서 GameObject를 여러 장면 (예 : 완벽한 배경 음악)으로 유지하려는 경우가 있습니다. 단 하나의 인스턴스 만 존재할 수 있음을 보장합니다. 싱글 톤을위한 완벽한 사용 사례.
GameObject에이 스크립트를 추가하면 (예를 들어 장면의 어느 위치 에나 포함시킴으로써) 인스턴스화 된 후에는 장면 전체에서 활성화 된 상태로 유지되고 하나의 인스턴스 만 존재하게됩니다.
ScriptableObject ( UnityDoc ) 인스턴스는 일부 사용 사례에 대해 Singletons에 대한 유효한 대안을 제공합니다. 단일 인스턴스 규칙을 암시 적으로 시행하지는 않지만 장면 간 상태를 유지하고 Unity 직렬화 프로세스를 제대로 수행합니다. 또한 편집기를 통해 종속성이 주입 될 때 Inversion of Control 을 촉진 합니다 .
// MyAudioManager.cs
using UnityEngine;
[CreateAssetMenu] // Remember to create the instance in editor
public class MyAudioManager : ScriptableObject {
public void PlaySound() {}
}
// MyGameObject.cs
using UnityEngine;
public class MyGameObject : MonoBehaviour
{
[SerializeField]
MyAudioManager audioManager; //Insert through Inspector
void OnEnable()
{
audioManager.PlaySound();
}
}
추가 읽기
RuntimeInitializeOnLoadMethodAttribute를 사용한 구현
Unity 5.2.5 이후로 RuntimeInitializeOnLoadMethodAttribute 를 사용하여 MonoBehaviour 순서를 우회하는 초기화 로직 을 실행할 수 있습니다. 보다 깨끗하고 강력한 구현을위한 방법을 제공합니다.
using UnityEngine;
sealed class GameDirector : MonoBehaviour
{
// Because of using RuntimeInitializeOnLoadMethod attribute to find/create and
// initialize the instance, this property is accessible and
// usable even in Awake() methods.
public static GameDirector Instance
{
get; private set;
}
// Thanks to the attribute, this method is executed before any other MonoBehaviour
// logic in the game.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void OnRuntimeMethodLoad()
{
var instance = FindObjectOfType<GameDirector>();
if (instance == null)
instance = new GameObject("Game Director").AddComponent<GameDirector>();
DontDestroyOnLoad(instance);
Instance = instance;
}
// This Awake() will be called immediately after AddComponent() execution
// in the OnRuntimeMethodLoad(). In other words, before any other MonoBehaviour's
// in the scene will begin to initialize.
private void Awake()
{
// Initialize non-MonoBehaviour logic, etc.
Debug.Log("GameDirector.Awake()", this);
}
}
결과 실행 순서 :
-
GameDirector.OnRuntimeMethodLoad()
시작되었습니다 ... -
GameDirector.Awake()
-
GameDirector.OnRuntimeMethodLoad()
완료되었습니다. -
OtherMonoBehaviour1.Awake()
-
OtherMonoBehaviour2.Awake()
등
Unity C #의 간단한 싱글 톤 MonoBehaviour
이 예제에서는 클래스의 private 정적 인스턴스가 처음에 선언됩니다.
정적 필드의 값은 인스턴스간에 공유되므로이 클래스의 새 인스턴스가 만들어지면 if
는 첫 번째 Singleton 객체에 대한 참조를 찾고 새 인스턴스 (또는 해당 게임 객체)를 삭제합니다.
using UnityEngine;
public class SingletonExample : MonoBehaviour {
private static SingletonExample _instance;
void Awake(){
if (_instance == null){
_instance = this;
DontDestroyOnLoad(this.gameObject);
//Rest of your Awake code
} else {
Destroy(this);
}
}
//Rest of your class code
}
고급 Unity 싱글 톤
이 예제는 인터넷에서 발견 된 MonoBehaviour 싱글 톤의 여러 변형을 하나로 결합하여 전역 정적 필드에 따라 동작을 변경하도록합니다.
이 예제는 Unity 5를 사용하여 테스트되었습니다.이 싱글 톤을 사용하려면 다음과 같이 확장하면됩니다. public class MySingleton : Singleton<MySingleton> {}
. 또한 오버라이드 (override) 할 필요가 있습니다 AwakeSingleton
보통 대신에 그것을 사용하는 Awake
. 추가 조정을 위해 아래에 설명 된대로 정적 필드의 기본값을 변경하십시오.
- 이 구현은 DisallowMultipleComponent 특성을 사용하여 GameObject 당 하나의 인스턴스를 유지합니다.
- 이 클래스는 추상 클래스이며 확장 만 가능합니다. 또한 일반적인
Awake
를 구현하는 대신 재정의AwakeSingleton
가상 메서드AwakeSingleton
을 포함합니다. - 이 구현은 스레드로부터 안전합니다.
- 이 싱글 톤은 최적화되어 있습니다. instance null check 대신에
instantiated
플래그를 사용함으로써 유니티의==
연산자 구현에 따른 오버 헤드를 피할 수 있습니다. ( 더 많은 것을 읽으십시오 ) - 이 구현은 Unity에 의해 파괴 될 때 싱글 톤 인스턴스에 대한 호출을 허용하지 않습니다.
- 이 싱글 톤에는 다음과 같은 옵션이 있습니다 :
-
FindInactive
: 비활성 게임 객체에 연결된 동일한 유형의 구성 요소 인스턴스를 찾을 지 여부입니다. -
Persist
: 장면간에 구성 요소를 유지할지 여부. -
DestroyOthers
: 같은 유형의 다른 구성 요소를 파괴하고 하나만 유지할지 여부. -
Lazy
: 싱글 톤 인스턴스를 "on the fly"(Awake
) 또는 "on demand"(getter가 호출 될 때)로 설정할지 여부.
using UnityEngine;
[DisallowMultipleComponent]
public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static volatile T instance;
// thread safety
private static object _lock = new object();
public static bool FindInactive = true;
// Whether or not this object should persist when loading new scenes. Should be set in Init().
public static bool Persist;
// Whether or not destory other singleton instances if any. Should be set in Init().
public static bool DestroyOthers = true;
// instead of heavy comparision (instance != null)
// http://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/
private static bool instantiated;
private static bool applicationIsQuitting;
public static bool Lazy;
public static T Instance
{
get
{
if (applicationIsQuitting)
{
Debug.LogWarningFormat("[Singleton] Instance '{0}' already destroyed on application quit. Won't create again - returning null.", typeof(T));
return null;
}
lock (_lock)
{
if (!instantiated)
{
Object[] objects;
if (FindInactive) { objects = Resources.FindObjectsOfTypeAll(typeof(T)); }
else { objects = FindObjectsOfType(typeof(T)); }
if (objects == null || objects.Length < 1)
{
GameObject singleton = new GameObject();
singleton.name = string.Format("{0} [Singleton]", typeof(T));
Instance = singleton.AddComponent<T>();
Debug.LogWarningFormat("[Singleton] An Instance of '{0}' is needed in the scene, so '{1}' was created{2}", typeof(T), singleton.name, Persist ? " with DontDestoryOnLoad." : ".");
}
else if (objects.Length >= 1)
{
Instance = objects[0] as T;
if (objects.Length > 1)
{
Debug.LogWarningFormat("[Singleton] {0} instances of '{1}'!", objects.Length, typeof(T));
if (DestroyOthers)
{
for (int i = 1; i < objects.Length; i++)
{
Debug.LogWarningFormat("[Singleton] Deleting extra '{0}' instance attached to '{1}'", typeof(T), objects[i].name);
Destroy(objects[i]);
}
}
}
return instance;
}
}
return instance;
}
}
protected set
{
instance = value;
instantiated = true;
instance.AwakeSingleton();
if (Persist) { DontDestroyOnLoad(instance.gameObject); }
}
}
// if Lazy = false and gameObject is active this will set instance
// unless instance was called by another Awake method
private void Awake()
{
if (Lazy) { return; }
lock (_lock)
{
if (!instantiated)
{
Instance = this as T;
}
else if (DestroyOthers && Instance.GetInstanceID() != GetInstanceID())
{
Debug.LogWarningFormat("[Singleton] Deleting extra '{0}' instance attached to '{1}'", typeof(T), name);
Destroy(this);
}
}
}
// this might be called for inactive singletons before Awake if FindInactive = true
protected virtual void AwakeSingleton() {}
protected virtual void OnDestroy()
{
applicationIsQuitting = true;
instantiated = false;
}
}
기본 클래스를 통한 싱글 톤 구현
싱글 톤 클래스를 특징으로하는 프로젝트에서 (종종 그렇듯이) 싱글 톤 동작을 기본 클래스로 추상화하는 것이 깨끗하고 편리 할 수 있습니다.
using UnityEngine;
using System.Collections.Generic;
using System;
public abstract class MonoBehaviourSingleton<T> : MonoBehaviour {
private static Dictionary<Type, object> _singletons
= new Dictionary<Type, object>();
public static T Instance {
get {
return (T)_singletons[typeof(T)];
}
}
void OnEnable() {
if (_singletons.ContainsKey(GetType())) {
Destroy(this);
} else {
_singletons.Add(GetType(), this);
DontDestroyOnLoad(this);
}
}
}
그런 다음 MonoBehaviour는 MonoBehaviourSingleton을 확장하여 단일 패턴을 구현할 수 있습니다. 이 방법을 사용하면 싱글 톤 자체에서 패턴을 최소한으로 활용할 수 있습니다.
using UnityEngine;
using System.Collections;
public class SingletonImplementation : MonoBehaviourSingleton<SingletonImplementation> {
public string Text= "String Instance";
// Use this for initialisation
IEnumerator Start () {
var demonstration = "SingletonImplementation.Start()\n" +
"Note that the this text logs only once and\n"
"only one class instance is allowed to exist.";
Debug.Log(demonstration);
yield return new WaitForSeconds(2f);
var secondInstance = new GameObject();
secondInstance.AddComponent<SingletonImplementation>();
}
}
싱글 톤 패턴의 이점 중 하나는 인스턴스에 대한 참조가 정적으로 액세스 될 수 있다는 것입니다.
// Logs: String Instance
Debug.Log(SingletonImplementation.Instance.Text);
그러나 커플 링을 줄이기 위해서는 이러한 방법을 최소화해야합니다. 이 접근법은 사전 사용으로 인해 약간의 성능 비용이 발생하지만이 컬렉션에는 각 단일 클래스의 인스턴스가 하나만 포함될 수 있으므로 DRY 원칙 (직접 반복하지 않음), 가독성 및 편의는 작다.
Unitys Entity-Component 시스템을 사용하는 싱글 톤 패턴
핵심 아이디어는 GameObject를 사용하여 여러 장점이있는 싱글 톤을 표현하는 것입니다.
- 복잡성을 최소한으로 유지하지만 종속성 주입과 같은 개념을 지원합니다.
- 싱글 톤은 Entity-Component 시스템의 일부로 정상적인 Unity 라이프 사이클을가집니다.
- 싱글 톤은 게일리언로드가 필요할 수 있고 로컬에 캐싱 될 수 있습니다 (예 : 업데이트 루프)
- 정적 필드가 필요하지 않습니다.
- Singletons로 사용하기 위해 기존 MonoBehaviours / Components를 수정할 필요가 없습니다.
- 리셋하기 쉽고 (Singletons GameObject를 파괴하십시오), 다음 사용시에 다시로드됩니다.
- mock을 쉽게 주입 할 수 있습니다 (사용하기 전에 mock으로 초기화하십시오).
- 일반 Unity 편집기를 사용하여 검사 및 구성하고 편집기 시간에 이미 발생할 수 있습니다 ( Unity 편집기에서 액세스 할 수있는 Singleton의 스크린 샷 )
Test.cs (예제 싱글 톤을 사용함) :
using UnityEngine;
using UnityEngine.Assertions;
public class Test : MonoBehaviour {
void Start() {
ExampleSingleton singleton = ExampleSingleton.instance;
Assert.IsNotNull(singleton); // automatic initialization on first usage
Assert.AreEqual("abc", singleton.myVar1);
singleton.myVar1 = "123";
// multiple calls to instance() return the same object:
Assert.AreEqual(singleton, ExampleSingleton.instance);
Assert.AreEqual("123", ExampleSingleton.instance.myVar1);
}
}
ExampleSingleton.cs (예제 및 실제 싱글 톤 클래스 포함) :
using UnityEngine;
using UnityEngine.Assertions;
public class ExampleSingleton : MonoBehaviour {
public static ExampleSingleton instance { get { return Singleton.get<ExampleSingleton>(); } }
public string myVar1 = "abc";
public void Start() { Assert.AreEqual(this, instance, "Singleton more than once in scene"); }
}
/// <summary> Helper that turns any MonBehaviour or other Component into a Singleton </summary>
public static class Singleton {
public static T get<T>() where T : Component {
return GetOrAddGo("Singletons").GetOrAddChild("" + typeof(T)).GetOrAddComponent<T>();
}
private static GameObject GetOrAddGo(string goName) {
var go = GameObject.Find(goName);
if (go == null) { return new GameObject(goName); }
return go;
}
}
public static class GameObjectExtensionMethods {
public static GameObject GetOrAddChild(this GameObject parentGo, string childName) {
var childGo = parentGo.transform.FindChild(childName);
if (childGo != null) { return childGo.gameObject; } // child found, return it
var newChild = new GameObject(childName); // no child found, create it
newChild.transform.SetParent(parentGo.transform, false); // add it to parent
return newChild;
}
public static T GetOrAddComponent<T>(this GameObject parentGo) where T : Component {
var comp = parentGo.GetComponent<T>();
if (comp == null) { return parentGo.AddComponent<T>(); }
return comp;
}
}
GameObject의 두 가지 확장 메서드는 Singleton 클래스 내부로 이동하여 비공개로 만들 필요가없는 경우 다른 상황에서도 유용합니다.
MonoBehaviour 및 ScriptableObject 기반의 싱글 톤 클래스
대부분의 싱글 톤 예제는 MonoBehaviour를 기본 클래스로 사용합니다. 가장 큰 단점은이 Singleton 클래스가 런타임 중에 만 존재한다는 것입니다. 여기에는 몇 가지 단점이 있습니다.
- 코드 변경 이외의 싱글 톤 필드를 직접 편집 할 수있는 방법은 없습니다.
- Singleton에서 다른 자산에 대한 참조를 저장할 방법이 없습니다.
- Unity UI 이벤트의 대상으로 싱글 톤을 설정하는 방법이 없습니다. 나는 "Proxy Component"라고 부르는 것을 사용하여 결국 "GameManager.Instance.SomeGlobalMethod ()"를 호출하는 1 개의 라인 메소드를 갖게된다.
발언에서 언급했듯이 ScriptableObjects를 기본 클래스로 사용하여이 문제를 해결하려고 시도하지만 MonoBehaviour의 런타임 이점을 상실한 구현이 있습니다. 이 구현은 런타임 동안 ScriptableObject를 기본 클래스 및 연결된 MonoBehavior로 사용하여이 문제를 해결합니다.
- 이 속성은 다른 Unity 자산과 마찬가지로 편집기에서 속성을 업데이트 할 수 있도록하는 자산입니다.
- Unity 직렬화 프로세스와 잘 어울립니다.
- 싱글 톤에 대한 참조를 에디터의 다른 애셋에 할당 할 수 있습니다 (의존성은 에디터를 통해 주입됩니다).
- Unity 이벤트는 Singleton에서 직접 메서드를 호출 할 수 있습니다.
- "SingletonClassName.Instance"를 사용하여 코드베이스의 아무 곳에서나 호출 할 수 있습니다.
- Update, Awake, Start, FixedUpdate, StartCoroutine 등과 같은 런타임 MonoBehaviour 이벤트 및 메서드에 액세스 할 수 있습니다.
/************************************************************
* Better Singleton by David Darias
* Use as you like - credit where due would be appreciated :D
* Licence: WTFPL V2, Dec 2014
* Tested on Unity v5.6.0 (should work on earlier versions)
* 03/02/2017 - v1.1
* **********************************************************/
using System;
using UnityEngine;
using SingletonScriptableObjectNamespace;
public class SingletonScriptableObject<T> : SingletonScriptableObjectNamespace.BehaviourScriptableObject where T : SingletonScriptableObjectNamespace.BehaviourScriptableObject
{
//Private reference to the scriptable object
private static T _instance;
private static bool _instantiated;
public static T Instance
{
get
{
if (_instantiated) return _instance;
var singletonName = typeof(T).Name;
//Look for the singleton on the resources folder
var assets = Resources.LoadAll<T>("");
if (assets.Length > 1) Debug.LogError("Found multiple " + singletonName + "s on the resources folder. It is a Singleton ScriptableObject, there should only be one.");
if (assets.Length == 0)
{
_instance = CreateInstance<T>();
Debug.LogError("Could not find a " + singletonName + " on the resources folder. It was created at runtime, therefore it will not be visible on the assets folder and it will not persist.");
}
else _instance = assets[0];
_instantiated = true;
//Create a new game object to use as proxy for all the MonoBehaviour methods
var baseObject = new GameObject(singletonName);
//Deactivate it before adding the proxy component. This avoids the execution of the Awake method when the the proxy component is added.
baseObject.SetActive(false);
//Add the proxy, set the instance as the parent and move to DontDestroyOnLoad scene
SingletonScriptableObjectNamespace.BehaviourProxy proxy = baseObject.AddComponent<SingletonScriptableObjectNamespace.BehaviourProxy>();
proxy.Parent = _instance;
Behaviour = proxy;
DontDestroyOnLoad(Behaviour.gameObject);
//Activate the proxy. This will trigger the MonoBehaviourAwake.
proxy.gameObject.SetActive(true);
return _instance;
}
}
//Use this reference to call MonoBehaviour specific methods (for example StartCoroutine)
protected static MonoBehaviour Behaviour;
public static void BuildSingletonInstance() { SingletonScriptableObjectNamespace.BehaviourScriptableObject i = Instance; }
private void OnDestroy(){ _instantiated = false; }
}
// Helper classes for the SingletonScriptableObject
namespace SingletonScriptableObjectNamespace
{
#if UNITY_EDITOR
//Empty custom editor to have cleaner UI on the editor.
using UnityEditor;
[CustomEditor(typeof(BehaviourProxy))]
public class BehaviourProxyEditor : Editor
{
public override void OnInspectorGUI(){}
}
#endif
public class BehaviourProxy : MonoBehaviour
{
public IBehaviour Parent;
public void Awake() { if (Parent != null) Parent.MonoBehaviourAwake(); }
public void Start() { if (Parent != null) Parent.Start(); }
public void Update() { if (Parent != null) Parent.Update(); }
public void FixedUpdate() { if (Parent != null) Parent.FixedUpdate(); }
}
public interface IBehaviour
{
void MonoBehaviourAwake();
void Start();
void Update();
void FixedUpdate();
}
public class BehaviourScriptableObject : ScriptableObject, IBehaviour
{
public void Awake() { ScriptableObjectAwake(); }
public virtual void ScriptableObjectAwake() { }
public virtual void MonoBehaviourAwake() { }
public virtual void Start() { }
public virtual void Update() { }
public virtual void FixedUpdate() { }
}
}
여기에는 많은 주석이있는 SingletonScriptableObject를 사용하는 GameManager 싱글 톤 클래스의 예제가 있습니다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//this attribute is optional but recommended. It will allow the creation of the singleton via the asset menu.
//the singleton asset should be on the Resources folder.
[CreateAssetMenu(fileName = "GameManager", menuName = "Game Manager", order = 0)]
public class GameManager : SingletonScriptableObject<GameManager> {
//any properties as usual
public int Lives;
public int Points;
//optional (but recommended)
//this method will run before the first scene is loaded. Initializing the singleton here
//will allow it to be ready before any other GameObjects on every scene and will
//will prevent the "initialization on first usage".
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void BeforeSceneLoad() { BuildSingletonInstance(); }
//optional,
//will run when the Singleton Scriptable Object is first created on the assets.
//Usually this happens on edit mode, not runtime. (the override keyword is mandatory for this to work)
public override void ScriptableObjectAwake(){
Debug.Log(GetType().Name + " created." );
}
//optional,
//will run when the associated MonoBehavioir awakes. (the override keyword is mandatory for this to work)
public override void MonoBehaviourAwake(){
Debug.Log(GetType().Name + " behaviour awake." );
//A coroutine example:
//Singleton Objects do not have coroutines.
//if you need to use coroutines use the atached MonoBehaviour
Behaviour.StartCoroutine(SimpleCoroutine());
}
//any methods as usual
private IEnumerator SimpleCoroutine(){
while(true){
Debug.Log(GetType().Name + " coroutine step." );
yield return new WaitForSeconds(3);
}
}
//optional,
//Classic runtime Update method (the override keyword is mandatory for this to work).
public override void Update(){
}
//optional,
//Classic runtime FixedUpdate method (the override keyword is mandatory for this to work).
public override void FixedUpdate(){
}
}
/*
* Notes:
* - Remember that you have to create the singleton asset on edit mode before using it. You have to put it on the Resources folder and of course it should be only one.
* - Like other Unity Singleton this one is accessible anywhere in your code using the "Instance" property i.e: GameManager.Instance
*/