Steering, HUD course, auto-index checkpoints

- PlayerController: MovementSpeed 5->25, ajout turnDamping+idleDrag pour virages nets
- CheckpointSystem: auto-assign checkpointIndex depuis l'array, déclenche race HUD sur CP0
- GameHUD: course visible dès passage de la porte (CP0), timer local indépendant du serveur

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 20:52:36 +02:00
parent a167930048
commit 7aa4a518db
3 changed files with 61 additions and 22 deletions

View File

@@ -15,9 +15,15 @@ public class PlayerController : MonoBehaviour
public float JumpForce = 5f; // Force applied when jumping public float JumpForce = 5f; // Force applied when jumping
public float MovementSpeed = 5f; // Speed of player movement public float MovementSpeed = 25f; // Speed of player movement
public float BoostSpeed = 2f; // Multiplicateur de vitesse sur GelOrange public float BoostSpeed = 2f; // Multiplicateur de vitesse sur GelOrange
[Header("Steering Feel")]
[Tooltip("Damps velocity perpendicular to input — higher = sharper turns")]
public float turnDamping = 7f;
[Tooltip("Horizontal friction when no input is held")]
public float idleDrag = 3f;
[Header("Bump Collision")] [Header("Bump Collision")]
public float bumpForce = 4f; // Impulse force when bumping a remote player public float bumpForce = 4f; // Impulse force when bumping a remote player
public float bumpCooldown = 0.25f; // Minimum time between bumps from the same player public float bumpCooldown = 0.25f; // Minimum time between bumps from the same player
@@ -261,21 +267,28 @@ public class PlayerController : MonoBehaviour
} }
} }
if (isForwardHeld) Vector3 inputDir = Vector3.zero;
if (isForwardHeld) inputDir += forward;
if (isBackwardsHeld) inputDir -= forward;
if (isRightHeld) inputDir += right;
if (isLeftHeld) inputDir -= right;
if (inputDir.sqrMagnitude > 0.01f)
{ {
rb.AddForce(forward * currentSpeed * Time.deltaTime, ForceMode.VelocityChange); inputDir.Normalize();
rb.AddForce(inputDir * currentSpeed * Time.deltaTime, ForceMode.VelocityChange);
// Counter-force on the lateral component (makes turns sharper)
Vector3 horizVel = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);
Vector3 perp = horizVel - Vector3.Project(horizVel, inputDir);
if (perp.sqrMagnitude > 0.01f)
rb.AddForce(-perp * turnDamping * Time.deltaTime, ForceMode.VelocityChange);
} }
if (isBackwardsHeld) else if (!isOnGelViolet)
{ {
rb.AddForce(-forward * currentSpeed * Time.deltaTime, ForceMode.VelocityChange); // Gradual horizontal slow-down when no key is held
} Vector3 horizVel = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);
if (isLeftHeld) rb.AddForce(-horizVel * idleDrag * Time.deltaTime, ForceMode.VelocityChange);
{
rb.AddForce(-right * currentSpeed * Time.deltaTime, ForceMode.VelocityChange);
}
if (isRightHeld)
{
rb.AddForce(right * currentSpeed * Time.deltaTime, ForceMode.VelocityChange);
} }
// GelViolet : colle la balle à la surface (sticky) // GelViolet : colle la balle à la surface (sticky)

View File

