diff --git a/game/Assets/PlayerController.cs b/game/Assets/PlayerController.cs index a3f6d24..880b34a 100644 --- a/game/Assets/PlayerController.cs +++ b/game/Assets/PlayerController.cs @@ -44,6 +44,7 @@ public class PlayerController : MonoBehaviour public float maxVelocity = 120f; // Velocity cap to prevent infinite acceleration public float respawnY = -10f; // Y threshold for respawn private Vector3 _spawnPos = new Vector3(0f, 3f, -30f); + private Rigidbody _rb; // Squash & stretch private bool _isSquashing = false; @@ -80,10 +81,12 @@ public class PlayerController : MonoBehaviour void Start() { Debug.Log("PlayerController script initialized."); - // Cursor lock is handled by LobbyUI on connect/disconnect - _meshTransform = transform; // Will be the sphere itself for squash + _rb = GetComponent(); + _meshTransform = transform; } + public void SetSpawnPosition(Vector3 pos) => _spawnPos = pos; + /// /// Called by LobbyUI after connecting. Sets up the local player /// with a floating name label and a 50% color tint. @@ -208,16 +211,15 @@ public class PlayerController : MonoBehaviour // --- Respawn if fallen off the map --- if (transform.position.y < respawnY) { - Rigidbody rbRespawn = GetComponent(); - if (rbRespawn != null) + if (_rb != null) { - rbRespawn.linearVelocity = Vector3.zero; - rbRespawn.angularVelocity = Vector3.zero; + _rb.linearVelocity = Vector3.zero; + _rb.angularVelocity = Vector3.zero; + _rb.useGravity = true; } transform.position = _spawnPos; isOnGelViolet = false; isOnGelOrange = false; - if (rbRespawn != null) rbRespawn.useGravity = true; Debug.Log("[Player] Respawned after falling."); return; } @@ -227,7 +229,7 @@ public class PlayerController : MonoBehaviour _fallWarningAlpha = Mathf.Lerp(_fallWarningAlpha, fallTarget, Time.deltaTime * 5f); // Mouvement continu selon les directions maintenues - Rigidbody rb = GetComponent(); + Rigidbody rb = _rb; if (rb != null) { float currentSpeed = MovementSpeed; @@ -326,42 +328,25 @@ public class PlayerController : MonoBehaviour void OnCollisionStay(Collision collision) { Collider col = collision.collider; - if (col != null && col.sharedMaterial != null) - { - if (col.sharedMaterial.name.Contains("GelOrange")) - { - isOnGelOrange = true; - } - else - { - isOnGelOrange = false; - } + if (col == null || col.sharedMaterial == null) return; - if (col.sharedMaterial.name.Contains("GelViolet")) + if (col.sharedMaterial.name.Contains("GelOrange")) + { + isOnGelOrange = true; + } + + if (col.sharedMaterial.name.Contains("GelViolet")) + { + if (!isOnGelViolet) { - if (!isOnGelViolet) - { - // Premier contact : sauvegarder le drag et augmenter le frein - Rigidbody rb = GetComponent(); - if (rb != null) - { - originalDrag = rb.linearDamping; - rb.linearDamping = 1f; // Fort frein pour éviter le catapultage aux bords - } - } - isOnGelViolet = true; - // Normale instantanée de la surface de contact - Vector3 avgNormal = Vector3.zero; - foreach (ContactPoint contact in collision.contacts) - { - avgNormal += contact.normal; - } - stickyNormal = avgNormal.normalized; - } - else - { - isOnGelViolet = false; + originalDrag = _rb != null ? _rb.linearDamping : 0f; + if (_rb != null) _rb.linearDamping = 1f; } + isOnGelViolet = true; + Vector3 avgNormal = Vector3.zero; + foreach (ContactPoint contact in collision.contacts) + avgNormal += contact.normal; + stickyNormal = avgNormal.normalized; } } @@ -424,17 +409,9 @@ public class PlayerController : MonoBehaviour public void PerformJump(float force) { - Rigidbody rb = GetComponent(); - if (rb != null) - { - // Jump direction: surface normal when on sticky, otherwise up - Vector3 jumpDir = isOnGelViolet ? stickyNormal : Vector3.up; - rb.AddForce(jumpDir * force, ForceMode.Impulse); - } - else - { - Debug.LogWarning("Rigidbody component not found on PlayerController."); - } + if (_rb == null) return; + Vector3 jumpDir = isOnGelViolet ? stickyNormal : Vector3.up; + _rb.AddForce(jumpDir * force, ForceMode.Impulse); } private bool IsGrounded() @@ -546,11 +523,8 @@ public class PlayerController : MonoBehaviour // Add slight upward component so the ball lifts off the ground dir = (dir + Vector3.up * 0.3f).normalized; - var rb = GetComponent(); - if (rb != null) - { - rb.AddForce(dir * bumpForce, ForceMode.Impulse); - } + if (_rb != null) + _rb.AddForce(dir * bumpForce, ForceMode.Impulse); } void OnCollisionEnter(Collision collision) @@ -742,10 +716,9 @@ public class PlayerController : MonoBehaviour // ======================== // SPEED INDICATOR // ======================== - Rigidbody rbHud = GetComponent(); - if (rbHud != null) + if (_rb != null) { - float speed = rbHud.linearVelocity.magnitude; + float speed = _rb.linearVelocity.magnitude; var speedStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleRight, diff --git a/game/Assets/Scripts/ArenaZoneBuilder.cs b/game/Assets/Scripts/ArenaZoneBuilder.cs deleted file mode 100644 index d7d19db..0000000 --- a/game/Assets/Scripts/ArenaZoneBuilder.cs +++ /dev/null @@ -1,573 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -/// -/// Builds a compact 80×80 tutorial arena at runtime with connected zones: -/// -/// Zone A (South) — Movement basics + jump gaps -/// Zone B (Center-S) — Bounce (GelBleu) training -/// Zone C (West) — Speed (GelOrange) training -/// Zone D (NW) — Moving platforms -/// Zone E (NE) — Sticky walls (GelViolet) training -/// Zone F (North) — Combo challenge (all gels + platforms) -/// Zone G (Center) — Central tower (final challenge) -/// -/// Spawn: (0, 2, -30) — south edge -/// -/// Ball capabilities reference (from PlayerController): -/// Max jump height ≈ 5.1 units (JumpForce=10, gravity=9.81) -/// Horizontal jump range ≈ 6-8 units at cruising speed -/// GelBleu bounce = perfect elasticity (bounciness 1.0, combine=Max) -/// GelOrange speed = ×3.83 multiplier (~19 u/s) -/// GelViolet sticky = surface-relative movement, jump = surface normal -/// Ball radius = 0.5, min platform 2×2 for playability -/// -/// Loads PhysicMaterials from Resources/ folder. -/// Attach to a persistent GameObject (e.g. NetworkManager). -/// -public class ArenaZoneBuilder : MonoBehaviour -{ - private PhysicsMaterial _matGelBleu; - private PhysicsMaterial _matGelOrange; - private PhysicsMaterial _matGelViolet; - private PhysicsMaterial _matBouncy; - private PhysicsMaterial _matNormal; - private Material _baseMat; - - private readonly List _movingPlatforms = new List(); - - // ── Color palette ── - private static readonly Color ColFloor = new Color(0.28f, 0.29f, 0.34f, 1f); - private static readonly Color ColBleu = new Color(0.2f, 0.5f, 1f, 0.85f); - private static readonly Color ColBleuLt = new Color(0.3f, 0.65f, 1f, 0.9f); - private static readonly Color ColOrange = new Color(1f, 0.55f, 0.1f, 0.85f); - private static readonly Color ColOrangeLt = new Color(1f, 0.65f, 0.2f, 0.9f); - private static readonly Color ColViolet = new Color(0.6f, 0.2f, 0.8f, 0.8f); - private static readonly Color ColVioletLt = new Color(0.7f, 0.35f, 0.9f, 0.85f); - private static readonly Color ColNormal = new Color(0.42f, 0.43f, 0.50f, 0.9f); - private static readonly Color ColNormalLt = new Color(0.52f, 0.53f, 0.60f, 0.95f); - private static readonly Color ColDark = new Color(0.22f, 0.22f, 0.28f, 0.95f); - private static readonly Color ColGold = new Color(1f, 0.84f, 0f, 0.95f); - private static readonly Color ColWall = new Color(0.35f, 0.35f, 0.42f, 0.95f); - private static readonly Color ColPath = new Color(0.38f, 0.40f, 0.48f, 0.9f); - private static readonly Color ColSignBleu = new Color(0.15f, 0.4f, 0.9f, 0.95f); - private static readonly Color ColSignOrange= new Color(0.9f, 0.45f, 0.05f, 0.95f); - private static readonly Color ColSignViolet= new Color(0.5f, 0.15f, 0.7f, 0.95f); - private static readonly Color ColSignGrey = new Color(0.5f, 0.5f, 0.55f, 0.95f); - private static readonly Color ColSignGold = new Color(0.9f, 0.75f, 0f, 0.95f); - private static readonly Color ColGuide = new Color(0.5f, 0.5f, 0.6f, 0.5f); - - // ── Constants ── - private const float HALF = 40f; // Arena half-size - private const float WALL_H = 12f; // Perimeter wall height - private const float WALL_T = 1f; // Perimeter wall thickness - - void Start() - { - LoadMaterials(); - if (_baseMat == null) FindBaseMaterial(); - - BuildFloorAndWalls(); - BuildPaths(); - BuildZoneA_Movement(); - BuildZoneB_Bounce(); - BuildZoneC_Speed(); - BuildZoneD_MovingPlatforms(); - BuildZoneE_StickyWalls(); - BuildZoneF_Combo(); - BuildZoneG_CentralTower(); - - Debug.Log("[ArenaZoneBuilder] 80x80 tutorial arena built successfully."); - } - - void Update() - { - for (int i = 0; i < _movingPlatforms.Count; i++) - { - var mp = _movingPlatforms[i]; - if (mp.go == null) continue; - mp.t += Time.deltaTime * mp.speed; - float ping = Mathf.PingPong(mp.t, 1f); - mp.go.transform.position = Vector3.Lerp(mp.posA, mp.posB, ping); - } - } - - void OnDestroy() - { - _movingPlatforms.Clear(); - } - - // ═══════════════════════════════════════════ - // FLOOR, WALLS & PATHS - // ═══════════════════════════════════════════ - - private void BuildFloorAndWalls() - { - // Main floor - CreateZone("Arena_Floor", new Vector3(0, -0.25f, 0), - new Vector3(HALF * 2f, 0.5f, HALF * 2f), _matNormal, ColFloor); - - // Perimeter walls (high to prevent escape) - float h2 = WALL_H / 2f; - float full = HALF * 2f + 2f; - CreateZone("Wall_N", new Vector3(0, h2, HALF), new Vector3(full, WALL_H, WALL_T), _matNormal, ColWall); - CreateZone("Wall_S", new Vector3(0, h2, -HALF), new Vector3(full, WALL_H, WALL_T), _matNormal, ColWall); - CreateZone("Wall_E", new Vector3(HALF, h2, 0), new Vector3(WALL_T, WALL_H, full), _matNormal, ColWall); - CreateZone("Wall_W", new Vector3(-HALF, h2, 0), new Vector3(WALL_T, WALL_H, full), _matNormal, ColWall); - } - - /// Ground-level paths connecting all zones, with directional color markers. - private void BuildPaths() - { - float pathH = 0.06f; - float pathY = 0.03f; - - // Spawn -> Zone A (already at spawn) - // Zone A -> Zone B (south to center-south) - CreateZone("Path_A_B", new Vector3(0, pathY, -22f), new Vector3(3f, pathH, 10f), _matNormal, ColPath); - // Zone B -> Zone G center - CreateZone("Path_B_G", new Vector3(0, pathY, -8f), new Vector3(3f, pathH, 10f), _matNormal, ColPath); - // Zone G -> Zone C (center to west) - CreateZone("Path_G_C", new Vector3(-10f, pathY, 0f), new Vector3(12f, pathH, 3f), _matNormal, ColPath); - // Zone C -> Zone D (west to northwest) - CreateZone("Path_C_D", new Vector3(-28f, pathY, 12f), new Vector3(3f, pathH, 16f), _matNormal, ColPath); - // Zone G -> Zone E (center to northeast) - CreateZone("Path_G_E", new Vector3(12f, pathY, 10f), new Vector3(16f, pathH, 3f), _matNormal, ColPath); - // Zone D/E -> Zone F (north) - CreateZone("Path_D_F", new Vector3(-12f, pathY, 28f), new Vector3(16f, pathH, 3f), _matNormal, ColPath); - CreateZone("Path_E_F", new Vector3(12f, pathY, 28f), new Vector3(16f, pathH, 3f), _matNormal, ColPath); - } - - // ═══════════════════════════════════════════ - // ZONE A — MOVEMENT BASICS + JUMP GAPS (South) - // Origin: (0, 0, -32) - // ═══════════════════════════════════════════ - - private void BuildZoneA_Movement() - { - // Sign - CreateSign("Sign_A", new Vector3(-4f, 1.5f, -35f), ColSignGrey); - - // Spawn platform (raised) - CreateZone("A_Spawn", new Vector3(0, 0.4f, -32f), new Vector3(8f, 0.8f, 6f), _matNormal, ColNormalLt); - - // Straight corridor with slight turns to learn rolling - CreateZone("A_Corr1", new Vector3(0, 0.15f, -27f), new Vector3(4f, 0.3f, 5f), _matNormal, ColNormal); - CreateZone("A_Corr2", new Vector3(3f, 0.15f, -23f), new Vector3(4f, 0.3f, 4f), _matNormal, ColNormal); - CreateZone("A_Corr3", new Vector3(0f, 0.15f, -19.5f), new Vector3(4f, 0.3f, 4f), _matNormal, ColNormal); - - // Low guide walls for the corridor - CreateZone("A_GuideL1", new Vector3(-2.5f, 0.6f, -27f), new Vector3(0.3f, 0.9f, 5f), _matNormal, ColGuide); - CreateZone("A_GuideR1", new Vector3(2.5f, 0.6f, -27f), new Vector3(0.3f, 0.9f, 5f), _matNormal, ColGuide); - - // Jump gaps: 3 gaps of increasing difficulty - // Gap 1: 2 units gap - CreateZone("A_Plat1", new Vector3(-3f, 0.15f, -16f), new Vector3(4f, 0.3f, 4f), _matNormal, ColNormalLt); - // (gap of 2 units) - CreateZone("A_Plat2", new Vector3(-3f, 0.15f, -10f), new Vector3(4f, 0.3f, 4f), _matNormal, ColNormalLt); - - // Gap 2: 3 units gap - // (gap of 3 units) - CreateZone("A_Plat3", new Vector3(-3f, 0.15f, -3f), new Vector3(4f, 0.3f, 4f), _matNormal, ColNormalLt); - - // Gap 3: 4.5 units gap (needs charged jump) - // (gap of 4.5 units) - CreateZone("A_Plat4", new Vector3(-3f, 0.15f, 5f), new Vector3(4f, 0.3f, 4f), _matNormal, ColNormalLt); - - // Return ramp to ground - CreateRamp("A_Ramp", new Vector3(-3f, 1.0f, 9f), new Vector3(4f, 0.3f, 5f), - -12f, Vector3.right, _matNormal, ColNormal); - - Debug.Log("[ArenaZoneBuilder] Zone A (Movement) built."); - } - - // ═══════════════════════════════════════════ - // ZONE B — BOUNCE TRAINING (Center-South) - // Origin: (10, 0, -15) - // ═══════════════════════════════════════════ - - private void BuildZoneB_Bounce() - { - Vector3 o = new Vector3(10f, 0f, -15f); - - // Sign - CreateSign("Sign_B", o + new Vector3(-3f, 1.5f, -3f), ColSignBleu); - - // Entry pad - CreateZone("B_Entry", o + new Vector3(0, 0.15f, 0), new Vector3(5f, 0.3f, 5f), _matNormal, ColNormalLt); - - // Bounce pads with targets at increasing heights - // Pad 1 (small) -> target at 3m - CreateZone("B_Pad1", o + new Vector3(0, 0.2f, 5f), new Vector3(3f, 0.25f, 3f), _matGelBleu, ColBleu); - CreateZone("B_Tgt1", o + new Vector3(0, 3f, 9f), new Vector3(4f, 0.4f, 4f), _matNormal, ColNormalLt); - - // Pad 2 (medium) -> target at 4.5m - CreateZone("B_Pad2", o + new Vector3(0, 3.2f, 9f), new Vector3(3.5f, 0.25f, 3.5f), _matGelBleu, ColBleuLt); - CreateZone("B_Tgt2", o + new Vector3(4f, 5.5f, 12f), new Vector3(4f, 0.4f, 4f), _matNormal, ColNormalLt); - - // Pad 3 on target 2 -> top at 8m - CreateZone("B_Pad3", o + new Vector3(4f, 5.7f, 12f), new Vector3(3f, 0.2f, 3f), _matGelBleu, ColBleu); - CreateZone("B_Tgt3", o + new Vector3(0, 8f, 15f), new Vector3(5f, 0.4f, 5f), _matNormal, ColNormalLt); - - // Bounce staircase: fall from 8m -> bounce up further - CreateZone("B_Stair_Bnc", o + new Vector3(-5f, 0.2f, 15f), new Vector3(4f, 0.25f, 4f), _matGelBleu, ColBleu); - CreateZone("B_Stair_Mid", o + new Vector3(-5f, 5f, 19f), new Vector3(4f, 0.4f, 4f), _matNormal, ColNormalLt); - CreateZone("B_Stair_Top", o + new Vector3(-5f, 8.5f, 23f), new Vector3(5f, 0.4f, 4f), _matNormal, ColNormalLt); - - // Return to ground via gentle ramp - CreateRamp("B_Return", o + new Vector3(-5f, 4f, 27f), new Vector3(4f, 0.3f, 8f), - -20f, Vector3.right, _matNormal, ColNormal); - - Debug.Log("[ArenaZoneBuilder] Zone B (Bounce) built."); - } - - // ═══════════════════════════════════════════ - // ZONE C — SPEED TRAINING (West) - // Origin: (-25, 0, -5) - // ═══════════════════════════════════════════ - - private void BuildZoneC_Speed() - { - Vector3 o = new Vector3(-25f, 0f, -5f); - - // Sign - CreateSign("Sign_C", o + new Vector3(8f, 1.5f, -2f), ColSignOrange); - - // Entry - CreateZone("C_Entry", o + new Vector3(0, 0.15f, 0), new Vector3(5f, 0.3f, 5f), _matNormal, ColNormalLt); - - // Speed strip 1 -> short, straight, with ramp launch - CreateZone("C_Strip1", o + new Vector3(0, 0.12f, 5f), new Vector3(4f, 0.15f, 8f), _matGelOrange, ColOrange); - // Guide walls - CreateZone("C_GuideL1", new Vector3(o.x - 2.5f, 0.5f, o.z + 5f), new Vector3(0.3f, 1f, 8f), _matNormal, ColGuide); - CreateZone("C_GuideR1", new Vector3(o.x + 2.5f, 0.5f, o.z + 5f), new Vector3(0.3f, 1f, 8f), _matNormal, ColGuide); - - // Ramp at end of strip 1 - CreateRamp("C_Ramp1", o + new Vector3(0, 0.8f, 10.5f), new Vector3(4f, 0.25f, 4f), - 20f, Vector3.right, _matGelOrange, ColOrangeLt); - - // Landing platform (12m ahead, 2m up) - CreateZone("C_Land1", o + new Vector3(0, 2f, 18f), new Vector3(5f, 0.4f, 5f), _matNormal, ColNormalLt); - - // Speed strip 2 -> longer with a curve - CreateZone("C_Strip2a", o + new Vector3(0, 2.1f, 22f), new Vector3(4f, 0.15f, 5f), _matGelOrange, ColOrange); - CreateZone("C_Strip2b", o + new Vector3(4f, 2.1f, 26f), new Vector3(5f, 0.15f, 4f), _matGelOrange, ColOrangeLt); - CreateZone("C_Strip2c", o + new Vector3(8f, 2.1f, 30f), new Vector3(4f, 0.15f, 5f), _matGelOrange, ColOrange); - // Guide walls for curve - CreateZone("C_GuideO", o + new Vector3(-2.5f, 2.6f, 22f), new Vector3(0.3f, 1f, 5f), _matNormal, ColGuide); - CreateZone("C_GuideI", o + new Vector3(10.5f, 2.6f, 30f), new Vector3(0.3f, 1f, 5f), _matNormal, ColGuide); - - // Final landing with another ramp - CreateRamp("C_Ramp2", o + new Vector3(8f, 3f, 34f), new Vector3(4f, 0.25f, 4f), - 25f, Vector3.right, _matGelOrange, ColOrangeLt); - CreateZone("C_Land2", o + new Vector3(8f, 4.5f, 38f), new Vector3(6f, 0.4f, 5f), _matNormal, ColNormalLt); - - Debug.Log("[ArenaZoneBuilder] Zone C (Speed) built."); - } - - // ═══════════════════════════════════════════ - // ZONE D — MOVING PLATFORMS (Northwest) - // Origin: (-28, 0, 18) - // ═══════════════════════════════════════════ - - private void BuildZoneD_MovingPlatforms() - { - Vector3 o = new Vector3(-28f, 0f, 18f); - - // Sign - CreateSign("Sign_D", o + new Vector3(4f, 1.5f, -2f), ColSignGrey); - - // Entry platform - CreateZone("D_Entry", o + new Vector3(0, 0.2f, 0), new Vector3(6f, 0.4f, 6f), _matNormal, ColNormalLt); - - // 3 horizontal sliders (oscillate along X, spaced along Z, rising) - for (int i = 0; i < 3; i++) - { - Vector3 a = o + new Vector3(-2f, 1.5f + i * 2f, 5f + i * 6f); - Vector3 b = a + new Vector3(8f, 0f, 0f); - var go = CreateZone("D_Slide" + i, a, new Vector3(4f, 0.5f, 4f), _matNormal, ColDark); - AddMovingPlatform(go, a, b, 0.3f + i * 0.1f); - } - - // Mid-platform - CreateZone("D_Mid", o + new Vector3(2f, 7f, 20f), new Vector3(5f, 0.4f, 5f), _matNormal, ColNormalLt); - - // 2 vertical lifts - for (int i = 0; i < 2; i++) - { - Vector3 a = o + new Vector3(-4f + i * 8f, 7.5f, 24f + i * 5f); - Vector3 b = a + new Vector3(0f, 5f, 0f); - var go = CreateZone("D_Lift" + i, a, new Vector3(4f, 0.5f, 4f), _matNormal, ColDark); - AddMovingPlatform(go, a, b, 0.22f + i * 0.1f); - } - - // End platform (high, with view) - CreateZone("D_End", o + new Vector3(2f, 12f, 32f), new Vector3(6f, 0.4f, 6f), _matNormal, ColNormalLt); - - // Bouncy descent pad at ground level - CreateZone("D_Desc", o + new Vector3(2f, 0.2f, 32f), new Vector3(4f, 0.25f, 4f), _matGelBleu, ColBleu); - - Debug.Log("[ArenaZoneBuilder] Zone D (Moving Platforms) built."); - } - - // ═══════════════════════════════════════════ - // ZONE E — STICKY WALLS (Northeast) - // Origin: (24, 0, 15) - // ═══════════════════════════════════════════ - - private void BuildZoneE_StickyWalls() - { - Vector3 o = new Vector3(24f, 0f, 15f); - - // Sign - CreateSign("Sign_E", o + new Vector3(-5f, 1.5f, -2f), ColSignViolet); - - // Entry pad - CreateZone("E_Entry", o + new Vector3(0, 0.15f, 0), new Vector3(5f, 0.3f, 5f), _matNormal, ColNormalLt); - - // === Intro wall: simple vertical climb (4m high) === - // Wall face pointing -X (ball approaches from the left) - CreateZone("E_Wall1", o + new Vector3(3f, 3f, 3f), new Vector3(0.5f, 6f, 5f), _matGelViolet, ColViolet); - // Platform at top of wall - CreateZone("E_Top1", o + new Vector3(3f, 6.2f, 3f), new Vector3(4f, 0.4f, 5f), _matNormal, ColNormalLt); - - // === L-shaped sticky: wall -> turn -> wall === - // Vertical wall going up (face -Z) - CreateZone("E_Wall2a", o + new Vector3(0, 3.5f, 8f), new Vector3(4f, 7f, 0.5f), _matGelViolet, ColViolet); - // Ceiling connecting to second wall (face down, -Y) - CreateZone("E_Ceil", o + new Vector3(0, 7.1f, 10f), new Vector3(4f, 0.5f, 4f), _matGelViolet, ColVioletLt); - // Second wall going down (face +Z) - CreateZone("E_Wall2b", o + new Vector3(0, 3.5f, 12f), new Vector3(4f, 7f, 0.5f), _matGelViolet, ColViolet); - // Landing after L traverse - CreateZone("E_Land2", o + new Vector3(0, 0.2f, 14f), new Vector3(5f, 0.4f, 4f), _matNormal, ColNormalLt); - - // === Vertical tunnel: two sticky walls face-to-face, zigzag up === - // Left wall - CreateZone("E_Tun_L", o + new Vector3(-3.5f, 6f, 18f), new Vector3(0.5f, 12f, 4f), _matGelViolet, ColViolet); - // Right wall - CreateZone("E_Tun_R", o + new Vector3(3.5f, 6f, 18f), new Vector3(0.5f, 12f, 4f), _matGelViolet, ColVioletLt); - // Small ledges alternating sides to help zigzag - for (int i = 0; i < 4; i++) - { - float side = (i % 2 == 0) ? -2.5f : 2.5f; - float h = 2f + i * 2.8f; - CreateZone("E_Tun_Ledge" + i, o + new Vector3(side, h, 18f), - new Vector3(2f, 0.3f, 3f), _matNormal, ColNormalLt); - } - // Top of tunnel - CreateZone("E_TunTop", o + new Vector3(0, 12.2f, 18f), new Vector3(5f, 0.4f, 5f), _matNormal, ColNormalLt); - - Debug.Log("[ArenaZoneBuilder] Zone E (Sticky Walls) built."); - } - - // ═══════════════════════════════════════════ - // ZONE F — COMBO CHALLENGE (North) - // Origin: (0, 0, 30) - // ═══════════════════════════════════════════ - - private void BuildZoneF_Combo() - { - Vector3 o = new Vector3(0f, 0f, 30f); - - // Sign - CreateSign("Sign_F", o + new Vector3(-5f, 1.5f, -4f), ColSignGold); - - // Entry - CreateZone("F_Entry", o + new Vector3(0, 0.15f, 0), new Vector3(5f, 0.3f, 5f), _matNormal, ColNormalLt); - - // Step 1: Speed strip launch - CreateZone("F_Speed", o + new Vector3(0, 0.12f, 4f), new Vector3(3f, 0.15f, 6f), _matGelOrange, ColOrange); - CreateZone("F_GuidL", o + new Vector3(-2f, 0.5f, 4f), new Vector3(0.3f, 0.7f, 6f), _matNormal, ColGuide); - CreateZone("F_GuidR", o + new Vector3(2f, 0.5f, 4f), new Vector3(0.3f, 0.7f, 6f), _matNormal, ColGuide); - - // Step 2: Ramp -> bounce pad - CreateRamp("F_Ramp", o + new Vector3(0, 0.6f, 8.5f), new Vector3(3f, 0.25f, 3f), - 22f, Vector3.right, _matGelOrange, ColOrangeLt); - CreateZone("F_BncPad", o + new Vector3(0, 0.2f, 13f), new Vector3(4f, 0.25f, 4f), _matGelBleu, ColBleu); - - // Step 3: High platform with sticky wall leading higher - CreateZone("F_MidPlat", o + new Vector3(0, 4.5f, 17f), new Vector3(5f, 0.4f, 4f), _matNormal, ColNormalLt); - CreateZone("F_StickyWall", o + new Vector3(-3f, 7f, 17f), new Vector3(0.5f, 5f, 4f), _matGelViolet, ColViolet); - - // Step 4: Moving platform to final - Vector3 mpA = o + new Vector3(0, 9.5f, 17f); - Vector3 mpB = o + new Vector3(0, 9.5f, 23f); - var mp = CreateZone("F_MovPlat", mpA, new Vector3(4f, 0.5f, 4f), _matNormal, ColDark); - AddMovingPlatform(mp, mpA, mpB, 0.25f); - - // Step 5: Gold finish platform - CreateZone("F_Finish", o + new Vector3(0, 10f, 27f), new Vector3(6f, 0.5f, 5f), _matNormal, ColGold); - CreateZone("F_FinBnc", o + new Vector3(0, 10.3f, 27f), new Vector3(3f, 0.2f, 3f), _matGelBleu, ColBleuLt); - - Debug.Log("[ArenaZoneBuilder] Zone F (Combo) built."); - } - - // ═══════════════════════════════════════════ - // ZONE G — CENTRAL TOWER (Center) - // Origin: (0, 0, 5) - // ═══════════════════════════════════════════ - - private void BuildZoneG_CentralTower() - { - Vector3 o = new Vector3(0f, 0f, 5f); - - // Base (accessible from ground) - CreateZone("G_Base", o + new Vector3(0, 0.25f, 0), new Vector3(10f, 0.5f, 10f), _matNormal, ColNormal); - - // Access ramp from south - CreateRamp("G_Ramp", o + new Vector3(0, 0.8f, -6f), new Vector3(4f, 0.3f, 5f), - -10f, Vector3.right, _matNormal, ColNormal); - - // Level 1: Bounce pad -> L2 - CreateZone("G_L1_Bnc", o + new Vector3(0, 0.5f, 0), new Vector3(3.5f, 0.25f, 3.5f), _matGelBleu, ColBleu); - - // Level 2 (3.5m): platform + speed strip - CreateZone("G_L2", o + new Vector3(0, 3.5f, 0), new Vector3(8f, 0.4f, 8f), _matNormal, ColNormalLt); - CreateZone("G_L2_Spd", o + new Vector3(0, 3.7f, 0), new Vector3(6f, 0.15f, 2f), _matGelOrange, ColOrange); - CreateRamp("G_L2_Ramp", o + new Vector3(3f, 4.5f, 3f), new Vector3(3f, 0.25f, 3f), - 25f, Vector3.right, _matGelOrange, ColOrangeLt); - - // Level 3 (7m): platform + moving platform to L4 - CreateZone("G_L3", o + new Vector3(0, 7f, 0), new Vector3(7f, 0.4f, 7f), _matNormal, ColNormalLt); - CreateZone("G_L3_Bnc", o + new Vector3(2f, 7.25f, 2f), new Vector3(3f, 0.2f, 3f), _matGelBleu, ColBleuLt); - - // Moving platform L3->L4 - Vector3 mp3a = o + new Vector3(5f, 8f, 0); - Vector3 mp3b = o + new Vector3(0, 8f, 5f); - var m3 = CreateZone("G_MP3", mp3a, new Vector3(3.5f, 0.5f, 3.5f), _matNormal, ColDark); - AddMovingPlatform(m3, mp3a, mp3b, 0.2f); - - // Level 4 (10.5m): platform + sticky wall to L5 - CreateZone("G_L4", o + new Vector3(0, 10.5f, 0), new Vector3(7f, 0.4f, 7f), _matNormal, ColNormalLt); - CreateZone("G_L4_Sticky", o + new Vector3(-3.8f, 13f, 0), new Vector3(0.5f, 5f, 5f), _matGelViolet, ColViolet); - - // Level 5 — SUMMIT (15.5m): gold platform - CreateZone("G_L5", o + new Vector3(0, 15.5f, 0), new Vector3(8f, 0.5f, 8f), _matNormal, ColGold); - CreateZone("G_L5_Bnc", o + new Vector3(0, 15.85f, 0), new Vector3(4f, 0.25f, 4f), _matGelBleu, ColBleuLt); - - // Orbital moving platform for alternative L3->L4 access - Vector3 orb_a = o + new Vector3(-6f, 9f, 0); - Vector3 orb_b = o + new Vector3(0, 9f, -6f); - var orb = CreateZone("G_Orb", orb_a, new Vector3(3.5f, 0.5f, 3.5f), _matNormal, ColDark); - AddMovingPlatform(orb, orb_a, orb_b, 0.18f); - - Debug.Log("[ArenaZoneBuilder] Zone G (Central Tower) built."); - } - - // ═══════════════════════════════════════════ - // SIGN HELPER (zone entrance markers) - // ═══════════════════════════════════════════ - - private void CreateSign(string name, Vector3 position, Color color) - { - // Tall thin panel as zone marker - CreateZone(name, position, new Vector3(0.3f, 2.5f, 0.3f), _matNormal, color); - // Colored cap on top - CreateZone(name + "_Cap", position + new Vector3(0, 1.45f, 0), new Vector3(0.8f, 0.4f, 0.8f), _matNormal, color); - } - - // ═══════════════════════════════════════════ - // MATERIAL LOADING - // ═══════════════════════════════════════════ - - private void LoadMaterials() - { - _matGelBleu = Resources.Load("GelBleu"); - _matGelOrange = Resources.Load("GelOrange"); - _matGelViolet = Resources.Load("GelViolet"); - _matBouncy = Resources.Load("Bouncy"); - _matNormal = Resources.Load("Normal"); - - if (_matGelBleu == null) Debug.LogWarning("[ArenaZoneBuilder] GelBleu not found in Resources!"); - if (_matGelOrange == null) Debug.LogWarning("[ArenaZoneBuilder] GelOrange not found in Resources!"); - if (_matGelViolet == null) Debug.LogWarning("[ArenaZoneBuilder] GelViolet not found in Resources!"); - if (_matBouncy == null) Debug.LogWarning("[ArenaZoneBuilder] Bouncy not found in Resources!"); - if (_matNormal == null) Debug.LogWarning("[ArenaZoneBuilder] Normal not found in Resources!"); - } - - private void FindBaseMaterial() - { - var shader = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"); - _baseMat = new Material(shader); - } - - // ═══════════════════════════════════════════ - // ZONE HELPERS - // ═══════════════════════════════════════════ - - private GameObject CreateZone(string name, Vector3 position, Vector3 size, - PhysicsMaterial physMat, Color color) - { - var go = GameObject.CreatePrimitive(PrimitiveType.Cube); - go.name = name; - go.transform.position = position; - go.transform.localScale = size; - go.isStatic = true; - - var col = go.GetComponent(); - if (col != null && physMat != null) col.material = physMat; - - var rend = go.GetComponent(); - if (rend != null) - { - var mat = new Material(_baseMat); - SetMatColor(mat, color); - if (color.a < 1f) SetMatTransparent(mat, color); - rend.material = mat; - } - return go; - } - - private GameObject CreateRamp(string name, Vector3 position, Vector3 size, - float angle, Vector3 axis, PhysicsMaterial physMat, Color color) - { - var go = CreateZone(name, position, size, physMat, color); - go.transform.rotation = Quaternion.AngleAxis(angle, axis); - return go; - } - - private void AddMovingPlatform(GameObject go, Vector3 posA, Vector3 posB, float speed) - { - go.isStatic = false; - _movingPlatforms.Add(new MovingPlatform { go = go, posA = posA, posB = posB, speed = speed }); - } - - // ═══════════════════════════════════════════ - // MATERIAL HELPERS - // ═══════════════════════════════════════════ - - private static void SetMatColor(Material mat, Color color) - { - if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", color); - if (mat.HasProperty("_Color")) mat.color = color; - } - - private static void SetMatTransparent(Material mat, Color color) - { - if (mat.HasProperty("_Surface")) - { - mat.SetFloat("_Surface", 1); - mat.SetFloat("_Blend", 0); - } - mat.EnableKeyword("_SURFACE_TYPE_TRANSPARENT"); - mat.DisableKeyword("_SURFACE_TYPE_OPAQUE"); - mat.EnableKeyword("_ALPHAPREMULTIPLY_ON"); - mat.renderQueue = 3000; - if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", color); - if (mat.HasProperty("_Color")) mat.color = color; - } - - // ═══════════════════════════════════════════ - // MOVING PLATFORM DATA - // ═══════════════════════════════════════════ - - private class MovingPlatform - { - public GameObject go; - public Vector3 posA; - public Vector3 posB; - public float speed; - public float t; - } -} - diff --git a/game/Assets/Scripts/ArenaZoneBuilder.cs.meta b/game/Assets/Scripts/ArenaZoneBuilder.cs.meta deleted file mode 100644 index f3b1709..0000000 --- a/game/Assets/Scripts/ArenaZoneBuilder.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 31c6f8ef706b51448b461b2b027e2ea8 \ No newline at end of file diff --git a/game/Assets/Scripts/GameManager.cs b/game/Assets/Scripts/GameManager.cs index 61449ef..a1bc5ae 100644 --- a/game/Assets/Scripts/GameManager.cs +++ b/game/Assets/Scripts/GameManager.cs @@ -27,32 +27,32 @@ public class GameManager : MonoBehaviour DontDestroyOnLoad(gameObject); } - void OnEnable() + void Start() { var nm = NetworkManager.Instance; if (nm == null) return; - nm.OnPhaseChanged += HandlePhaseChanged; + nm.OnPhaseChanged += HandlePhaseChanged; nm.OnCountdownChanged += HandleCountdownChanged; - nm.OnEliminated += HandleEliminated; - nm.OnQualified += HandleQualified; - nm.OnRoundStart += HandleRoundStart; - nm.OnRoundEnd += HandleRoundEnd; - nm.OnGameEnd += HandleGameEnd; - nm.OnDisconnected += HandleDisconnected; + nm.OnEliminated += HandleEliminated; + nm.OnQualified += HandleQualified; + nm.OnRoundStart += HandleRoundStart; + nm.OnRoundEnd += HandleRoundEnd; + nm.OnGameEnd += HandleGameEnd; + nm.OnDisconnected += HandleDisconnected; } - void OnDisable() + void OnDestroy() { var nm = NetworkManager.Instance; if (nm == null) return; - nm.OnPhaseChanged -= HandlePhaseChanged; + nm.OnPhaseChanged -= HandlePhaseChanged; nm.OnCountdownChanged -= HandleCountdownChanged; - nm.OnEliminated -= HandleEliminated; - nm.OnQualified -= HandleQualified; - nm.OnRoundStart -= HandleRoundStart; - nm.OnRoundEnd -= HandleRoundEnd; - nm.OnGameEnd -= HandleGameEnd; - nm.OnDisconnected -= HandleDisconnected; + nm.OnEliminated -= HandleEliminated; + nm.OnQualified -= HandleQualified; + nm.OnRoundStart -= HandleRoundStart; + nm.OnRoundEnd -= HandleRoundEnd; + nm.OnGameEnd -= HandleGameEnd; + nm.OnDisconnected -= HandleDisconnected; } // ─── Event Handlers ─────────────────────────────────────────────────── @@ -106,11 +106,12 @@ public class GameManager : MonoBehaviour } } - void HandleRoundStart(int round, string mode) + void HandleRoundStart(int round, string mode, int totalRounds) { CurrentRound = round; CurrentMode = mode; gameHUD?.SetRoundInfo(round, mode); + gameHUD?.SetTotalRounds(totalRounds); IsLocalEliminated = false; } @@ -140,7 +141,7 @@ public class GameManager : MonoBehaviour switch (phase) { case GamePhase.Lobby: - SetPlayerActive(false); + SetPlayerActive(NetworkManager.Instance?.IsConnected ?? false); SetSpectatorActive(false); gameHUD?.SetPhase("lobby"); break; diff --git a/game/Assets/Scripts/Network/LobbyUI.cs b/game/Assets/Scripts/Network/LobbyUI.cs index 549be34..197e3be 100644 --- a/game/Assets/Scripts/Network/LobbyUI.cs +++ b/game/Assets/Scripts/Network/LobbyUI.cs @@ -88,6 +88,7 @@ public class LobbyUI : MonoBehaviour _lobbyActive = false; _isConnecting = false; _statusMessage = ""; + CancelInvoke(nameof(CheckConnectionTimeout)); // --- Activate the player hierarchy --- if (playerRoot != null) @@ -113,6 +114,7 @@ public class LobbyUI : MonoBehaviour rb.position = spawnPos; } pc.transform.position = spawnPos; + pc.SetSpawnPosition(spawnPos); Debug.Log($"[Lobby] Player teleported to spawn: {spawnPos}"); } pc.enabled = true; diff --git a/game/Assets/Scripts/Network/NetworkManager.cs b/game/Assets/Scripts/Network/NetworkManager.cs index fcbc6a4..ab583bf 100644 --- a/game/Assets/Scripts/Network/NetworkManager.cs +++ b/game/Assets/Scripts/Network/NetworkManager.cs @@ -44,10 +44,9 @@ public class NetworkManager : MonoBehaviour public event Action OnCountdownChanged; // seconds remaining public event Action OnEliminated; // sessionId, reason public event Action OnQualified; // sessionId - public event Action OnRoundStart; // roundNumber, mode + public event Action OnRoundStart; // roundNumber, mode, totalRounds public event Action OnRoundEnd; // roundNumber public event Action OnGameEnd; // winnerName - public event Action OnDeathZoneYChanged; // for survival mode // --- Internals --- private Client _client; @@ -145,7 +144,6 @@ public class NetworkManager : MonoBehaviour // Game state changes _callbacks.Listen(state => state.phase, (newValue, prevValue) => _OnPhaseChanged(newValue)); _callbacks.Listen(state => state.countdown, (newValue, prevValue) => OnCountdownChanged?.Invoke(newValue)); - _callbacks.Listen(state => state.deathZoneY, (newValue, prevValue) => OnDeathZoneYChanged?.Invoke(newValue)); // Server messages _room.OnMessage("eliminated", msg => @@ -161,7 +159,7 @@ public class NetworkManager : MonoBehaviour _room.OnMessage("roundStart", msg => { Debug.Log($"[Network] Round {msg.round} started ({msg.mode})"); - OnRoundStart?.Invoke(msg.round, msg.mode); + OnRoundStart?.Invoke(msg.round, msg.mode, msg.totalRounds); }); _room.OnMessage("roundEnd", msg => { @@ -208,18 +206,6 @@ public class NetworkManager : MonoBehaviour await _room.Send("checkpointReached", new { index }); } - public async void SendDeathZoneHit() - { - if (_room != null && IsConnected) - await _room.Send("deathZoneHit", null); - } - - public async void SendInZone(bool inZone) - { - if (_room != null && IsConnected) - await _room.Send("inZone", new { inZone }); - } - // ─── State Callbacks ───────────────────────────────────────────────── private void _OnPhaseChanged(string phase) @@ -285,11 +271,6 @@ public class NetworkManager : MonoBehaviour new Vector3(player.avx, player.avy, player.avz) ); - // Sync team color changes (for teams mode) - controller.UpdateTeamColor(player.team, - new Color(player.colorR, player.colorG, player.colorB)); - - // Hide/show eliminated remote players controller.SetVisible(!player.isEliminated); } } diff --git a/game/Assets/Scripts/Network/NetworkSchema.cs b/game/Assets/Scripts/Network/NetworkSchema.cs index bdf5914..dce0157 100644 --- a/game/Assets/Scripts/Network/NetworkSchema.cs +++ b/game/Assets/Scripts/Network/NetworkSchema.cs @@ -25,8 +25,7 @@ public partial class NetworkPlayer : Schema [Type(18, "boolean")] public bool isEliminated = false; [Type(19, "boolean")] public bool isQualified = false; [Type(20, "boolean")] public bool isReady = false; - [Type(21, "int8")] public int team = 0; - [Type(22, "int8")] public int checkpointIndex = 0; + [Type(21, "int8")] public int checkpointIndex = 0; } public partial class NetworkState : Schema @@ -40,8 +39,5 @@ public partial class NetworkState : Schema [Type(4, "int8")] public int totalRounds = 4; [Type(5, "int8")] public int playersAlive = 0; [Type(6, "string")] public string gameMode = "race"; - [Type(7, "float32")] public float deathZoneY = -100; - [Type(8, "int16")] public int teamScoreRed = 0; - [Type(9, "int16")] public int teamScoreBlue = 0; - [Type(10, "string")] public string winnerName = ""; + [Type(7, "string")] public string winnerName = ""; } diff --git a/game/Assets/Scripts/Race/CheckpointSystem.cs b/game/Assets/Scripts/Race/CheckpointSystem.cs index 8f6d27b..e3649cb 100644 --- a/game/Assets/Scripts/Race/CheckpointSystem.cs +++ b/game/Assets/Scripts/Race/CheckpointSystem.cs @@ -80,11 +80,10 @@ public class CheckpointSystem : MonoBehaviour { _finished = true; Debug.Log("[Checkpoint] FINISH LINE reached!"); - StartCoroutine(FinishFlash()); } else { - StartCoroutine(FlashCheckpoint(index)); + StartCoroutine(FlashCheckpoint(_localCheckpointIndex)); } } @@ -146,17 +145,6 @@ public class CheckpointSystem : MonoBehaviour } } - private IEnumerator FinishFlash() - { - float t = 0f; - while (t < 2f) - { - t += Time.deltaTime; - yield return null; - } - // Finish confirmed via network — GameManager/EliminationOverlay handles the "Qualifié!" overlay - } - private static void SetRendererColor(Renderer rend, Color c) { if (rend.material.HasProperty("_BaseColor")) rend.material.SetColor("_BaseColor", c); diff --git a/game/Assets/Scripts/Survival/DeathZone.cs b/game/Assets/Scripts/Survival/DeathZone.cs deleted file mode 100644 index 05ebb7f..0000000 --- a/game/Assets/Scripts/Survival/DeathZone.cs +++ /dev/null @@ -1,93 +0,0 @@ -using UnityEngine; - -/// -/// Survival mode: a rising death zone that climbs from below. -/// The server is authoritative on the Y position (broadcast via NetworkState.deathZoneY). -/// This component moves the visual/collider locally, and detects local player contact. -/// -/// Setup: Create a large Plane or Cube GameObject, attach this script, -/// add a Box/Mesh Collider set to "Is Trigger". -/// The object will be positioned at deathZoneY each frame. -/// -public class DeathZone : MonoBehaviour -{ - [Header("Visual")] - [Tooltip("Half-size of the death zone plane (X and Z)")] - public float halfExtent = 200f; - - [Tooltip("Thickness of the zone collider")] - public float thickness = 2f; - - [Header("Warning")] - [Tooltip("Distance above death zone where the red tint starts")] - public float warningDistance = 8f; - - private bool _hitSent = false; - private float _targetY = -100f; - - void Start() - { - // Scale the collider to cover the arena - transform.localScale = new Vector3(halfExtent * 2f, thickness, halfExtent * 2f); - - // Subscribe to death zone Y changes from server - if (NetworkManager.Instance != null) - { - NetworkManager.Instance.OnDeathZoneYChanged += OnDeathZoneYChanged; - NetworkManager.Instance.OnPhaseChanged += OnPhaseChanged; - } - } - - void OnDestroy() - { - if (NetworkManager.Instance != null) - { - NetworkManager.Instance.OnDeathZoneYChanged -= OnDeathZoneYChanged; - NetworkManager.Instance.OnPhaseChanged -= OnPhaseChanged; - } - } - - void OnDeathZoneYChanged(float y) - { - _targetY = y; - } - - void OnPhaseChanged(string phase) - { - if (phase == "playing" || phase == "lobby") - { - _hitSent = false; // reset for new round - } - } - - void Update() - { - // Smooth follow of the server Y value - Vector3 pos = transform.position; - pos.y = Mathf.Lerp(pos.y, _targetY, Time.deltaTime * 3f); - transform.position = pos; - - // Update warning tint based on local player proximity - var nm = NetworkManager.Instance; - if (nm != null && nm.IsConnected && GameHUD.Instance != null) - { - var localState = nm.GetLocalPlayerState(); - if (localState != null) - { - float dist = localState.y - pos.y; - float intensity = Mathf.Clamp01(1f - dist / warningDistance); - GameHUD.Instance.SetDeathZoneWarning(intensity); - } - } - } - - void OnTriggerEnter(Collider other) - { - if (_hitSent) return; - if (other.GetComponent() == null) return; - - _hitSent = true; - Debug.Log("[DeathZone] Local player hit the death zone!"); - NetworkManager.Instance?.SendDeathZoneHit(); - } -} diff --git a/game/Assets/Scripts/Survival/DeathZone.cs.meta b/game/Assets/Scripts/Survival/DeathZone.cs.meta deleted file mode 100644 index 2a01d26..0000000 --- a/game/Assets/Scripts/Survival/DeathZone.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 99fdfaa3e87a64d4e958f81014e6cdab \ No newline at end of file diff --git a/game/Assets/Scripts/Teams/ZoneCapture.cs b/game/Assets/Scripts/Teams/ZoneCapture.cs deleted file mode 100644 index bbb2a76..0000000 --- a/game/Assets/Scripts/Teams/ZoneCapture.cs +++ /dev/null @@ -1,125 +0,0 @@ -using UnityEngine; - -/// -/// Teams mode: a central capture zone. The local player sends "inZone" messages -/// to the server while inside the zone. The server tallies scores. -/// Visualizes zone control by tinting the zone's renderer. -/// -/// Setup: Create a flat Box/Plane in the center of the arena. -/// Add a BoxCollider set to "Is Trigger". Attach this script. -/// -public class ZoneCapture : MonoBehaviour -{ - public static ZoneCapture Instance { get; private set; } - - [Header("Visual")] - [Tooltip("Neutral zone color")] - public Color neutralColor = new Color(0.5f, 0.5f, 0.6f, 0.5f); - [Tooltip("Red team controls color")] - public Color redColor = new Color(1f, 0.2f, 0.2f, 0.6f); - [Tooltip("Blue team controls color")] - public Color blueColor = new Color(0.2f, 0.5f, 1f, 0.6f); - - [Header("Score Reporting")] - [Tooltip("How often (seconds) to send inZone=true to server while inside")] - public float reportInterval = 0.5f; - - private bool _isLocalPlayerInZone = false; - private float _reportTimer = 0f; - private Renderer _renderer; - - // Zone occupant counts (received via server state — approximated by remote player positions) - private int _redInZone = 0; - private int _blueInZone = 0; - - void Awake() - { - Instance = this; - _renderer = GetComponent(); - SetZoneColor(neutralColor); - } - - void Start() - { - if (NetworkManager.Instance != null) - { - NetworkManager.Instance.OnPhaseChanged += OnPhaseChanged; - } - } - - void OnDestroy() - { - if (NetworkManager.Instance != null) - NetworkManager.Instance.OnPhaseChanged -= OnPhaseChanged; - } - - void OnPhaseChanged(string phase) - { - if (phase == "lobby" || phase == "roundEnd") - { - _isLocalPlayerInZone = false; - _redInZone = 0; - _blueInZone = 0; - SetZoneColor(neutralColor); - } - } - - void Update() - { - if (!_isLocalPlayerInZone) return; - - _reportTimer += Time.deltaTime; - if (_reportTimer >= reportInterval) - { - _reportTimer = 0f; - NetworkManager.Instance?.SendInZone(true); - } - - // Update zone tint based on dominance - UpdateZoneColor(); - } - - void OnTriggerEnter(Collider other) - { - if (other.GetComponent() != null) - { - _isLocalPlayerInZone = true; - _reportTimer = 0f; - NetworkManager.Instance?.SendInZone(true); - } - - // Count remote players in zone - var remote = other.GetComponent(); - if (remote != null) - { - // team info would need to be tracked — skip for now, server handles scoring - } - } - - void OnTriggerExit(Collider other) - { - if (other.GetComponent() != null) - { - _isLocalPlayerInZone = false; - NetworkManager.Instance?.SendInZone(false); - } - } - - private void UpdateZoneColor() - { - // Read team scores from NetworkManager state for visual feedback - // For now use a pulsing neutral tint when local player is inside - float pulse = 0.7f + 0.3f * Mathf.Sin(Time.time * 3f); - Color c = neutralColor; - c.a = pulse * 0.6f; - SetZoneColor(c); - } - - public void SetZoneColor(Color c) - { - if (_renderer == null) return; - var mat = _renderer.material; - if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", c); - else mat.color = c; - } -} diff --git a/game/Assets/Scripts/Teams/ZoneCapture.cs.meta b/game/Assets/Scripts/Teams/ZoneCapture.cs.meta deleted file mode 100644 index f813494..0000000 --- a/game/Assets/Scripts/Teams/ZoneCapture.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 3e7c98b369c3ccf4aac0ad3ad2bcbbff \ No newline at end of file diff --git a/game/Assets/Scripts/UI/GameHUD.cs b/game/Assets/Scripts/UI/GameHUD.cs index 6267e54..cce9bd0 100644 --- a/game/Assets/Scripts/UI/GameHUD.cs +++ b/game/Assets/Scripts/UI/GameHUD.cs @@ -91,6 +91,7 @@ public class GameHUD : MonoBehaviour 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 SetTotalRounds(int n) => _totalRounds = n; public void SetCheckpoint(int current, int total) { _checkpointsCurrent = current; _checkpointsTotal = total; } public void SetLocalRaceActive(bool active) @@ -168,15 +169,6 @@ public class GameHUD : MonoBehaviour 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); @@ -239,44 +231,6 @@ public class GameHUD : MonoBehaviour $"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 @@ -285,32 +239,7 @@ public class GameHUD : MonoBehaviour // 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() diff --git a/rolld_backend/game/src/rooms/ArenaRoom.js b/rolld_backend/game/src/rooms/ArenaRoom.js index ce5eb7d..aac5de4 100644 --- a/rolld_backend/game/src/rooms/ArenaRoom.js +++ b/rolld_backend/game/src/rooms/ArenaRoom.js @@ -1,16 +1,11 @@ const { Room } = require("@colyseus/core"); const { GameState, Player } = require("../schema/GameState"); -const ROUND_MODES = ["race", "survival", "teams"]; -const LOBBY_TIMEOUT = 30; // seconds before auto-start +const LOBBY_TIMEOUT = 30; const COUNTDOWN_DURATION = 3; const ROUND_END_DURATION = 5; -const RACE_TIMEOUT = 180; // 3 min -const SURVIVAL_START_DELAY = 20; // seconds before deathzone rises -const SURVIVAL_RISE_RATE = 0.3; // units/sec -const SURVIVAL_MAX_Y = 15; -const TEAMS_DURATION = 90; -const QUALIFY_RATIO = 0.6; // top 60% qualify in race +const RACE_TIMEOUT = 180; +const QUALIFY_RATIO = 0.6; class ArenaRoom extends Room { maxClients = 20; @@ -20,10 +15,7 @@ class ArenaRoom extends Room { this.setPatchRate(16); // ~62.5 Hz this._phaseTimer = null; - this._survivalInterval = null; - this._teamInterval = null; this._lobbyTimer = null; - this._inZonePlayers = new Set(); // sessionIds currently in zone console.log(`[ArenaRoom] Room ${this.roomId} created`); @@ -55,35 +47,17 @@ class ArenaRoom extends Room { }); this.onMessage("checkpointReached", (client, data) => { - if (this.state.phase !== "playing" || this.state.gameMode !== "race") return; + if (this.state.phase !== "playing") return; const player = this.state.players.get(client.sessionId); if (!player || player.isEliminated || player.isQualified) return; const expected = player.checkpointIndex; - if (data.index !== expected) return; // must hit in order + if (data.index !== expected) return; player.checkpointIndex = data.index + 1; - // The last checkpoint (index 4 = finish) qualifies the player - // CheckpointSystem sends index after increment, so finish = totalCheckpoints const TOTAL_CHECKPOINTS = 5; if (player.checkpointIndex >= TOTAL_CHECKPOINTS) { this._qualifyPlayer(client.sessionId, "finish"); } }); - - this.onMessage("deathZoneHit", (client) => { - if (this.state.phase !== "playing" || this.state.gameMode !== "survival") return; - this._eliminatePlayer(client.sessionId, "deathzone"); - }); - - this.onMessage("inZone", (client, data) => { - if (this.state.phase !== "playing" || this.state.gameMode !== "teams") return; - const player = this.state.players.get(client.sessionId); - if (!player || player.isEliminated) return; - if (data.inZone) { - this._inZonePlayers.add(client.sessionId); - } else { - this._inZonePlayers.delete(client.sessionId); - } - }); } onJoin(client, options) { @@ -101,7 +75,6 @@ class ArenaRoom extends Room { this.state.players.set(client.sessionId, player); this._updatePlayersAlive(); - // Auto-start lobby timer on first player if (this.state.players.size === 1 && this.state.phase === "lobby") { this._startLobbyTimer(); } @@ -109,7 +82,6 @@ class ArenaRoom extends Room { onLeave(client, consented) { console.log(`[ArenaRoom] ${client.sessionId} left`); - this._inZonePlayers.delete(client.sessionId); this.state.players.delete(client.sessionId); this._updatePlayersAlive(); if (this.state.phase === "playing") { @@ -159,29 +131,17 @@ class ArenaRoom extends Room { } _startPlaying() { - const modeIndex = (this.state.roundNumber - 1) % ROUND_MODES.length; - this.state.gameMode = ROUND_MODES[modeIndex]; + this.state.gameMode = "race"; this.state.phase = "playing"; this.state.countdown = 0; - // Reset player state for new round - let teamToggle = 0; - this.state.players.forEach((p, id) => { + this.state.players.forEach((p) => { p.isEliminated = false; p.isQualified = false; p.isReady = false; p.checkpointIndex = 0; - if (this.state.gameMode === "teams") { - p.team = (teamToggle++ % 2 === 0) ? 1 : 2; - } else { - p.team = 0; - } }); - this.state.deathZoneY = -50; - this.state.teamScoreRed = 0; - this.state.teamScoreBlue = 0; - this._inZonePlayers.clear(); this._updatePlayersAlive(); this.broadcast("roundStart", { @@ -190,16 +150,9 @@ class ArenaRoom extends Room { totalRounds: this.state.totalRounds, }); - console.log(`[ArenaRoom] Round ${this.state.roundNumber} started (mode: ${this.state.gameMode})`); + console.log(`[ArenaRoom] Round ${this.state.roundNumber} started (race)`); - if (this.state.gameMode === "race") { - this._phaseTimer = setTimeout(() => this._endRaceTimeout(), RACE_TIMEOUT * 1000); - } else if (this.state.gameMode === "survival") { - this._phaseTimer = setTimeout(() => this._startSurvivalRise(), SURVIVAL_START_DELAY * 1000); - } else if (this.state.gameMode === "teams") { - this._startTeamsScoring(); - this._phaseTimer = setTimeout(() => this._endTeamsRound(), TEAMS_DURATION * 1000); - } + this._phaseTimer = setTimeout(() => this._endRaceTimeout(), RACE_TIMEOUT * 1000); } _endRound() { @@ -209,7 +162,6 @@ class ArenaRoom extends Room { this.broadcast("roundEnd", { round: this.state.roundNumber }); console.log(`[ArenaRoom] Round ${this.state.roundNumber} ended`); - // Check if all rounds done if (this.state.roundNumber >= this.state.totalRounds) { this._phaseTimer = setTimeout(() => this._endGame(), ROUND_END_DURATION * 1000); } else { @@ -220,13 +172,10 @@ class ArenaRoom extends Room { _nextRound() { this.state.roundNumber += 1; this.state.phase = "lobby"; - this.state.playersAlive = 0; this.state.players.forEach((p) => { - if (!p.isEliminated) { - p.isReady = false; - const spawn = this._findSpawnPosition(); - p.x = spawn.x; p.y = spawn.y; p.z = spawn.z; - } + p.isReady = false; + const spawn = this._findSpawnPosition(); + p.x = spawn.x; p.y = spawn.y; p.z = spawn.z; }); this._updatePlayersAlive(); this._lobbyTimer = null; @@ -236,7 +185,6 @@ class ArenaRoom extends Room { _endGame() { this.state.phase = "gameEnd"; - // Find winner: last qualified player, or player with most checkpoints let winner = ""; let best = -1; this.state.players.forEach((p) => { @@ -251,7 +199,6 @@ class ArenaRoom extends Room { // ─── Race mode ────────────────────────────────────────────────────── _endRaceTimeout() { - // Eliminate anyone who hasn't qualified this.state.players.forEach((p, id) => { if (!p.isQualified && !p.isEliminated) { this._eliminatePlayer(id, "timeout"); @@ -260,49 +207,6 @@ class ArenaRoom extends Room { this._endRound(); } - // ─── Survival mode ────────────────────────────────────────────────── - - _startSurvivalRise() { - console.log(`[ArenaRoom] DeathZone starts rising`); - this._survivalInterval = setInterval(() => { - this.state.deathZoneY += SURVIVAL_RISE_RATE * (16 / 1000); - if (this.state.deathZoneY > SURVIVAL_MAX_Y) { - this.state.deathZoneY = SURVIVAL_MAX_Y; - } - }, 16); - } - - // ─── Teams mode ───────────────────────────────────────────────────── - - _startTeamsScoring() { - this._teamInterval = setInterval(() => { - let redInZone = 0; - let blueInZone = 0; - this._inZonePlayers.forEach((id) => { - const p = this.state.players.get(id); - if (!p || p.isEliminated) return; - if (p.team === 1) redInZone++; - else if (p.team === 2) blueInZone++; - }); - if (redInZone > blueInZone) this.state.teamScoreRed = Math.min(this.state.teamScoreRed + 1, 32767); - else if (blueInZone > redInZone) this.state.teamScoreBlue = Math.min(this.state.teamScoreBlue + 1, 32767); - }, 1000); - } - - _endTeamsRound() { - // Eliminate losing team - const redWins = this.state.teamScoreRed >= this.state.teamScoreBlue; - const losingTeam = redWins ? 2 : 1; - this.state.players.forEach((p, id) => { - if (p.team === losingTeam && !p.isEliminated) { - this._eliminatePlayer(id, "teams_lost"); - } else if (!p.isEliminated) { - this._qualifyPlayer(id, "teams_won"); - } - }); - this._endRound(); - } - // ─── Elimination helpers ───────────────────────────────────────────── _eliminatePlayer(sessionId, reason) { @@ -323,45 +227,23 @@ class ArenaRoom extends Room { this.broadcast("qualified", { sessionId, name: player.name }); console.log(`[ArenaRoom] ${player.name} (${sessionId}) qualified: ${reason}`); - if (this.state.gameMode === "race") { - const aliveCount = this._getAliveCount(); - const totalActive = this._getActiveCount(); - const qualifiedCount = this._getQualifiedCount(); - // Eliminate once qualify_ratio reached - const toQualify = Math.ceil(totalActive * QUALIFY_RATIO); - if (qualifiedCount >= toQualify) { - this.state.players.forEach((p, id) => { - if (!p.isQualified && !p.isEliminated) { - this._eliminatePlayer(id, "too_slow"); - } - }); - this._endRound(); - } - } else if (this.state.gameMode === "survival") { - // In survival: only 1 qualifies (last one), rest get eliminated by zone - this._checkRoundEndCondition(); + const totalActive = this._getActiveCount(); + const qualifiedCount = this._getQualifiedCount(); + const toQualify = Math.ceil(totalActive * QUALIFY_RATIO); + if (qualifiedCount >= toQualify) { + this.state.players.forEach((p, id) => { + if (!p.isQualified && !p.isEliminated) { + this._eliminatePlayer(id, "too_slow"); + } + }); + this._endRound(); } } _checkRoundEndCondition() { if (this.state.phase !== "playing") return; const alive = this._getAliveCount(); - const qualified = this._getQualifiedCount(); - const total = this._getActiveCount(); - - if (this.state.gameMode === "survival") { - if (alive <= 1) { - // Qualify the last survivor - this.state.players.forEach((p, id) => { - if (!p.isEliminated && !p.isQualified) { - this._qualifyPlayer(id, "last_survivor"); - } - }); - this._endRound(); - } - } else if (alive === 0 || alive + qualified >= total) { - this._endRound(); - } + if (alive === 0) this._endRound(); } _getAliveCount() { @@ -377,7 +259,9 @@ class ArenaRoom extends Room { } _getActiveCount() { - return this.state.players.size; + let n = 0; + this.state.players.forEach((p) => { if (!p.isEliminated) n++; }); + return n; } _updatePlayersAlive() { @@ -387,8 +271,6 @@ class ArenaRoom extends Room { _clearAllTimers() { if (this._phaseTimer) { clearTimeout(this._phaseTimer); this._phaseTimer = null; } if (this._lobbyTimer) { clearTimeout(this._lobbyTimer); this._lobbyTimer = null; } - if (this._survivalInterval) { clearInterval(this._survivalInterval); this._survivalInterval = null; } - if (this._teamInterval) { clearInterval(this._teamInterval); this._teamInterval = null; } } // ─── Spawn helper ──────────────────────────────────────────────────── diff --git a/rolld_backend/game/src/schema/GameState.js b/rolld_backend/game/src/schema/GameState.js index a8e610f..e8c61d2 100644 --- a/rolld_backend/game/src/schema/GameState.js +++ b/rolld_backend/game/src/schema/GameState.js @@ -3,88 +3,58 @@ const { Schema, MapSchema, defineTypes } = require("@colyseus/schema"); class Player extends Schema { constructor() { super(); - this.x = 0; - this.y = 5; - this.z = 0; - this.vx = 0; - this.vy = 0; - this.vz = 0; - this.rx = 0; - this.ry = 0; - this.rz = 0; - this.rw = 1; + this.x = 0; this.y = 5; this.z = 0; + this.vx = 0; this.vy = 0; this.vz = 0; + this.rx = 0; this.ry = 0; this.rz = 0; this.rw = 1; this.t = 0; this.name = ""; - this.colorR = 1; - this.colorG = 1; - this.colorB = 1; - this.avx = 0; - this.avy = 0; - this.avz = 0; - // Game state + this.colorR = 1; this.colorG = 1; this.colorB = 1; + this.avx = 0; this.avy = 0; this.avz = 0; this.isEliminated = false; - this.isQualified = false; - this.isReady = false; - this.team = 0; + this.isQualified = false; + this.isReady = false; this.checkpointIndex = 0; } } +// Field order must match NetworkSchema.cs [Type(N)] indices exactly defineTypes(Player, { - x: "float32", - y: "float32", - z: "float32", - vx: "float32", - vy: "float32", - vz: "float32", - rx: "float32", - ry: "float32", - rz: "float32", - rw: "float32", - t: "float64", - name: "string", - colorR: "float32", - colorG: "float32", - colorB: "float32", - avx: "float32", - avy: "float32", - avz: "float32", - isEliminated: "boolean", - isQualified: "boolean", - isReady: "boolean", - team: "int8", - checkpointIndex: "int8", + x: "float32", y: "float32", z: "float32", // 0-2 + vx: "float32", vy: "float32", vz: "float32", // 3-5 + rx: "float32", ry: "float32", rz: "float32", rw: "float32", // 6-9 + t: "float64", // 10 + name: "string", // 11 + colorR: "float32", colorG: "float32", colorB: "float32", // 12-14 + avx: "float32", avy: "float32", avz: "float32", // 15-17 + isEliminated: "boolean", // 18 + isQualified: "boolean", // 19 + isReady: "boolean", // 20 + checkpointIndex: "int8", // 21 }); class GameState extends Schema { constructor() { super(); - this.players = new MapSchema(); - this.phase = "lobby"; - this.countdown = 0; - this.roundNumber = 1; - this.totalRounds = 3; + this.players = new MapSchema(); + this.phase = "lobby"; + this.countdown = 0; + this.roundNumber = 1; + this.totalRounds = 3; this.playersAlive = 0; - this.gameMode = "race"; - this.deathZoneY = -50; - this.teamScoreRed = 0; - this.teamScoreBlue = 0; - this.winnerName = ""; + this.gameMode = "race"; + this.winnerName = ""; } } defineTypes(GameState, { - players: { map: Player }, - phase: "string", - countdown: "float32", - roundNumber: "int8", - totalRounds: "int8", - playersAlive: "int8", - gameMode: "string", - deathZoneY: "float32", - teamScoreRed: "int16", - teamScoreBlue: "int16", - winnerName: "string", + players: { map: Player }, // 0 + phase: "string", // 1 + countdown: "float32", // 2 + roundNumber: "int8", // 3 + totalRounds: "int8", // 4 + playersAlive: "int8", // 5 + gameMode: "string", // 6 + winnerName: "string", // 7 }); module.exports = { GameState, Player };