Skip to content

Instantly share code, notes, and snippets.

@rtlsilva
Created May 3, 2016 04:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rtlsilva/d8bf4ebe31807b2e650d89db8a164c3e to your computer and use it in GitHub Desktop.
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.
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;
}
}
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