feat: free-roam mode + fix multiplayer sync + remote player polish
Backend (ArenaRoom.js):
- Strip race state machine (lobby/countdown/playing/round/qualify). Persistent
"playing" phase, no rounds, no checkpoints. Free-roam multi.
- Spawn lowered to y=1.5 (was 5) + MIN_DIST raised to 5 (was 3) to avoid
ejecting overlapping players at connect.
- Schema kept intact (handshake-safe); deprecated fields default-valued.
- npm run schema:gen wired (anti-drift codegen).
Unity client:
- C# schema generated by schema-codegen into RolldSchema namespace
(Generated/GameState.cs, Generated/Player.cs). NetworkSchema.cs removed —
handshake no longer scans global namespace.
- NetworkManager: typed Room<GameState>, callbacks rebound, seeds players
already in room on join.
- RemotePlayerController:
* Post-spawn 1.5s grace window (BumpReady) — local PlayerController.HandleBump
ignores remotes during grace.
* Solid SphereCollider disabled during grace, re-enabled afterwards — fixes
the kinematic-vs-dynamic eject when a new client spawns inside someone.
* NPCBall prefab material switched from invisible-in-URP Default-Material to
BallShader.shadergraph.
* TrailRenderer added, tinted with player's chosen color.
* Name label distance-scales (1x-8x) so pseudos remain readable far away.
- GameHUD: OnGUI emptied — race UI (rounds, mode, timer, playersAlive) gone.
- GameCanvas.jsx: BUILD_PREFIX/VERSION bumped for cache-bust.
Frontend WebGL build (pretty_build): final build with all the above.
This commit is contained in:
@@ -101,108 +101,7 @@ public class GameHUD : MonoBehaviour
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (_phase == "lobby" && !_localRaceActive) 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 ──────────────────────────────────────
|
||||
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) ──────────────────────────────────────
|
||||
float displayTimer = _timerRunning ? _roundTimer : (_localRaceActive ? _localRaceTimer : -1f);
|
||||
if (displayTimer >= 0f)
|
||||
{
|
||||
int mins = Mathf.FloorToInt(displayTimer / 60f);
|
||||
int secs = Mathf.FloorToInt(displayTimer % 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 UI disabled — free-roam mode has no rounds/countdown/timer.
|
||||
}
|
||||
|
||||
// Static accessors for cross-script use
|
||||
|
||||
Reference in New Issue
Block a user