Skip to content

Instantly share code, notes, and snippets.

@bruce965
Last active December 9, 2015 09:23
Show Gist options
  • Save bruce965/785bc506610347cdcc35 to your computer and use it in GitHub Desktop.
Save bruce965/785bc506610347cdcc35 to your computer and use it in GitHub Desktop.
Unity Singleton
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using UnityEngine;
namespace SandWar.Unity.Tools
{
/// <summary>
/// Attributes to be used with <see cref="Singleton&lt;T&gt;" />s.
/// </summary>
public static class Singleton {
/// <summary>Forces an instance a <see cref="Singleton&lt;T&gt;" /> to be automatically added to every scene.</summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class SelfInitializedAttribute : Attribute { }
/// <summary>Ensures this <see cref="Singleton&lt;T&gt;" /> to persist when changing scene.</summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class PersistentAttribute : Attribute { }
/// <summary>Hides the instance of this <see cref="Singleton&lt;T&gt;" /> from the scene.</summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class HiddenAttribute : Attribute { }
/// <summary>Loads the instance of this <see cref="Singleton&lt;T&gt;" /> from a resource.</summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class LoadFromResourceAttribute : Attribute {
public string Path;
public LoadFromResourceAttribute(string path) {
this.Path = path;
}
}
class FakeSingleton : Singleton<FakeSingleton> { }
[RuntimeInitializeOnLoadMethod]
static void initSingletons() {
// ensures singletons with SelfInitializedAttribute are initialized
var ensureInstantiatedMethod = getMethod(() => FakeSingleton.EnsureInstantiated());
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
foreach (var type in assembly.GetTypes().Where(type => isSelfInitialized(type)))
ensureInstantiatedMethod.getMethodDefinitionInType(type.BaseType).Invoke(null, new object[0]);
}
static MethodInfo getMethod(Expression<Action> method) {
return ((MethodCallExpression)method.Body).Method;
}
static MethodInfo getMethodDefinitionInType(this MethodInfo method, Type type) {
// HACK: MetadataToken seems to be shared between generic implementations, but this is undocumented!
var originalToken = method.MetadataToken;
return type.GetMethods().First(method2 => method2.MetadataToken == originalToken);
}
static bool isSelfInitialized(Type type) {
return type.IsDefined(typeof(Singleton.SelfInitializedAttribute), false);
}
}
/// <summary>
/// Provides a way to quickly instantiate singletons.
///
/// Always seal the inheritor class.
/// </summary>
[AddComponentMenu("")]
public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
static object @lock = new object();
static bool instantiated = false;
static bool quitting = false;
static T instance = null;
static bool persistent {
get { return typeof(T).IsDefined(typeof(Singleton.PersistentAttribute), true); }
}
static bool visible {
get { return !typeof(T).IsDefined(typeof(Singleton.HiddenAttribute), true); }
}
static string resource {
get {
var attrib = (Singleton.LoadFromResourceAttribute)typeof(T).GetCustomAttributes(typeof(Singleton.LoadFromResourceAttribute), false).FirstOrDefault();
return attrib == null ? null : attrib.Path;
}
}
/// <summary>Holds a reference to the instance of this <see cref="Singleton&lt;T&gt;" />.</summary>
protected static T Instance {
get {
EnsureInstantiated();
return instance;
}
set {
instantiated = value != null;
instance = value;
}
}
/// <summary>Intantiates a <see cref="Singleton&lt;T&gt;" /> of type <typeparamref>T</typeparamref> if not already in the scene.</summary>
public static void EnsureInstantiated() {
lock (@lock) {
if (!instantiated) {
if (quitting)
return;
instantiated = true;
instance = GameObject.FindObjectOfType<T>();
if (instance == null) {
GameObject singleton;
if (resource != null) {
instance = GameObject.Instantiate(UnityEngine.Resources.Load<T>(resource));
singleton = instance.gameObject;
} else {
singleton = new GameObject(typeof(T).FullName + "(Singleton)");
instance = singleton.AddComponent<T>();
}
if (!visible)
singleton.hideFlags = HideFlags.HideInHierarchy;
}
if (persistent)
GameObject.DontDestroyOnLoad(instance.gameObject);
}
}
}
/// <summary>
/// Used by Unity to initialize the <see cref="GameObject" />.
///
/// Always call this method if overriding.
/// </summary>
protected virtual void Awake() {
lock (@lock) {
if (instance != null && instance != this) {
// Do not log anything, because switching scene triggers this code if there is an instance of the Singleton
//Debug.LogWarningFormat(this, "Multiple instances of Singleton<{0}> found on the scene.", typeof(T).FullName);
GameObject.Destroy(gameObject);
}
}
}
/// <summary>
/// Used by Unity to destroy the <see cref="GameObject" />.
///
/// Always call this method if overriding.
/// </summary>
protected virtual void OnDestroy() {
lock (@lock) {
if (instance == this) {
instantiated = false;
instance = null;
}
}
}
/// <summary>
/// Called by Unity when the application is quitting.
///
/// Always call this method if overriding.
/// </summary>
protected virtual void OnApplicationQuit() {
lock (@lock)
quitting = true;
}
}
}
using UnityEngine;
using SandWar.Unity.Tools;
using System;
// Ensure there will only be no more than one instance at a time.
public sealed class PlayerController : Singleton<PlayerController>
{
public Ship Ship;
void Update() {
Ship.MoveTowards(InputSettings.AxialInput);
if (InputSettings.IsBrakeDown)
Ship.Brake();
if (InputSettings.IsScreenDown())
Ship.MoveTo(InputSettings.GetScreenPoint(Ship.transform.position.z));
if (InputSettings.IsTriggerDown)
Ship.Fire();
}
}
using UnityEngine;
using SandWar.Unity.Tools;
using System;
// Ensure this singleton is not unloaded between scenes.
[Singleton.Persistent]
// Load "ShipPrefabs.prefab" from "Resources" folder as the instance of this singleton.
[Singleton.LoadFromResource("ShipPrefabs")]
// Do not show the instance of this singleton in the inspector.
// [Singleton.Hidden]
public sealed class ShipPrefabs : Singleton<ShipPrefabs>
{
public Transform Ship1Prefab;
public Transform Ship2Prefab;
}
using UnityEngine;
using SandWar.Unity.Tools;
using System;
// Automatically add an instance of this singleton to every scene.
[Singleton.SelfInitialized]
public sealed class TestAutorun : Singleton<TestAutorun>
{
void Start() {
Debug.Log("Hello, World!");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment