feat: add Unity project (Assets, ProjectSettings)
This commit is contained in:
142
game/Assets/Scripts/UI/EliminationOverlay.cs
Normal file
142
game/Assets/Scripts/UI/EliminationOverlay.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Full-screen overlay for elimination, qualification, and game-end events.
|
||||
/// Fade in → hold → fade out automatically.
|
||||
/// </summary>
|
||||
public class EliminationOverlay : MonoBehaviour
|
||||
{
|
||||
private enum OverlayType { None, Eliminated, Qualified, GameEnd }
|
||||
|
||||
private OverlayType _type = OverlayType.None;
|
||||
private float _alpha = 0f;
|
||||
private string _winnerName = "";
|
||||
|
||||
private static Texture2D _bgTex;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (_bgTex == null)
|
||||
{
|
||||
_bgTex = new Texture2D(1, 1);
|
||||
_bgTex.SetPixel(0, 0, Color.white);
|
||||
_bgTex.Apply();
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowEliminated() => StartCoroutine(ShowOverlay(OverlayType.Eliminated, 3f));
|
||||
public void ShowQualified() => StartCoroutine(ShowOverlay(OverlayType.Qualified, 2.5f));
|
||||
public void ShowGameEnd(string winner)
|
||||
{
|
||||
_winnerName = winner;
|
||||
StartCoroutine(ShowOverlay(OverlayType.GameEnd, 6f));
|
||||
}
|
||||
|
||||
private IEnumerator ShowOverlay(OverlayType type, float holdTime)
|
||||
{
|
||||
_type = type;
|
||||
|
||||
// Fade in
|
||||
float t = 0f;
|
||||
while (t < 0.3f)
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
_alpha = Mathf.Clamp01(t / 0.3f);
|
||||
yield return null;
|
||||
}
|
||||
_alpha = 1f;
|
||||
|
||||
// Hold
|
||||
yield return new WaitForSeconds(holdTime);
|
||||
|
||||
// Fade out
|
||||
t = 0f;
|
||||
while (t < 0.4f)
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
_alpha = 1f - Mathf.Clamp01(t / 0.4f);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
_alpha = 0f;
|
||||
_type = OverlayType.None;
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (_type == OverlayType.None || _alpha < 0.01f) return;
|
||||
|
||||
// Background tint
|
||||
Color bgColor = _type switch
|
||||
{
|
||||
OverlayType.Eliminated => new Color(0.7f, 0.05f, 0.05f, _alpha * 0.55f),
|
||||
OverlayType.Qualified => new Color(0.05f, 0.55f, 0.15f, _alpha * 0.45f),
|
||||
OverlayType.GameEnd => new Color(0.05f, 0.05f, 0.3f, _alpha * 0.6f),
|
||||
_ => Color.clear
|
||||
};
|
||||
GUI.color = bgColor;
|
||||
GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), _bgTex);
|
||||
GUI.color = Color.white;
|
||||
|
||||
// Main text
|
||||
var mainStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = 72,
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
|
||||
string mainText = _type switch
|
||||
{
|
||||
OverlayType.Eliminated => "ÉLIMINÉ !",
|
||||
OverlayType.Qualified => "QUALIFIÉ !",
|
||||
OverlayType.GameEnd => "VICTOIRE !",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
Color textColor = _type switch
|
||||
{
|
||||
OverlayType.Eliminated => new Color(1f, 0.3f, 0.2f, _alpha),
|
||||
OverlayType.Qualified => new Color(0.3f, 1f, 0.5f, _alpha),
|
||||
OverlayType.GameEnd => new Color(1f, 0.85f, 0.1f, _alpha),
|
||||
_ => Color.clear
|
||||
};
|
||||
mainStyle.normal.textColor = textColor;
|
||||
GUI.Label(new Rect(0, Screen.height * 0.35f, Screen.width, 100f), mainText, mainStyle);
|
||||
|
||||
// Sub text
|
||||
var subStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = 26,
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
subStyle.normal.textColor = new Color(1f, 1f, 1f, _alpha * 0.85f);
|
||||
|
||||
string subText = _type switch
|
||||
{
|
||||
OverlayType.Eliminated => "Meilleure chance la prochaine fois !",
|
||||
OverlayType.Qualified => "Tu passes au round suivant !",
|
||||
OverlayType.GameEnd => $"Gagnant : {_winnerName}",
|
||||
_ => ""
|
||||
};
|
||||
GUI.Label(new Rect(0, Screen.height * 0.35f + 100f, Screen.width, 50f), subText, subStyle);
|
||||
|
||||
// Emoji accent
|
||||
var emojiStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = 48,
|
||||
};
|
||||
emojiStyle.normal.textColor = new Color(1f, 1f, 1f, _alpha * 0.7f);
|
||||
string emoji = _type switch
|
||||
{
|
||||
OverlayType.Eliminated => "💀",
|
||||
OverlayType.Qualified => "✅",
|
||||
OverlayType.GameEnd => "🏆",
|
||||
_ => ""
|
||||
};
|
||||
GUI.Label(new Rect(0, Screen.height * 0.35f - 80f, Screen.width, 70f), emoji, emojiStyle);
|
||||
}
|
||||
}
|
||||
2
game/Assets/Scripts/UI/EliminationOverlay.cs.meta
Normal file
2
game/Assets/Scripts/UI/EliminationOverlay.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51e21afb9dba1904bb425ac1fae825cb
|
||||
324
game/Assets/Scripts/UI/GameHUD.cs
Normal file
324
game/Assets/Scripts/UI/GameHUD.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// In-game HUD: round info, countdown, players alive, timer, checkpoints.
|
||||
/// Uses ImGuiSkin for visual consistency with LobbyUI.
|
||||
/// Only shown when a game is active (not in lobby).
|
||||
/// </summary>
|
||||
public class GameHUD : MonoBehaviour
|
||||
{
|
||||
// State
|
||||
private string _phase = "lobby";
|
||||
private float _countdown = 0f;
|
||||
private int _roundNumber = 1;
|
||||
private int _totalRounds = 4;
|
||||
private string _gameMode = "race";
|
||||
private float _roundTimer = 0f;
|
||||
private bool _timerRunning = false;
|
||||
|
||||
// Checkpoint info (set by CheckpointSystem)
|
||||
private int _checkpointsCurrent = 0;
|
||||
private int _checkpointsTotal = 5;
|
||||
|
||||
// Countdown animation
|
||||
private float _lastCountdownShown = -1f;
|
||||
private float _countdownPulse = 0f;
|
||||
|
||||
// --- Static textures ---
|
||||
private static Texture2D _bgTex;
|
||||
private static Texture2D _barFillTex;
|
||||
private static Texture2D _barBgTex;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
EnsureTextures();
|
||||
if (NetworkManager.Instance != null)
|
||||
{
|
||||
NetworkManager.Instance.OnRoundStart += OnRoundStart;
|
||||
NetworkManager.Instance.OnPhaseChanged += OnPhaseChanged;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (NetworkManager.Instance != null)
|
||||
{
|
||||
NetworkManager.Instance.OnRoundStart -= OnRoundStart;
|
||||
NetworkManager.Instance.OnPhaseChanged -= OnPhaseChanged;
|
||||
}
|
||||
}
|
||||
|
||||
void OnRoundStart(int round, string mode)
|
||||
{
|
||||
_roundNumber = round;
|
||||
_gameMode = mode;
|
||||
_roundTimer = 0f;
|
||||
_timerRunning = true;
|
||||
_checkpointsCurrent = 0;
|
||||
}
|
||||
|
||||
void OnPhaseChanged(string phase)
|
||||
{
|
||||
_phase = phase;
|
||||
if (phase == "playing") _timerRunning = true;
|
||||
else if (phase == "roundEnd" || phase == "gameEnd") _timerRunning = false;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_timerRunning)
|
||||
_roundTimer += Time.deltaTime;
|
||||
|
||||
if (_countdown > 0f && _countdown != _lastCountdownShown)
|
||||
{
|
||||
_countdownPulse = 1f;
|
||||
_lastCountdownShown = _countdown;
|
||||
}
|
||||
_countdownPulse = Mathf.Max(0f, _countdownPulse - Time.deltaTime * 3f);
|
||||
}
|
||||
|
||||
public void SetPhase(string phase) => _phase = phase;
|
||||
public void SetCountdown(float v) => _countdown = v;
|
||||
public void SetRoundInfo(int round, string mode) { _roundNumber = round; _gameMode = mode; }
|
||||
public void SetCheckpoint(int current, int total) { _checkpointsCurrent = current; _checkpointsTotal = total; }
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (_phase == "lobby") return;
|
||||
|
||||
ImGuiSkin.EnsureReady();
|
||||
var nm = NetworkManager.Instance;
|
||||
|
||||
// ── Countdown (center, large) ─────────────────────────────────────
|
||||
if (_phase == "countdown" && _countdown > 0f)
|
||||
{
|
||||
float scale = 1f + _countdownPulse * 0.4f;
|
||||
float fontSize = 96f * scale;
|
||||
var countStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = Mathf.RoundToInt(fontSize),
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
countStyle.normal.textColor = new Color(1f, 0.85f, 0.1f, 1f);
|
||||
GUI.Label(new Rect(0, Screen.height * 0.3f, Screen.width, 120f),
|
||||
Mathf.CeilToInt(_countdown).ToString(), countStyle);
|
||||
|
||||
// "Préparez-vous !" label below
|
||||
var subStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = 22,
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
subStyle.normal.textColor = new Color(1f, 1f, 1f, 0.8f);
|
||||
string modeLabel = _gameMode switch {
|
||||
"race" => "COURSE",
|
||||
"survival" => "SURVIVAL",
|
||||
"teams" => "ÉQUIPES",
|
||||
_ => _gameMode.ToUpper()
|
||||
};
|
||||
GUI.Label(new Rect(0, Screen.height * 0.3f + 110f, Screen.width, 36f),
|
||||
$"— {modeLabel} —", subStyle);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Top-left: Round & Mode ─────────────────────────────────────────
|
||||
float panelX = 12f;
|
||||
float panelY = 12f;
|
||||
float panelW = 220f;
|
||||
float panelH = 70f;
|
||||
|
||||
GUI.color = new Color(0.08f, 0.08f, 0.12f, 0.85f);
|
||||
GUI.DrawTexture(new Rect(panelX, panelY, panelW, panelH), _bgTex);
|
||||
GUI.color = Color.white;
|
||||
|
||||
var roundStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
fontSize = 14,
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
roundStyle.normal.textColor = new Color(1f, 0.85f, 0.1f);
|
||||
GUI.Label(new Rect(panelX + 8f, panelY + 4f, panelW - 16f, 28f),
|
||||
$"ROUND {_roundNumber} / {_totalRounds}", roundStyle);
|
||||
|
||||
var modeStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleLeft, fontSize = 12 };
|
||||
modeStyle.normal.textColor = new Color(0.7f, 0.7f, 0.85f);
|
||||
string modeFull = _gameMode switch {
|
||||
"race" => "COURSE", "survival" => "SURVIVAL", "teams" => "ÉQUIPES", _ => _gameMode.ToUpper()
|
||||
};
|
||||
GUI.Label(new Rect(panelX + 8f, panelY + 32f, panelW - 16f, 24f), modeFull, modeStyle);
|
||||
|
||||
// ── Top-right: Players alive ──────────────────────────────────────
|
||||
int alive = nm?.GetLocalPlayerState() != null
|
||||
? (_room_playersAlive > 0 ? _room_playersAlive : 1)
|
||||
: 0;
|
||||
|
||||
if (nm != null)
|
||||
{
|
||||
// read from room state if accessible
|
||||
}
|
||||
|
||||
float prX = Screen.width - 180f;
|
||||
GUI.color = new Color(0.08f, 0.08f, 0.12f, 0.85f);
|
||||
GUI.DrawTexture(new Rect(prX, panelY, 168f, panelH), _bgTex);
|
||||
GUI.color = Color.white;
|
||||
|
||||
var aliveStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = 28,
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
aliveStyle.normal.textColor = new Color(0.3f, 1f, 0.5f);
|
||||
GUI.Label(new Rect(prX, panelY + 2f, 168f, 40f), $"{_cachedPlayersAlive}", aliveStyle);
|
||||
|
||||
var aliveLabel = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = 11 };
|
||||
aliveLabel.normal.textColor = new Color(0.6f, 0.6f, 0.7f);
|
||||
GUI.Label(new Rect(prX, panelY + 40f, 168f, 22f), "joueurs en jeu", aliveLabel);
|
||||
|
||||
// ── Round timer (top center) ──────────────────────────────────────
|
||||
if (_timerRunning)
|
||||
{
|
||||
int mins = Mathf.FloorToInt(_roundTimer / 60f);
|
||||
int secs = Mathf.FloorToInt(_roundTimer % 60f);
|
||||
var timerStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = 18,
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
timerStyle.normal.textColor = new Color(0.85f, 0.85f, 0.9f, 0.9f);
|
||||
GUI.Label(new Rect(Screen.width * 0.5f - 60f, panelY, 120f, 40f),
|
||||
$"{mins:00}:{secs:00}", timerStyle);
|
||||
}
|
||||
|
||||
// ── Race: checkpoint progress (bottom center) ─────────────────────
|
||||
if (_gameMode == "race" && _phase == "playing")
|
||||
{
|
||||
float bw = 300f;
|
||||
float bx = (Screen.width - bw) / 2f;
|
||||
float by = Screen.height - 60f;
|
||||
|
||||
GUI.color = new Color(0.08f, 0.08f, 0.12f, 0.85f);
|
||||
GUI.DrawTexture(new Rect(bx - 8f, by - 8f, bw + 16f, 36f), _bgTex);
|
||||
GUI.color = Color.white;
|
||||
|
||||
// Background bar
|
||||
GUI.color = new Color(0.2f, 0.2f, 0.28f, 1f);
|
||||
GUI.DrawTexture(new Rect(bx, by, bw, 20f), _barBgTex);
|
||||
|
||||
// Fill
|
||||
float fill = _checkpointsTotal > 0 ? (float)_checkpointsCurrent / _checkpointsTotal : 0f;
|
||||
GUI.color = new Color(0.3f, 1f, 0.5f, 1f);
|
||||
GUI.DrawTexture(new Rect(bx, by, bw * fill, 20f), _barFillTex);
|
||||
GUI.color = Color.white;
|
||||
|
||||
var cpStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = 11 };
|
||||
cpStyle.normal.textColor = Color.white;
|
||||
GUI.Label(new Rect(bx, by, bw, 20f),
|
||||
$"Checkpoint {_checkpointsCurrent} / {_checkpointsTotal}", cpStyle);
|
||||
}
|
||||
|
||||
// ── Teams: score display (bottom center) ──────────────────────────
|
||||
if (_gameMode == "teams" && _phase == "playing")
|
||||
{
|
||||
float tw = 260f;
|
||||
float tx = (Screen.width - tw) / 2f;
|
||||
float ty = Screen.height - 60f;
|
||||
|
||||
GUI.color = new Color(0.08f, 0.08f, 0.12f, 0.85f);
|
||||
GUI.DrawTexture(new Rect(tx - 8f, ty - 8f, tw + 16f, 36f), _bgTex);
|
||||
GUI.color = Color.white;
|
||||
|
||||
var teamStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = 20, fontStyle = FontStyle.Bold };
|
||||
|
||||
// Red team score
|
||||
teamStyle.normal.textColor = new Color(1f, 0.3f, 0.3f);
|
||||
GUI.Label(new Rect(tx, ty - 2f, tw * 0.4f, 28f), $"{_cachedScoreRed}", teamStyle);
|
||||
|
||||
// Separator
|
||||
var sepStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = 16 };
|
||||
sepStyle.normal.textColor = new Color(0.5f, 0.5f, 0.6f);
|
||||
GUI.Label(new Rect(tx + tw * 0.4f, ty - 2f, tw * 0.2f, 28f), "vs", sepStyle);
|
||||
|
||||
// Blue team score
|
||||
teamStyle.normal.textColor = new Color(0.3f, 0.6f, 1f);
|
||||
GUI.Label(new Rect(tx + tw * 0.6f, ty - 2f, tw * 0.4f, 28f), $"{_cachedScoreBlue}", teamStyle);
|
||||
}
|
||||
|
||||
// ── Survival: death zone warning ──────────────────────────────────
|
||||
if (_gameMode == "survival" && _phase == "playing" && _deathZoneWarning > 0.01f)
|
||||
{
|
||||
GUI.color = new Color(1f, 0.3f, 0.1f, _deathZoneWarning * 0.4f);
|
||||
GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), _bgTex);
|
||||
GUI.color = Color.white;
|
||||
|
||||
var warnStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = 20, fontStyle = FontStyle.Bold };
|
||||
warnStyle.normal.textColor = new Color(1f, 0.4f, 0.2f, _deathZoneWarning);
|
||||
GUI.Label(new Rect(0, Screen.height * 0.8f, Screen.width, 36f), "⚠ ZONE DE MORT MONTE !", warnStyle);
|
||||
}
|
||||
}
|
||||
|
||||
// Static accessors for cross-script use
|
||||
public static GameHUD Instance { get; private set; }
|
||||
public static int TotalCheckpoints { get; set; } = 5;
|
||||
|
||||
// Cached values updated from NetworkManager state polling
|
||||
private int _cachedPlayersAlive = 0;
|
||||
private int _cachedScoreRed = 0;
|
||||
private int _cachedScoreBlue = 0;
|
||||
private float _deathZoneWarning = 0f;
|
||||
private int _room_playersAlive = 0;
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
// Poll NetworkManager for display values (avoids tight coupling via events for display-only data)
|
||||
if (NetworkManager.Instance == null || !NetworkManager.Instance.IsConnected) return;
|
||||
|
||||
// Survival: check death zone proximity
|
||||
if (_gameMode == "survival")
|
||||
{
|
||||
var localState = NetworkManager.Instance.GetLocalPlayerState();
|
||||
if (localState != null)
|
||||
{
|
||||
// deathZoneY is synced via NetworkState — we read via a static accessor pattern
|
||||
// For now, warn when player Y is within 5 units above death zone
|
||||
// (actual deathZoneY is not directly accessible here without extra plumbing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called by DeathZone.cs to update the warning
|
||||
public void SetDeathZoneWarning(float intensity) => _deathZoneWarning = intensity;
|
||||
public void SetTeamScores(int red, int blue) { _cachedScoreRed = red; _cachedScoreBlue = blue; }
|
||||
public void SetPlayersAlive(int count) => _cachedPlayersAlive = count;
|
||||
|
||||
private static void EnsureTextures()
|
||||
{
|
||||
if (_bgTex == null)
|
||||
{
|
||||
_bgTex = new Texture2D(1, 1);
|
||||
_bgTex.SetPixel(0, 0, Color.white);
|
||||
_bgTex.Apply();
|
||||
}
|
||||
if (_barBgTex == null)
|
||||
{
|
||||
_barBgTex = new Texture2D(1, 1);
|
||||
_barBgTex.SetPixel(0, 0, Color.white);
|
||||
_barBgTex.Apply();
|
||||
}
|
||||
if (_barFillTex == null)
|
||||
{
|
||||
_barFillTex = new Texture2D(1, 1);
|
||||
_barFillTex.SetPixel(0, 0, Color.white);
|
||||
_barFillTex.Apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
game/Assets/Scripts/UI/GameHUD.cs.meta
Normal file
2
game/Assets/Scripts/UI/GameHUD.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80ec66341fb507e45bf59c98c638c70a
|
||||
362
game/Assets/Scripts/UI/ImGuiSkin.cs
Normal file
362
game/Assets/Scripts/UI/ImGuiSkin.cs
Normal file
@@ -0,0 +1,362 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Centralized Dear ImGui–style skin for Unity's OnGUI / IMGUI.
|
||||
/// Provides cached textures, pre-built GUIStyles, and helper draw methods
|
||||
/// that replicate the classic Dear ImGui dark theme.
|
||||
///
|
||||
/// Usage: Call ImGuiSkin.Init() once (auto-called on first use),
|
||||
/// then use the static styles and helpers from any OnGUI method.
|
||||
/// </summary>
|
||||
public static class ImGuiSkin
|
||||
{
|
||||
// ════════════════════════════════════════════
|
||||
// PALETTE (Dear ImGui "Dark" defaults)
|
||||
// ════════════════════════════════════════════
|
||||
public static readonly Color ColWindowBg = new(0.06f, 0.06f, 0.10f, 0.94f); // #10101A
|
||||
public static readonly Color ColFrameBg = new(0.12f, 0.12f, 0.18f, 1f); // #1E1E2E
|
||||
public static readonly Color ColFrameHover = new(0.18f, 0.18f, 0.26f, 1f);
|
||||
public static readonly Color ColHeader = new(0.16f, 0.20f, 0.36f, 1f); // #283A5C
|
||||
public static readonly Color ColHeaderHover = new(0.22f, 0.28f, 0.48f, 1f);
|
||||
public static readonly Color ColButton = new(0.16f, 0.20f, 0.36f, 1f);
|
||||
public static readonly Color ColButtonHover = new(0.22f, 0.28f, 0.48f, 1f);
|
||||
public static readonly Color ColButtonActive= new(0.10f, 0.30f, 0.60f, 1f);
|
||||
public static readonly Color ColAccent = new(0.26f, 0.59f, 0.98f, 1f); // #4296FA
|
||||
public static readonly Color ColAccentDark = new(0.20f, 0.42f, 0.78f, 1f);
|
||||
public static readonly Color ColText = new(0.92f, 0.92f, 0.92f, 1f);
|
||||
public static readonly Color ColTextDim = new(0.55f, 0.55f, 0.60f, 1f);
|
||||
public static readonly Color ColBorder = new(0.28f, 0.28f, 0.36f, 1f);
|
||||
public static readonly Color ColSeparator = new(0.28f, 0.28f, 0.36f, 0.6f);
|
||||
public static readonly Color ColOverlay = new(0f, 0f, 0f, 0.65f);
|
||||
public static readonly Color ColGreen = new(0.3f, 1f, 0.3f, 1f);
|
||||
public static readonly Color ColRed = new(1f, 0.35f, 0.35f, 1f);
|
||||
public static readonly Color ColYellow = new(1f, 0.85f, 0.25f, 1f);
|
||||
public static readonly Color ColGold = new(1f, 0.84f, 0f, 1f);
|
||||
|
||||
// ════════════════════════════════════════════
|
||||
// CACHED TEXTURES (1×1)
|
||||
// ════════════════════════════════════════════
|
||||
public static Texture2D TexWindowBg { get; private set; }
|
||||
public static Texture2D TexFrameBg { get; private set; }
|
||||
public static Texture2D TexFrameHover { get; private set; }
|
||||
public static Texture2D TexHeader { get; private set; }
|
||||
public static Texture2D TexHeaderHover{ get; private set; }
|
||||
public static Texture2D TexButton { get; private set; }
|
||||
public static Texture2D TexButtonHover{ get; private set; }
|
||||
public static Texture2D TexButtonActive{ get; private set; }
|
||||
public static Texture2D TexAccent { get; private set; }
|
||||
public static Texture2D TexAccentDark { get; private set; }
|
||||
public static Texture2D TexBorder { get; private set; }
|
||||
public static Texture2D TexOverlay { get; private set; }
|
||||
public static Texture2D TexTransparent{ get; private set; }
|
||||
|
||||
// HUD-specific
|
||||
public static Texture2D TexHudStrip { get; private set; }
|
||||
|
||||
// ════════════════════════════════════════════
|
||||
// STYLES (lazy-initialized)
|
||||
// ════════════════════════════════════════════
|
||||
private static bool _inited;
|
||||
|
||||
// Window / panel
|
||||
public static GUIStyle WindowTitle { get; private set; }
|
||||
public static GUIStyle WindowSubtitle{ get; private set; }
|
||||
|
||||
// Section headers
|
||||
public static GUIStyle SectionHeader { get; private set; }
|
||||
|
||||
// Labels
|
||||
public static GUIStyle Label { get; private set; }
|
||||
public static GUIStyle LabelDim { get; private set; }
|
||||
public static GUIStyle LabelBold { get; private set; }
|
||||
public static GUIStyle LabelCenter { get; private set; }
|
||||
public static GUIStyle LabelRich { get; private set; }
|
||||
|
||||
// Fields (key-value pairs)
|
||||
public static GUIStyle FieldKey { get; private set; }
|
||||
public static GUIStyle FieldValue { get; private set; }
|
||||
|
||||
// Buttons
|
||||
public static GUIStyle Button { get; private set; }
|
||||
public static GUIStyle ButtonAccent { get; private set; }
|
||||
public static GUIStyle ButtonSmall { get; private set; }
|
||||
|
||||
// TextField
|
||||
public static GUIStyle TextField { get; private set; }
|
||||
|
||||
// HUD strip
|
||||
public static GUIStyle HudLabel { get; private set; }
|
||||
|
||||
// Status
|
||||
public static GUIStyle StatusGreen { get; private set; }
|
||||
public static GUIStyle StatusRed { get; private set; }
|
||||
|
||||
// Hint/footer
|
||||
public static GUIStyle Hint { get; private set; }
|
||||
public static GUIStyle Footer { get; private set; }
|
||||
|
||||
// Scroll
|
||||
public static GUIStyle ScrollView { get; private set; }
|
||||
|
||||
// ════════════════════════════════════════════
|
||||
// INIT
|
||||
// ════════════════════════════════════════════
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
if (_inited) return;
|
||||
_inited = true;
|
||||
|
||||
// --- Textures ---
|
||||
TexWindowBg = MakeTex(ColWindowBg);
|
||||
TexFrameBg = MakeTex(ColFrameBg);
|
||||
TexFrameHover = MakeTex(ColFrameHover);
|
||||
TexHeader = MakeTex(ColHeader);
|
||||
TexHeaderHover = MakeTex(ColHeaderHover);
|
||||
TexButton = MakeTex(ColButton);
|
||||
TexButtonHover = MakeTex(ColButtonHover);
|
||||
TexButtonActive= MakeTex(ColButtonActive);
|
||||
TexAccent = MakeTex(ColAccent);
|
||||
TexAccentDark = MakeTex(ColAccentDark);
|
||||
TexBorder = MakeTex(ColBorder);
|
||||
TexOverlay = MakeTex(ColOverlay);
|
||||
TexTransparent = MakeTex(Color.clear);
|
||||
TexHudStrip = MakeTex(new Color(0.04f, 0.04f, 0.08f, 0.85f));
|
||||
}
|
||||
|
||||
/// <summary>Call at the top of every OnGUI that uses this skin.</summary>
|
||||
public static void EnsureReady()
|
||||
{
|
||||
if (!_inited) Init();
|
||||
// Build styles lazily (needs GUI.skin to exist, so must be inside OnGUI)
|
||||
if (WindowTitle == null) BuildStyles();
|
||||
}
|
||||
|
||||
static void BuildStyles()
|
||||
{
|
||||
var pad4 = new RectOffset(4, 4, 2, 2);
|
||||
var pad6 = new RectOffset(6, 6, 4, 4);
|
||||
var pad8 = new RectOffset(8, 8, 4, 4);
|
||||
|
||||
// ── Window Title ──
|
||||
WindowTitle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 22,
|
||||
fontStyle = FontStyle.Bold,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
padding = pad6,
|
||||
richText = true
|
||||
};
|
||||
WindowTitle.normal.textColor = ColAccent;
|
||||
|
||||
WindowSubtitle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 13,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
padding = pad4,
|
||||
};
|
||||
WindowSubtitle.normal.textColor = ColTextDim;
|
||||
|
||||
// ── Section Header ──
|
||||
SectionHeader = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 12,
|
||||
fontStyle = FontStyle.Bold,
|
||||
padding = new RectOffset(6, 4, 4, 4),
|
||||
margin = new RectOffset(0, 0, 6, 2),
|
||||
richText = true,
|
||||
};
|
||||
SectionHeader.normal.background = TexHeader;
|
||||
SectionHeader.normal.textColor = ColAccent;
|
||||
|
||||
// ── Labels ──
|
||||
Label = new GUIStyle(GUI.skin.label) { fontSize = 13, richText = true };
|
||||
Label.normal.textColor = ColText;
|
||||
|
||||
LabelDim = new GUIStyle(Label);
|
||||
LabelDim.normal.textColor = ColTextDim;
|
||||
|
||||
LabelBold = new GUIStyle(Label) { fontStyle = FontStyle.Bold };
|
||||
|
||||
LabelCenter = new GUIStyle(Label) { alignment = TextAnchor.MiddleCenter };
|
||||
|
||||
LabelRich = new GUIStyle(Label) { richText = true, wordWrap = true };
|
||||
|
||||
// ── Field key/value ──
|
||||
FieldKey = new GUIStyle(Label) { fontSize = 12 };
|
||||
FieldKey.normal.textColor = ColTextDim;
|
||||
|
||||
FieldValue = new GUIStyle(Label) { fontSize = 12 };
|
||||
|
||||
// ── Buttons ──
|
||||
Button = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fontSize = 13,
|
||||
fontStyle = FontStyle.Bold,
|
||||
padding = pad8,
|
||||
margin = new RectOffset(2, 2, 2, 2),
|
||||
border = new RectOffset(1, 1, 1, 1),
|
||||
};
|
||||
Button.normal.background = TexButton;
|
||||
Button.normal.textColor = ColText;
|
||||
Button.hover.background = TexButtonHover;
|
||||
Button.hover.textColor = Color.white;
|
||||
Button.active.background = TexButtonActive;
|
||||
Button.active.textColor = Color.white;
|
||||
Button.focused.background = TexButtonHover;
|
||||
|
||||
ButtonAccent = new GUIStyle(Button)
|
||||
{
|
||||
fontSize = 16,
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
ButtonAccent.normal.background = TexAccentDark;
|
||||
ButtonAccent.normal.textColor = Color.white;
|
||||
ButtonAccent.hover.background = TexAccent;
|
||||
ButtonAccent.active.background = TexButtonActive;
|
||||
|
||||
ButtonSmall = new GUIStyle(Button) { fontSize = 11, padding = pad4 };
|
||||
|
||||
// ── TextField ──
|
||||
TextField = new GUIStyle(GUI.skin.textField)
|
||||
{
|
||||
fontSize = 14,
|
||||
padding = new RectOffset(8, 8, 6, 6),
|
||||
border = new RectOffset(2, 2, 2, 2),
|
||||
};
|
||||
TextField.normal.background = TexFrameBg;
|
||||
TextField.normal.textColor = ColText;
|
||||
TextField.focused.background = TexFrameHover;
|
||||
TextField.focused.textColor = Color.white;
|
||||
TextField.hover.background = TexFrameHover;
|
||||
// Cursor color follows textColor
|
||||
|
||||
// ── HUD Strip ──
|
||||
HudLabel = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = 13,
|
||||
richText = true,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
padding = new RectOffset(10, 10, 0, 0),
|
||||
};
|
||||
HudLabel.normal.textColor = ColText;
|
||||
|
||||
// ── Status ──
|
||||
StatusGreen = new GUIStyle(Label) { fontStyle = FontStyle.Bold };
|
||||
StatusGreen.normal.textColor = ColGreen;
|
||||
|
||||
StatusRed = new GUIStyle(Label) { fontStyle = FontStyle.Bold };
|
||||
StatusRed.normal.textColor = ColRed;
|
||||
|
||||
// ── Hint / Footer ──
|
||||
Hint = new GUIStyle(Label)
|
||||
{
|
||||
fontSize = 13,
|
||||
fontStyle = FontStyle.Italic,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
};
|
||||
Hint.normal.textColor = ColYellow;
|
||||
|
||||
Footer = new GUIStyle(Label)
|
||||
{
|
||||
fontSize = 11,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
};
|
||||
Footer.normal.textColor = new Color(1, 1, 1, 0.3f);
|
||||
|
||||
// ── ScrollView ──
|
||||
ScrollView = new GUIStyle(GUI.skin.scrollView);
|
||||
ScrollView.normal.background = TexFrameBg;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════
|
||||
// DRAWING HELPERS
|
||||
// ════════════════════════════════════════════
|
||||
|
||||
/// <summary>Draw a full-screen darkened overlay.</summary>
|
||||
public static void DrawOverlay()
|
||||
{
|
||||
GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), TexOverlay);
|
||||
}
|
||||
|
||||
/// <summary>Draw a window/panel background with a subtle border.</summary>
|
||||
public static void DrawWindowBg(Rect rect)
|
||||
{
|
||||
// Border (1px)
|
||||
GUI.DrawTexture(new Rect(rect.x - 1, rect.y - 1, rect.width + 2, rect.height + 2), TexBorder);
|
||||
// Fill
|
||||
GUI.DrawTexture(rect, TexWindowBg);
|
||||
}
|
||||
|
||||
/// <summary>Draw the HUD strip background.</summary>
|
||||
public static void DrawHudStripBg(float height)
|
||||
{
|
||||
GUI.DrawTexture(new Rect(0, 0, Screen.width, height), TexHudStrip);
|
||||
}
|
||||
|
||||
/// <summary>Begin a centered window panel. Returns content Rect (inset by padding).</summary>
|
||||
public static Rect BeginWindow(float width, float height, string title)
|
||||
{
|
||||
float x = (Screen.width - width) / 2f;
|
||||
float y = (Screen.height - height) / 2f;
|
||||
return BeginWindowAt(x, y, width, height, title);
|
||||
}
|
||||
|
||||
/// <summary>Begin a window at a specific position.</summary>
|
||||
public static Rect BeginWindowAt(float x, float y, float width, float height, string title)
|
||||
{
|
||||
DrawWindowBg(new Rect(x, y, width, height));
|
||||
|
||||
// Title bar
|
||||
float titleH = 32;
|
||||
GUI.DrawTexture(new Rect(x, y, width, titleH), TexHeader);
|
||||
GUI.Label(new Rect(x, y, width, titleH), title, WindowTitle);
|
||||
|
||||
// Content area
|
||||
float pad = 16;
|
||||
Rect content = new(x + pad, y + titleH + 8, width - pad * 2, height - titleH - pad - 8);
|
||||
GUILayout.BeginArea(content);
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>End a window started with BeginWindow/BeginWindowAt.</summary>
|
||||
public static void EndWindow()
|
||||
{
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
/// <summary>Draw a section header bar (e.g. "CONNECTION", "LOCAL PLAYER").</summary>
|
||||
public static void DrawSectionHeader(string text)
|
||||
{
|
||||
GUILayout.Label(text, SectionHeader);
|
||||
}
|
||||
|
||||
/// <summary>Draw a key-value field row.</summary>
|
||||
public static void DrawField(string key, string value)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(key + ":", FieldKey, GUILayout.Width(85));
|
||||
GUILayout.Label(value, FieldValue);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
/// <summary>Draw a thin horizontal separator line.</summary>
|
||||
public static void Separator()
|
||||
{
|
||||
GUILayout.Space(4);
|
||||
Rect r = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(1));
|
||||
GUI.DrawTexture(r, TexBorder);
|
||||
GUILayout.Space(4);
|
||||
}
|
||||
|
||||
// ─── Internal ───
|
||||
|
||||
static Texture2D MakeTex(Color c)
|
||||
{
|
||||
var t = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
t.SetPixel(0, 0, c);
|
||||
t.Apply();
|
||||
t.hideFlags = HideFlags.HideAndDontSave;
|
||||
return t;
|
||||
}
|
||||
}
|
||||
2
game/Assets/Scripts/UI/ImGuiSkin.cs.meta
Normal file
2
game/Assets/Scripts/UI/ImGuiSkin.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e92577bb278c4764cb7fd9810a56084b
|
||||
Reference in New Issue
Block a user