@@ -23,7 +23,10 @@ public class CheckpointSystem : MonoBehaviour
[Tooltip("Material to apply to finish line")] [Tooltip("Material to apply to finish line")]
public Material finishLineMaterial; public Material finishLineMaterial;
private int _localCheckpointIndex = 0; // how many checkpoints this local player passed public int LocalCheckpointIndex => _localCheckpointIndex;
public bool RaceStarted { get; private set; }
private int _localCheckpointIndex = 0;
private Renderer[] _checkpointRenderers; private Renderer[] _checkpointRenderers;
private bool _finished = false; private bool _finished = false;
@@ -38,9 +41,11 @@ public class CheckpointSystem : MonoBehaviour
for (int i = 0; i < checkpoints.Length; i++) for (int i = 0; i < checkpoints.Length; i++)
{ {
_checkpointRenderers[i] = checkpoints[i].GetComponent<Renderer>(); _checkpointRenderers[i] = checkpoints[i].GetComponent<Renderer>();
// Tag checkpoints with their index for trigger identification
checkpoints[i].gameObject.name = $"Checkpoint_{i}"; checkpoints[i].gameObject.name = $"Checkpoint_{i}";
// Auto-assign index so trigger knows its position in the sequence
var trigger = checkpoints[i].GetComponent<CheckpointTrigger>();
if (trigger != null) trigger.checkpointIndex = i;
} }
// Tell HUD total checkpoints // Tell HUD total checkpoints
@@ -52,9 +57,15 @@ public class CheckpointSystem : MonoBehaviour
public void OnLocalPlayerHitCheckpoint(int index) public void OnLocalPlayerHitCheckpoint(int index)
{ {
if (_finished) return; if (_finished) return;
// Must hit checkpoints in order
if (index != _localCheckpointIndex) return; if (index != _localCheckpointIndex) return;
// CP0 = start gate: activate race HUD and start local timer
if (index == 0)
{
RaceStarted = true;
GameHUD.Instance?.SetLocalRaceActive(true);
}
_localCheckpointIndex++; _localCheckpointIndex++;
NetworkManager.Instance?.SendCheckpoint(_localCheckpointIndex); NetworkManager.Instance?.SendCheckpoint(_localCheckpointIndex);
@@ -81,7 +92,9 @@ public class CheckpointSystem : MonoBehaviour
{ {
_localCheckpointIndex = 0; _localCheckpointIndex = 0;
_finished = false; _finished = false;
RaceStarted = false;
UpdateCheckpointVisuals(); UpdateCheckpointVisuals();
GameHUD.Instance?.SetLocalRaceActive(false);
} }
private void UpdateCheckpointVisuals() private void UpdateCheckpointVisuals()

View File

@@ -20,6 +20,10 @@ public class GameHUD : MonoBehaviour
private int _checkpointsCurrent = 0; private int _checkpointsCurrent = 0;
private int _checkpointsTotal = 5; private int _checkpointsTotal = 5;
// Local race state (activated when CP0 gate is crossed, independent of server phase)
private bool _localRaceActive = false;
private float _localRaceTimer = 0f;
// Countdown animation // Countdown animation
private float _lastCountdownShown = -1f; private float _lastCountdownShown = -1f;
private float _countdownPulse = 0f; private float _countdownPulse = 0f;
@@ -73,6 +77,8 @@ public class GameHUD : MonoBehaviour
{ {
if (_timerRunning) if (_timerRunning)
_roundTimer += Time.deltaTime; _roundTimer += Time.deltaTime;
if (_localRaceActive)
_localRaceTimer += Time.deltaTime;
if (_countdown > 0f && _countdown != _lastCountdownShown) if (_countdown > 0f && _countdown != _lastCountdownShown)
{ {
@@ -87,9 +93,15 @@ public class GameHUD : MonoBehaviour
public void SetRoundInfo(int round, string mode) { _roundNumber = round; _gameMode = mode; } public void SetRoundInfo(int round, string mode) { _roundNumber = round; _gameMode = mode; }
public void SetCheckpoint(int current, int total) { _checkpointsCurrent = current; _checkpointsTotal = total; } public void SetCheckpoint(int current, int total) { _checkpointsCurrent = current; _checkpointsTotal = total; }
public void SetLocalRaceActive(bool active)
{
_localRaceActive = active;
if (!active) _localRaceTimer = 0f;
}
void OnGUI() void OnGUI()
{ {
if (_phase == "lobby") return; if (_phase == "lobby" && !_localRaceActive) return;
ImGuiSkin.EnsureReady(); ImGuiSkin.EnsureReady();
var nm = NetworkManager.Instance; var nm = NetworkManager.Instance;
@@ -184,10 +196,11 @@ public class GameHUD : MonoBehaviour
GUI.Label(new Rect(prX, panelY + 40f, 168f, 22f), "joueurs en jeu", aliveLabel); GUI.Label(new Rect(prX, panelY + 40f, 168f, 22f), "joueurs en jeu", aliveLabel);
// ── Round timer (top center) ────────────────────────────────────── // ── Round timer (top center) ──────────────────────────────────────
if (_timerRunning) float displayTimer = _timerRunning ? _roundTimer : (_localRaceActive ? _localRaceTimer : -1f);
if (displayTimer >= 0f)
{ {
int mins = Mathf.FloorToInt(_roundTimer / 60f); int mins = Mathf.FloorToInt(displayTimer / 60f);
int secs = Mathf.FloorToInt(_roundTimer % 60f); int secs = Mathf.FloorToInt(displayTimer % 60f);
var timerStyle = new GUIStyle(GUI.skin.label) var timerStyle = new GUIStyle(GUI.skin.label)
{ {
alignment = TextAnchor.MiddleCenter, alignment = TextAnchor.MiddleCenter,
@@ -200,7 +213,7 @@ public class GameHUD : MonoBehaviour
} }
// ── Race: checkpoint progress (bottom center) ───────────────────── // ── Race: checkpoint progress (bottom center) ─────────────────────
if (_gameMode == "race" && _phase == "playing") if (_gameMode == "race" && (_phase == "playing" || _localRaceActive))
{ {
float bw = 300f; float bw = 300f;
float bx = (Screen.width - bw) / 2f; float bx = (Screen.width - bw) / 2f;