Add Framerate Counter (capped at 60)
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class PerformanceOverlay : MonoBehaviour
|
||||
{
|
||||
// ── singleton ────────────────────────────────────────────────────────────
|
||||
public static PerformanceOverlay Instance { get; private set; }
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
// A valid instance already exists — destroy this duplicate.
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
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<float> _frameTimes = new Queue<float>();
|
||||
private float _accumulatedTime;
|
||||
private float _timeSinceLastUpdate;
|
||||
|
||||
// Colour tags (works with both Text and TMP richtext)
|
||||
private const string GREEN = "<color=#00ff88>";
|
||||
private const string YELLOW = "<color=#ffcc00>";
|
||||
private const string RED = "<color=#ff4444>";
|
||||
private const string END = "</color>";
|
||||
|
||||
// ── 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)
|
||||
{
|
||||
_timeSinceLastUpdate = 0f;
|
||||
RefreshDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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 ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Toggle the overlay on / off (e.g. bind to F3).</summary>
|
||||
public void Toggle() => displayText.gameObject.SetActive(
|
||||
!displayText.gameObject.activeSelf);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 508ae74360f7e5249b100f7873b182f1
|
||||
Reference in New Issue
Block a user