Created
May 13, 2023 17:39
-
-
Save unitycoder/b3338858245ed38f4dc2485db1fa3238 to your computer and use it in GitHub Desktop.
Timer
This file contains hidden or 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
// https://forum.unity.com/threads/building-an-accurate-clock-that-will-stay-accurate-over-time.1436062/ | |
using System; | |
using UnityEngine; | |
using TimeUtil = UnityEngine.Time; | |
using Debug = UnityEngine.Debug; | |
public class ReliableTime : MonoBehaviour { | |
[SerializeField] [Range(0f, 100f)] float _timeScale = 1f; | |
private const int SIXTY = 60; | |
private const double TOLERANCE = 1E-2; | |
static ReliableTime _instance; | |
static public ReliableTime Instance => _instance; | |
float _lastTime; // in seconds | |
float _measTime; // in seconds | |
int _minRegister; // in minutes | |
public float Time => SIXTY * _minRegister + _measTime; | |
public double TimeAsDouble => SIXTY * (double)_minRegister + (double)_measTime; | |
public (int h, int m, float s) GetTime() | |
=> ( (int)(_minRegister / (float)SIXTY), | |
_minRegister % SIXTY, | |
_measTime ); | |
void Awake() { | |
_instance = this; | |
setValues(TimeUtil.timeAsDouble); | |
_lastTime = _measTime; | |
} | |
void setValues(double absTime) { | |
_measTime = (float)(absTime % SIXTY); | |
_minRegister = (int)(absTime / SIXTY); | |
} | |
void Update() { | |
TimeUtil.timeScale = _timeScale; | |
_measTime += TimeUtil.deltaTime; | |
if(abs(_measTime - _lastTime) >= 1f) { | |
_lastTime = floor(_measTime); | |
if(_timeScale <= 1f) { | |
var time = GetTime(); | |
Debug.Log($"{time.h}:{time.m}:{time.s:F3}"); | |
} | |
var measured = TimeAsDouble; | |
var lapsed = TimeUtil.timeAsDouble; | |
if(Math.Abs(measured - lapsed) > TOLERANCE) { | |
var next = (measured + lapsed) / 2f; | |
Debug.Log($"correction: was {measured:F4} now {lapsed:F4}"); | |
setValues(lapsed); | |
} | |
if(_measTime >= SIXTY) { | |
_measTime -= SIXTY; | |
_minRegister++; | |
} | |
} | |
} | |
//---------------------------------------- | |
static readonly float D90 = .5f * MathF.PI; | |
static readonly float TAU = 4f * D90; | |
void OnDrawGizmos() { | |
var t = Time; | |
var pos = transform.position; | |
for(int i = 0; i < 12 * 5; i++) { | |
Color c; | |
float t1; | |
if(i % 5 == 0) { | |
c = (i % 3) == 0? Color.white : Color.cyan; | |
t1 = (i % 3) == 0? .7f : .9f; | |
} else { | |
c = Color.cyan; | |
t1 = .96f; | |
} | |
drawDial(pos, 1f, t1, 1f, TAU / (12f * 5f) * i, c); | |
} | |
var sec = (int)t % 60f; | |
var min = (int)t / 60f % 60f; | |
var hrs = (int)t / 3600f % 12f; | |
draw7SegmentValue(new Vector3(.12f, .35f, 0f), .02f, .07f, .01f, (int)hrs, Color.yellow); | |
draw7SegmentValue(new Vector3(.34f, .35f, 0f), .02f, .07f, .01f, (int)min, Color.yellow); | |
draw7SegmentValue(new Vector3(.56f, .35f, 0f), .02f, .07f, .01f, (int)sec, Color.yellow); | |
drawDial(pos, 1f, 0f, .55f, D90 - TAU / 12f * hrs, Color.white); | |
drawDial(pos, 1f, 0f, .8f, D90 - TAU / 60f * min, Color.blue); | |
drawDial(pos, 1f, 0f, .9f, D90 - TAU / 60f * easedSecondsDial(t), Color.red); | |
var msc = frac(t) * 1000f; | |
var microDial = new Vector3(-1f, 1f, 0f) / 3f; | |
drawDial(pos + microDial, 1f, 0f, .22f, D90 - TAU / 1000f * msc, Color.white); | |
drawCircle(pos + microDial, .22f, segments: 24); | |
drawPlrSine(pos + .1f * Vector3.up, new Vector3(2f, -.8f, 1.2f), pos + new Vector3(-.6f, -.333f, 0f), new Vector2(.6f, .1f), 6f * t, 6f * t + 12f * TAU, segments: 96, color: Color.yellow); | |
drawPlrSine(pos, new Vector3(MathF.PI * 10f / 6f, .2f, 0f), pos + new Vector3(-.6f, -.333f, 0f), new Vector2(.6f, .1f), -6f * t, -6f * t + 6f * TAU, segments: 24, color: Color.gray); | |
drawCircle(pos, 1f, color: Color.cyan); | |
} | |
void drawPlrSine(Vector3 o, Vector3 s, Vector3 c, Vector2 scale, float min, float max, int segments = 48, Color? color = null) { | |
if(color.HasValue) Gizmos.color = color.Value; | |
var last = Vector3.zero; | |
var step = (max - min) / segments; | |
for(int i = 0; i <= segments; i++) { | |
var next = mad(1f, new Vector3(2f * scale.x / segments * i, scale.y * sin(i * step + min), 0f), c); | |
if(i > 0) drawSeg(polar(ref o, ref s, last), polar(ref o, ref s, next)); | |
last = next; | |
} | |
static Vector3 polar(ref Vector3 c, ref Vector3 scale, Vector3 p) { | |
p -= c; return mad(scale.y * (p.y + scale.z), trig(scale.x * p.x + D90), c); | |
} | |
} | |
// void drawSine(Vector3 c, Vector2 scale, float min, float max, int segments = 48, Color? color = null) { | |
// if(color.HasValue) Gizmos.color = color.Value; | |
// var last = Vector3.zero; | |
// var step = (max - min) / segments; | |
// for(int i = 0; i <= segments; i++) { | |
// var next = mad(1f, new Vector3(2f * scale.x / segments * i, scale.y * sin(i * step + min), 0f), c); | |
// if(i > 0) drawSeg(last, next); | |
// last = next; | |
// } | |
// } | |
void drawDial(Vector3 c, float r, float t1, float t2, float rad, Color? color = null) { | |
if(color.HasValue) Gizmos.color = color.Value; | |
var q = mad(r, trig(rad), c); | |
drawSeg(lerp(c, q, t1), lerp(c, q, t2)); | |
} | |
void drawCircle(Vector3 c, float r, int segments = 48, Color? color = null) { | |
if(color.HasValue) Gizmos.color = color.Value; | |
var last = Vector2.zero; | |
var step = 2f * MathF.PI / segments; | |
for(int i = 0; i <= segments; i++) { | |
var next = mad(r, trig(i * step), c); | |
if(i > 0) drawSeg(last, next); | |
last = next; | |
} | |
} | |
void drawSeg(Vector3 a, Vector3 b, Color? color = null) { | |
if(color.HasValue) Gizmos.color = color.Value; | |
Gizmos.DrawLine(a, b); | |
} | |
static float easedSecondsDial(float n) { | |
n = mod(n + .7f - 1f, 60f); | |
var f = frac(n); | |
return floor(n) + (f < .5f? 0f : easeInOutBack(2f * (f - .5f))); | |
} | |
static float easeInOutBack(float n) { | |
const float c1 = 1.70158f; | |
const float c2 = c1 * 1.525f; | |
return n < .5f? (sqr(2f * n) * ((c2 + 1f) * 2f * n - c2)) * .5f | |
: (sqr(2f * n - 2f) * ((c2 + 1f) * (n * 2f - 2f) + c2) + 2f) * .5f; | |
} | |
void draw7SegmentValue(Vector3 p, float space, float scale, float slant, int value, Color color, int digits = 2) { | |
for(int i = digits - 1; i >= 0; i--) { | |
int pw = pow(10, i); | |
int digit = value / pw; | |
draw7SegmentDigit(p, scale, digit, slant, color); | |
value -= digit * pw; | |
p.x += scale + space; | |
} | |
} | |
// 0 | |
// ___ | |
// 5 | | 1 | |
// +---+ | |
// 4 | 6 | 2 | |
// '---' | |
// 3 | |
static Vector3[] _ppts; | |
static int[] _pieces = new int[] { 0x3f, 0x6, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x7, 0x7f, 0x6f }; | |
void draw7SegmentDigit(Vector3 p, float scale, int digit, float slant, Color? color = null) { | |
if(_ppts is null) { | |
_ppts = new Vector3[6]; | |
for(int y = 0; y < 3; y++) | |
for(int x = 0; x < 2; x++) | |
_ppts[2*y+x] = new Vector3(x, -y, 0f); | |
} | |
drawSeg(pt(0), pt(1), test(0)); | |
drawSeg(pt(1), pt(3), test(1)); | |
drawSeg(pt(3), pt(5), test(2)); | |
drawSeg(pt(4), pt(5), test(3)); | |
drawSeg(pt(2), pt(4), test(4)); | |
drawSeg(pt(0), pt(2), test(5)); | |
drawSeg(pt(2), pt(3), test(6)); | |
Vector3 pt(int index) => p + scale * _ppts[index] + new Vector3(slant * _ppts[index].y, 0f); | |
Color test(int bit) => (color.HasValue && (_pieces[digit] & (1 << bit)) != 0)? color.Value | |
: default(Color); | |
} | |
static float abs(float n) => Math.Abs(n); | |
static float floor(float n) => MathF.Floor(n); | |
static float frac(float n) => n % 1f; | |
static float sin(float rad) => MathF.Sin(rad); | |
static float cos(float rad) => MathF.Cos(rad); | |
static Vector3 mad(float a, Vector3 b, Vector3 c) => a * b + c; | |
static Vector3 lerp(Vector3 a, Vector3 b, float t) => (1f - t) * a + t * b; | |
static Vector3 trig(float rad) => new Vector3(cos(rad), sin(rad), 0f); | |
static float sqr(float n) => n * n; | |
static float mod(float n, float m) => (n %= m) < 0f? m + n : n; | |
static int pow(float b, float p) => (int)MathF.Pow(b, p); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment