Krotox Engine › Docs › Scripting API › Systems & Utilities

Time, math, logging & debug drawing

Time and time scale, pausing, the Mathf helpers, Vector2/Vector3/Vector4 and Quaternion, angle units, logging to the Console, drawing debug lines, boxes, circles and text, assertions, and profiler samples.

Time

float dt      = Time.DeltaTime;             // scaled seconds this frame (same as OnUpdate's ts)
float rawDt   = Time.UnscaledDeltaTime;     // real seconds, ignores TimeScale
float fixedDt = Time.FixedDeltaTime;        // the physics step (1/60 s by default)
float t       = Time.ElapsedTime;           // scaled seconds since Play
float rawT    = Time.UnscaledElapsedTime;
ulong frame   = Time.FrameCount;

Time.TimeScale = 0.5f;   // slow motion; 0 freezes gameplay while the UI keeps working

Time.TimeScale resets to 1 when Play stops.

Frame-rate independence. Multiply anything that changes per frame by ts (or Time.DeltaTime) so it moves at the same speed at 30, 60 or 120 frames per second:

void OnUpdate(float ts)
{
    Translation += new Vector3(Speed * ts, 0, 0);     // units per second, at any frame rate
}

Pausing. Game.Pause(), Game.Resume() and Game.IsPaused pause gameplay while the UI stays live. Use unscaled time (WaitForSecondsRealtime, unscaled: true on tweens) for anything that must keep moving on a pause menu.

Hit-stop (a short freeze on impact)

IEnumerator HitStop(float seconds)
{
    Time.TimeScale = 0.0f;
    yield return new WaitForSecondsRealtime(seconds);
    Time.TimeScale = 1.0f;
}

Mathf

// Constants
Mathf.PI; Mathf.Deg2Rad; Mathf.Rad2Deg; Mathf.Epsilon; Mathf.Infinity;

float c  = Mathf.Clamp(value, 0.0f, 100.0f);
float c1 = Mathf.Clamp01(value);
float l  = Mathf.Lerp(a, b, t);               // t clamped to 0..1
float lu = Mathf.LerpUnclamped(a, b, t);
float la = Mathf.LerpAngle(a, b, t);          // takes the short way around
float it = Mathf.InverseLerp(a, b, value);    // the t that gives value
float s  = Mathf.SmoothStep(from, to, t);     // eased in and out
float m  = Mathf.MoveTowards(current, target, maxDelta);
float ma = Mathf.MoveTowardsAngle(current, target, maxDelta);
float d  = Mathf.DeltaAngle(current, target); // shortest signed difference
float pp = Mathf.PingPong(time, length);      // 0 -> length -> 0
float r  = Mathf.Repeat(time, length);        // wraps, always positive
bool eq  = Mathf.Approximately(a, b);

// Plus Sin, Cos, Tan, Asin, Acos, Atan, Atan2, Sqrt, Abs, Round, Floor, Ceil, Sign, Pow, Log, Log10

Smooth follow without overshoot

// Exponential smoothing that behaves the same at any frame rate
float k = 1.0f - Mathf.Pow(0.001f, ts);           // reaches ~99.9% in one second
m_Camera.Translation = Vector3.Lerp(m_Camera.Translation, m_Target.Translation, k);

Vectors

Vector2, Vector3 and Vector4 are structs with public X, Y, Z, W fields.

Vector2 v = new Vector2(3, 4);
float len   = v.Length();                 // 5
Vector2 dir = v.Normalized;               // unit-length copy
v.Normalize();                            // in place

// Presets: Zero, One, Up, Down, Left, Right (Vector3 also has Forward and Back)
float dist  = Vector2.Distance(a, b);
float dot   = Vector2.Dot(a, b);
float cross = Vector2.Cross(a, b);         // scalar perp-dot product
float angle = Vector2.Angle(from, to);     // 0 to 180 degrees
float sAng  = Vector2.SignedAngle(from, to);   // -180 to 180, counter-clockwise positive
Vector2 r   = Vector2.Reflect(direction, normal);
Vector2 p   = Vector2.Perpendicular(direction);  // rotated 90 degrees counter-clockwise
Vector2 lp  = Vector2.Lerp(a, b, t);
Vector2 mt  = Vector2.MoveTowards(current, target, maxDistanceDelta);

Vector3 adds Cross (a vector), Project, ProjectOnPlane and SignedAngle(from, to, axis). Vector4 is also used for colours (red, green, blue, alpha).

Aim at a target

Vector3 toTarget = target.WorldPosition - WorldPosition;
float angle = Mathf.Atan2(toTarget.Y, toTarget.X);        // radians
Rotation = new Vector3(0, 0, angle);

Quaternion

Mostly for 3D, but handy for smooth rotation blends:

Quaternion q    = Quaternion.Euler(0, 0, 90);          // degrees
Quaternion mid  = Quaternion.Slerp(a, b, t);
Vector3 euler   = q.ToEulerAngles();                   // degrees
Vector3 rotated = q * Vector3.Right;
Units. Entity.Rotation, Rigidbody2DComponent.Rotation and UIRectComponent.Rotation are in radians. Quaternion.Euler, ToEulerAngles and Tween.RotateTo use degrees. Convert with Mathf.Deg2Rad and Mathf.Rad2Deg.

There is no Color, Matrix4 or Random type in the engine API. Colours are Vector4; for random numbers use System.Random or KrotoxEngine.PCG.PCGSeededRandom.

Logging

Log.Trace("Very detailed");
Log.Info(quot;Position: {Translation}");
Log.Warn("Low health");
Log.Error("Missing reference");

Messages appear in the editor's Console with their severity, and in logcat on Android. Outside the engine (for example in a unit-test runner that loads your DLL), Log falls back to Console.WriteLine.

Debug drawing

Draw shapes in the Viewport and the game view to see what your code is doing. Colours are Vector4. duration: 0 (the default) draws for one frame.

var red   = new Vector4(1, 0, 0, 1);
var green = new Vector4(0, 1, 0, 1);

Debug.DrawLine(Vector3.Zero, Vector3.Up * 5.0f, red, duration: 1.0f);
Debug.DrawRay(WorldPosition, Vector3.Right, green);
Debug.DrawBox(center, new Vector2(2, 1), red);                 // 2D rectangle
Debug.DrawCircle(center, 1.5f, green, duration: 2.0f);
Debug.DrawText(WorldPosition + Vector3.Up, quot;HP {m_Health}", new Vector4(1, 1, 1, 1), size: 0.4f);
Debug.DrawTrajectoryArc(WorldPosition, launchVelocity, new Vector3(0, -9.8f, 0));

Visualize a raycast

Vector2 origin = new Vector2(WorldPosition.X, WorldPosition.Y);
RaycastHit2D hit = Physics2D.Raycast(origin, Vector2.Down, 2.0f);
Vector3 end = hit.Hit ? new Vector3(hit.Point.X, hit.Point.Y, 0) : WorldPosition + Vector3.Down * 2.0f;
Debug.DrawLine(WorldPosition, end, hit.Hit ? new Vector4(0, 1, 0, 1) : new Vector4(1, 0, 0, 1));

Assertions and breakpoints

Debug.Assert(m_Health >= 0, "Health went negative");   // logs an error with the call stack
Debug.Break();                                          // editor only: pauses the game here

Profiling your code

Profiler.BeginSample("Pathfinding");
FindPath();
Profiler.EndSample();

Samples show up in profiling captures (and do nothing in other builds), next to the engine's own timings. The Profiler panel shows frame time and draw statistics live.