Created
May 3, 2016 04:51
-
-
Save rtlsilva/d8bf4ebe31807b2e650d89db8a164c3e to your computer and use it in GitHub Desktop.
Unity3D scripts that keep track of multiple independent time layers, each with its own time scale and all updated from a single timer. This allows, for example, to simultaneously have multiple entities operating at a sped up/slowed down rate and being frozen in time by simply changing their TimeLayer in the Inspector.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Diagnostics; | |
using UnityEngine; | |
public enum TimeLayer { | |
//Unity's time | |
GameTime, | |
//Custom time layer for UI | |
MenuTime, | |
//Unity's unscaled time | |
RealTime, | |
//Custom unscalable time layer | |
UserTime, | |
} | |
public static class TimeLayerExtensions { | |
public static float GetTime(this TimeLayer timeScale) { | |
switch (timeScale) { | |
case TimeLayer.GameTime: | |
return TimeKeeper.GameTime; | |
case TimeLayer.MenuTime: | |
return TimeKeeper.MenuTime; | |
case TimeLayer.RealTime: | |
return TimeKeeper.RealTime; | |
case TimeLayer.UserTime: | |
return TimeKeeper.UserTime; | |
} | |
UnityEngine.Debug.LogWarning("Undefined TimeLayer, defaulting to GameTime"); | |
return TimeKeeper.GameTime; | |
} | |
public static float GetDeltaTime(this TimeLayer timeScale) { | |
switch (timeScale) { | |
case TimeLayer.GameTime: | |
return TimeKeeper.GameDeltaTime; | |
case TimeLayer.MenuTime: | |
return TimeKeeper.MenuDeltaTime; | |
case TimeLayer.RealTime: | |
return TimeKeeper.RealDeltaTime; | |
case TimeLayer.UserTime: | |
return TimeKeeper.UserDeltaTime; | |
} | |
UnityEngine.Debug.LogWarning("Undefined TimeLayer, defaulting to GameTime"); | |
return TimeKeeper.GameDeltaTime; | |
} | |
} | |
public class TimeKeeper : Singleton<TimeKeeper> { | |
public static float TimerResolution { get; private set; } | |
public static float UserTime { get; private set; } | |
public static float MenuTime { get; private set; } | |
public static float GameTime { get { return Time.time; } } | |
public static float RealTime { get { return Time.unscaledTime; } } | |
public static float UserDeltaTime { get; private set; } | |
public static float MenuDeltaTime { get; private set; } | |
public static float GameDeltaTime { get { return Time.deltaTime; } } | |
public static float RealDeltaTime { get { return Time.unscaledDeltaTime; } } | |
public static float UserTimeScale { get; private set; } | |
public static float MenuTimeScale { get; set; } | |
public static float GameTimeScale { | |
get { return Time.timeScale; } | |
set { Time.timeScale = value; } | |
} | |
private Stopwatch timer; | |
private float totalMilliSeconds; | |
private float previousTotalMilliSeconds; | |
private float deltaMilliSeconds; | |
private IEnumerator timeUpdateEnumerator; | |
protected override void Awake() { | |
base.Awake(); | |
this.Initialize(); | |
this.UpdateTimes(); | |
if (TimeKeeper.TimerResolution > 16.0f) { | |
UnityEngine.Debug.Log("TimeKeeper resolution is greater than maximum admitted for 60 fps"); | |
} | |
} | |
private void Initialize() { | |
this.timer = new Stopwatch(); | |
this.timeUpdateEnumerator = this.TimeUpdateLoop(); | |
TimeKeeper.TimerResolution = 1000.0f / (float)Stopwatch.Frequency; | |
TimeKeeper.UserTimeScale = 1; | |
TimeKeeper.MenuTimeScale = 1; | |
TimeKeeper.GameTimeScale = 1; | |
} | |
private void OnDestroy() { | |
if (this.timeUpdateEnumerator != null) { | |
this.StopCoroutine(this.timeUpdateEnumerator); | |
} | |
} | |
private void OnLevelWasLoaded(int level) { | |
this.timer.Reset(); | |
this.timer.Start(); | |
this.PollTime(); | |
this.UpdateTimes(); | |
} | |
private void PollTime() { | |
this.timer.Stop(); | |
this.previousTotalMilliSeconds = this.totalMilliSeconds; | |
this.totalMilliSeconds = (float)(timer.ElapsedMilliseconds) / 1000.0f; | |
this.deltaMilliSeconds = Mathf.Max(0, this.totalMilliSeconds - this.previousTotalMilliSeconds); | |
this.timer.Start(); | |
} | |
private void Start() { | |
this.timer.Start(); | |
this.StartCoroutine(this.timeUpdateEnumerator); | |
} | |
//TODO: Figure out if there's a way to provide static access to this or just via TimeKeeper.Instance | |
public IEnumerator TimeLayerWaitForSeconds(float seconds, TimeLayer timeLayer = TimeLayer.GameTime) { | |
float elapsedTime = 0; | |
while (elapsedTime < seconds) { | |
yield return null; | |
elapsedTime += timeLayer.GetDeltaTime(); | |
} | |
} | |
private IEnumerator TimeUpdateLoop() { | |
while (true) { | |
this.PollTime(); | |
this.UpdateTimes(); | |
yield return null; | |
} | |
} | |
private void UpdateTimes() { | |
TimeKeeper.UserDeltaTime = this.deltaMilliSeconds; | |
TimeKeeper.MenuDeltaTime = this.deltaMilliSeconds * TimeKeeper.MenuTimeScale; | |
TimeKeeper.UserTime = this.totalMilliSeconds; | |
TimeKeeper.MenuTime = this.totalMilliSeconds * TimeKeeper.MenuTimeScale; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
/// <summary> | |
/// This is the base class for MonoBehaviours that can get their deltaTime from somewhere other than Time.deltaTime | |
/// </summary> | |
public class TimeLayeredBehaviour : MonoBehaviour { | |
[SerializeField] | |
private TimeLayer timeLayer = TimeLayer.GameTime; | |
public TimeLayer TimeLayer { | |
get { return this.timeLayer; } | |
set { this.timeLayer = value; } | |
} | |
public float DeltaTime { get { return this.timeLayer.GetDeltaTime(); } } | |
protected virtual void Update() { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment