diff --git a/Assets/_Content/Scenes/TitleScreen.unity b/Assets/_Content/Scenes/TitleScreen.unity
index 3ad53f2f..3c660922 100644
--- a/Assets/_Content/Scenes/TitleScreen.unity
+++ b/Assets/_Content/Scenes/TitleScreen.unity
@@ -273,7 +273,7 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1195733056}
- m_Father: {fileID: 1741102573}
+ m_Father: {fileID: 1195136605}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -1342,12 +1342,12 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 508ae74360f7e5249b100f7873b182f1, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::PerformanceOverlay
- displayText: {fileID: 1195733054}
- sampleSize: 60
- updateInterval: 0.1
- pipelineLatencyFrames: 2
- goodFPS: 60
- warningFPS: 30
+ _displayText: {fileID: 1195733054}
+ _sampleSize: 60
+ _updateInterval: 0.1
+ _pipelineLatencyFrames: 1
+ _goodFps: 60
+ _warningFps: 30
--- !u!4 &1195136605
Transform:
m_ObjectHideFlags: 0
@@ -1356,11 +1356,12 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1195136603}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
- m_Children: []
+ m_Children:
+ - {fileID: 431578944}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1195733053
@@ -2233,7 +2234,6 @@ Transform:
m_Children:
- {fileID: 630642127}
- {fileID: 1505757215}
- - {fileID: 431578944}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1757053106 stripped
diff --git a/Assets/_Content/Scripts/Core/Utils/PerformanceOverlay.cs b/Assets/_Content/Scripts/Core/Utils/PerformanceOverlay.cs
index 4caa2bf1..079a712f 100644
--- a/Assets/_Content/Scripts/Core/Utils/PerformanceOverlay.cs
+++ b/Assets/_Content/Scripts/Core/Utils/PerformanceOverlay.cs
@@ -2,116 +2,119 @@ using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
-///
-/// PerformanceOverlay — displays FPS, frame time, and estimated input lag.
-/// Singleton: only one instance will ever exist. Safe to place in every scene —
-/// duplicates are destroyed automatically. Persists across scene loads.
-/// Requires: a Canvas with a Text (or TMP_Text) component assigned in the Inspector.
-///
-public class PerformanceOverlay : MonoBehaviour
+namespace OctoCore.UI
{
- // ── singleton ────────────────────────────────────────────────────────────
- public static PerformanceOverlay Instance { get; private set; }
-
- void Awake()
+ ///
+ /// Displays FPS, frame time, and estimated input lag.
+ /// Singleton that persists across scene loads. Safe to place in every scene — duplicates are destroyed automatically.
+ /// Requires a Canvas Text component assigned in the Inspector.
+ ///
+ public class PerformanceOverlay : MonoBehaviour
{
- if (Instance != null && Instance != this)
+ #region Constants and Static Fields
+
+ public static PerformanceOverlay Instance { get; private set; }
+
+ private const string GREEN = "";
+ private const string YELLOW = "";
+ private const string RED = "";
+ private const string END = "";
+
+ #endregion
+
+ #region Serialized Fields
+
+ [Header("UI Reference")]
+ [SerializeField] private Text _displayText;
+
+ [Header("FPS Settings")]
+ [Tooltip("Number of frames to average for a smooth reading.")]
+ [SerializeField] private int _sampleSize = 60;
+
+ [Tooltip("How often (seconds) the display refreshes.")]
+ [SerializeField] private float _updateInterval = 0.1f;
+
+ [Header("Input Lag Settings")]
+ [Tooltip("Render pipeline latency in frames. 1 for Built-in/URP, 2 for HDRP.")]
+ [SerializeField] private int _pipelineLatencyFrames = 1;
+
+ [Header("Thresholds")]
+ [SerializeField] private int _goodFps = 60;
+ [SerializeField] private int _warningFps = 30;
+
+ #endregion
+
+ #region Private Instance Fields
+
+ private readonly Queue _frameTimes = new Queue();
+ private float _accumulatedTime;
+ private float _timeSinceLastUpdate;
+
+ #endregion
+
+ #region Unity Lifecycle Methods
+
+ private void Awake()
{
- // A valid instance already exists — destroy this duplicate.
- Destroy(gameObject);
- return;
+ if (Instance != null && Instance != this)
+ {
+ Destroy(gameObject);
+ return;
+ }
+
+ Instance = this;
+ DontDestroyOnLoad(gameObject);
}
- Instance = this;
- DontDestroyOnLoad(gameObject);
- }
-
- [Header("UI Reference")]
- [Tooltip("Assign a UI Text or TextMeshPro Text component here.")]
- public Text displayText; // swap to TMP_Text if using TextMeshPro
-
- [Header("FPS Settings")]
- [Tooltip("How many frames to average over for a smooth FPS reading.")]
- public int sampleSize = 60;
-
- [Tooltip("How often (seconds) the display refreshes.")]
- public float updateInterval = 0.1f;
-
- [Header("Input Lag Settings")]
- [Tooltip("Frames of render pipeline latency added on top of frame time.\n" +
- "Typical values: 0–1 for Forward, 1–2 for Deferred / URP with buffering.")]
- public int pipelineLatencyFrames = 1;
-
- [Header("Thresholds (for colour coding)")]
- public int goodFPS = 60;
- public int warningFPS = 30;
-
- // ── internals ────────────────────────────────────────────────────────────
- private readonly Queue _frameTimes = new Queue();
- private float _accumulatedTime;
- private float _timeSinceLastUpdate;
-
- // Colour tags (works with both Text and TMP richtext)
- private const string GREEN = "";
- private const string YELLOW = "";
- private const string RED = "";
- private const string END = "";
-
- // ── lifecycle ────────────────────────────────────────────────────────────
- void Update()
- {
- float dt = Time.unscaledDeltaTime; // unscaled so pausing doesn't skew readings
-
- // Rolling window
- _frameTimes.Enqueue(dt);
- _accumulatedTime += dt;
- if (_frameTimes.Count > sampleSize)
- _accumulatedTime -= _frameTimes.Dequeue();
-
- _timeSinceLastUpdate += dt;
- if (_timeSinceLastUpdate >= updateInterval)
+ private void Update()
{
- _timeSinceLastUpdate = 0f;
- RefreshDisplay();
+ float dt = Time.unscaledDeltaTime;
+
+ _frameTimes.Enqueue(dt);
+ _accumulatedTime += dt;
+ if (_frameTimes.Count > _sampleSize)
+ _accumulatedTime -= _frameTimes.Dequeue();
+
+ _timeSinceLastUpdate += dt;
+ if (_timeSinceLastUpdate >= _updateInterval)
+ {
+ _timeSinceLastUpdate = 0f;
+ RefreshDisplay();
+ }
}
+
+ #endregion
+
+ #region Public Methods
+
+ /// Toggles the overlay on / off.
+ public void Toggle() => _displayText.gameObject.SetActive(!_displayText.gameObject.activeSelf);
+
+ #endregion
+
+ #region Private Methods
+
+ private void RefreshDisplay()
+ {
+ if (_displayText == null) return;
+
+ float avgFrameTime = _accumulatedTime / _frameTimes.Count;
+ float fps = 1f / avgFrameTime;
+ float frameTimeMs = avgFrameTime * 1000f;
+
+ // HID polling floor (~8 ms for 125 Hz USB; ~1 ms for 1000 Hz)
+ float inputLagMs = frameTimeMs * (1 + _pipelineLatencyFrames) + 8f;
+
+ string fpsColour = fps >= _goodFps ? GREEN
+ : fps >= _warningFps ? YELLOW
+ : RED;
+
+ _displayText.text =
+ $"{fpsColour}{fps,5:F0} FPS{END}\n" +
+ $"Frame {frameTimeMs,6:F2} ms\n" +
+ $"Lag ≈ {inputLagMs,6:F2} ms";
+ }
+
+ #endregion
}
-
- // ── display logic ────────────────────────────────────────────────────────
- void RefreshDisplay()
- {
- if (displayText == null) return;
-
- // ── FPS ──────────────────────────────────────────────────────────────
- float avgFrameTime = _accumulatedTime / _frameTimes.Count; // seconds
- float fps = 1f / avgFrameTime;
- float frameTimeMs = avgFrameTime * 1000f;
-
- // ── Input Lag estimate ───────────────────────────────────────────────
- // Input lag ≈ time from physical input to pixel on screen.
- // Best estimate without hardware: frame time + pipeline latency frames.
- //
- // inputLag ≈ frameTime + (pipelineLatencyFrames × frameTime)
- // = frameTime × (1 + pipelineLatencyFrames)
- //
- // Add 8 ms as a rough USB / Bluetooth HID polling floor.
- float hidPollMs = 8f; // ~1 frame @ 125 Hz polling; 1 ms @ 1000 Hz
- float inputLagMs = frameTimeMs * (1 + pipelineLatencyFrames) + hidPollMs;
-
- // ── colour coding ────────────────────────────────────────────────────
- string fpsColour = fps >= goodFPS ? GREEN
- : fps >= warningFPS ? YELLOW
- : RED;
-
- // ── format ───────────────────────────────────────────────────────────
- displayText.text =
- $"{fpsColour}{fps,5:F0} FPS{END}\n" +
- $"Frame {frameTimeMs,6:F2} ms\n" +
- $"Lag ≈ {inputLagMs,6:F2} ms";
- }
-
- // ── public helpers ───────────────────────────────────────────────────────
-
- /// Toggle the overlay on / off (e.g. bind to F3).
- public void Toggle() => displayText.gameObject.SetActive(
- !displayText.gameObject.activeSelf);
-}
+}
\ No newline at end of file