#region copyright
// ------------------------------------------------------
// Copyright (C) Dmitriy Yukhanov [https://codestage.net]
// ------------------------------------------------------
#endregion
namespace CodeStage.AntiCheat.Common
{
using UnityEngine;
using UnityEngine.SceneManagement;
///
/// Base class for ACTk in-scene objects which able to survive scene switch.
///
/// \htmlonlyWARNING:\endhtmlonly Will behave incorrectly if created within any non-default [RuntimeInitializeOnLoadMethod] except RuntimeInitializeLoadType.AfterSceneLoad (which is default).
public abstract class KeepAliveBehaviour : MonoBehaviour where T: KeepAliveBehaviour
{
///
/// Will survive new level (scene) load if checked. Otherwise it will be destroyed.
///
/// On dispose Behaviour follows 2 rules:
/// - if Game Object's name is "Anti-Cheat Toolkit": it will be automatically
/// destroyed if no other Behaviours left attached regardless of any other components or children;
/// - if Game Object's name is NOT "Anti-Cheat Toolkit": it will be automatically destroyed only
/// if it has neither other components nor children attached;
[Tooltip("Detector will survive new level (scene) load if checked.")]
public bool keepAlive = true;
protected int instancesInScene;
protected bool selfDestroying;
private Scene originalScene;
#region static instance
///
/// Allows reaching public properties from code.
/// Can be null if behaviour does not exist in scene or if accessed at or before Awake phase.
///
public static T Instance { get; protected set; }
protected static T GetOrCreateInstance
{
get
{
if (Instance != null)
return Instance;
Instance = ContainerHolder.AddContainerComponent();
return Instance;
}
}
#endregion
#region unity messages
// making sure it will reset statics even if domain reload is disabled
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void SubsystemRegistration()
{
Instance = null;
}
#if ACTK_EXCLUDE_OBFUSCATION
[System.Reflection.Obfuscation(Exclude = true)]
#endif
protected virtual void Awake()
{
selfDestroying = false;
instancesInScene++;
if (Init(Instance))
{
Instance = (T)this;
}
}
protected virtual void Start()
{
ContainerHolder.TrySetContainer(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
SceneManager.sceneUnloaded += OnSceneUnloaded;
}
protected virtual void OnDestroy()
{
var componentsCount = GetComponentsInChildren().Length;
if (transform.childCount == 0 && componentsCount <= 2)
{
Destroy(gameObject);
}
else if (name == ContainerHolder.ContainerName && componentsCount <= 2)
{
Destroy(gameObject);
}
instancesInScene--;
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneUnloaded -= OnSceneUnloaded;
if (Instance == this)
{
Instance = null;
}
}
#endregion
protected virtual void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (instancesInScene < 2)
{
if (!keepAlive && mode != LoadSceneMode.Additive)
DisposeInternal();
}
else
{
if (!keepAlive && Instance != this)
DisposeInternal();
}
}
protected virtual void OnSceneUnloaded(Scene scene)
{
if (originalScene == scene)
{
if (!keepAlive)
DisposeInternal();
}
}
protected virtual bool Init(T instance)
{
if (instance != null && instance != this && instance.keepAlive)
{
DisposeInternal();
return false;
}
originalScene = gameObject.scene;
DontDestroyOnLoad(transform.parent != null ? transform.root.gameObject : gameObject);
return true;
}
protected virtual void DisposeInternal()
{
if (selfDestroying)
return;
selfDestroying = true;
Destroy(this);
}
}
}