Supprime les mini-jeux (survival/teams), corrige tous les bugs identifiés

- Supprime DeathZone.cs, ZoneCapture.cs, ArenaZoneBuilder.cs
- ArenaRoom.js : mode race uniquement, fix _checkRoundEndCondition, fix _getActiveCount
- GameState.js : supprime team (Player) et deathZoneY/teamScoreRed/teamScoreBlue (GameState)
- NetworkSchema.cs : aligne sur le nouveau schéma serveur (supprime team, indices corrigés)
- NetworkManager.cs : supprime OnDeathZoneYChanged/SendDeathZoneHit/SendInZone, OnRoundStart passe totalRounds
- GameManager.cs : subscriptions OnEnable→Start/OnDestroy, fix Lobby (player visible si connecté), HandleRoundStart(totalRounds)
- GameHUD.cs : supprime blocs survival/teams, ajoute SetTotalRounds, supprime dead code
- PlayerController.cs : cache Rigidbody, fix OnCollisionStay gel (supprime else), SetSpawnPosition
- CheckpointSystem.cs : flash le prochain checkpoint actif, supprime FinishFlash vide
- LobbyUI.cs : CancelInvoke sur connexion, appelle SetSpawnPosition

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-17 16:28:37 +02:00
parent 456d876847
commit 4743c307a6
15 changed files with 120 additions and 1195 deletions

View File

@@ -44,6 +44,7 @@ public class PlayerController : MonoBehaviour
public float maxVelocity = 120f; // Velocity cap to prevent infinite acceleration public float maxVelocity = 120f; // Velocity cap to prevent infinite acceleration
public float respawnY = -10f; // Y threshold for respawn public float respawnY = -10f; // Y threshold for respawn
private Vector3 _spawnPos = new Vector3(0f, 3f, -30f); private Vector3 _spawnPos = new Vector3(0f, 3f, -30f);
private Rigidbody _rb;
// Squash & stretch // Squash & stretch
private bool _isSquashing = false; private bool _isSquashing = false;
@@ -80,10 +81,12 @@ public class PlayerController : MonoBehaviour
void Start() void Start()
{ {
Debug.Log("PlayerController script initialized."); Debug.Log("PlayerController script initialized.");
// Cursor lock is handled by LobbyUI on connect/disconnect _rb = GetComponent<Rigidbody>();
_meshTransform = transform; // Will be the sphere itself for squash _meshTransform = transform;
} }
public void SetSpawnPosition(Vector3 pos) => _spawnPos = pos;
/// <summary> /// <summary>
/// Called by LobbyUI after connecting. Sets up the local player /// Called by LobbyUI after connecting. Sets up the local player
/// with a floating name label and a 50% color tint. /// with a floating name label and a 50% color tint.
@@ -208,16 +211,15 @@ public class PlayerController : MonoBehaviour
// --- Respawn if fallen off the map --- // --- Respawn if fallen off the map ---
if (transform.position.y < respawnY) if (transform.position.y < respawnY)
{ {
Rigidbody rbRespawn = GetComponent<Rigidbody>(); if (_rb != null)
if (rbRespawn != null)
{ {
rbRespawn.linearVelocity = Vector3.zero; _rb.linearVelocity = Vector3.zero;
rbRespawn.angularVelocity = Vector3.zero; _rb.angularVelocity = Vector3.zero;
_rb.useGravity = true;
} }
transform.position = _spawnPos; transform.position = _spawnPos;
isOnGelViolet = false; isOnGelViolet = false;
isOnGelOrange = false; isOnGelOrange = false;
if (rbRespawn != null) rbRespawn.useGravity = true;
Debug.Log("[Player] Respawned after falling."); Debug.Log("[Player] Respawned after falling.");
return; return;
} }
@@ -227,7 +229,7 @@ public class PlayerController : MonoBehaviour
_fallWarningAlpha = Mathf.Lerp(_fallWarningAlpha, fallTarget, Time.deltaTime * 5f); _fallWarningAlpha = Mathf.Lerp(_fallWarningAlpha, fallTarget, Time.deltaTime * 5f);
// Mouvement continu selon les directions maintenues // Mouvement continu selon les directions maintenues
Rigidbody rb = GetComponent<Rigidbody>(); Rigidbody rb = _rb;
if (rb != null) if (rb != null)
{ {
float currentSpeed = MovementSpeed; float currentSpeed = MovementSpeed;
@@ -326,43 +328,26 @@ public class PlayerController : MonoBehaviour
void OnCollisionStay(Collision collision) void OnCollisionStay(Collision collision)
{ {
Collider col = collision.collider; Collider col = collision.collider;
if (col != null && col.sharedMaterial != null) if (col == null || col.sharedMaterial == null) return;
{
if (col.sharedMaterial.name.Contains("GelOrange")) if (col.sharedMaterial.name.Contains("GelOrange"))
{ {
isOnGelOrange = true; isOnGelOrange = true;
} }
else
{
isOnGelOrange = false;
}
if (col.sharedMaterial.name.Contains("GelViolet")) if (col.sharedMaterial.name.Contains("GelViolet"))
{ {
if (!isOnGelViolet) if (!isOnGelViolet)
{ {
// Premier contact : sauvegarder le drag et augmenter le frein originalDrag = _rb != null ? _rb.linearDamping : 0f;
Rigidbody rb = GetComponent<Rigidbody>(); if (_rb != null) _rb.linearDamping = 1f;
if (rb != null)
{
originalDrag = rb.linearDamping;
rb.linearDamping = 1f; // Fort frein pour éviter le catapultage aux bords
}
} }
isOnGelViolet = true; isOnGelViolet = true;
// Normale instantanée de la surface de contact
Vector3 avgNormal = Vector3.zero; Vector3 avgNormal = Vector3.zero;
foreach (ContactPoint contact in collision.contacts) foreach (ContactPoint contact in collision.contacts)
{
avgNormal += contact.normal; avgNormal += contact.normal;
}
stickyNormal = avgNormal.normalized; stickyNormal = avgNormal.normalized;
} }
else
{
isOnGelViolet = false;
}
}
} }
void OnCollisionExit(Collision collision) void OnCollisionExit(Collision collision)
@@ -424,17 +409,9 @@ public class PlayerController : MonoBehaviour
public void PerformJump(float force) public void PerformJump(float force)
{ {
Rigidbody rb = GetComponent<Rigidbody>(); if (_rb == null) return;
if (rb != null)
{
// Jump direction: surface normal when on sticky, otherwise up
Vector3 jumpDir = isOnGelViolet ? stickyNormal : Vector3.up; Vector3 jumpDir = isOnGelViolet ? stickyNormal : Vector3.up;
rb.AddForce(jumpDir * force, ForceMode.Impulse); _rb.AddForce(jumpDir * force, ForceMode.Impulse);
}
else
{
Debug.LogWarning("Rigidbody component not found on PlayerController.");
}
} }
private bool IsGrounded() private bool IsGrounded()
@@ -546,11 +523,8 @@ public class PlayerController : MonoBehaviour
// Add slight upward component so the ball lifts off the ground // Add slight upward component so the ball lifts off the ground
dir = (dir + Vector3.up * 0.3f).normalized; dir = (dir + Vector3.up * 0.3f).normalized;
var rb = GetComponent<Rigidbody>(); if (_rb != null)
if (rb != null) _rb.AddForce(dir * bumpForce, ForceMode.Impulse);
{
rb.AddForce(dir * bumpForce, ForceMode.Impulse);
}
} }
void OnCollisionEnter(Collision collision) void OnCollisionEnter(Collision collision)
@@ -742,10 +716,9 @@ public class PlayerController : MonoBehaviour
// ======================== // ========================
// SPEED INDICATOR // SPEED INDICATOR
// ======================== // ========================
Rigidbody rbHud = GetComponent<Rigidbody>(); if (_rb != null)
if (rbHud != null)
{ {
float speed = rbHud.linearVelocity.magnitude; float speed = _rb.linearVelocity.magnitude;
var speedStyle = new GUIStyle(GUI.skin.label) var speedStyle = new GUIStyle(GUI.skin.label)
{ {
alignment = TextAnchor.MiddleRight, alignment = TextAnchor.MiddleRight,

View File

@@ -1,573 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 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).
/// </summary>
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<MovingPlatform> _movingPlatforms = new List<MovingPlatform>();
// ── 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);
}
/// <summary>Ground-level paths connecting all zones, with directional color markers.</summary>
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<PhysicsMaterial>("GelBleu");
_matGelOrange = Resources.Load<PhysicsMaterial>("GelOrange");
_matGelViolet = Resources.Load<PhysicsMaterial>("GelViolet");
_matBouncy = Resources.Load<PhysicsMaterial>("Bouncy");
_matNormal = Resources.Load<PhysicsMaterial>("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<Collider>();
if (col != null && physMat != null) col.material = physMat;
var rend = go.GetComponent<Renderer>();
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;
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 31c6f8ef706b51448b461b2b027e2ea8

View File

@@ -27,7 +27,7 @@ public class GameManager : MonoBehaviour
DontDestroyOnLoad(gameObject); DontDestroyOnLoad(gameObject);
} }
void OnEnable() void Start()
{ {
var nm = NetworkManager.Instance; var nm = NetworkManager.Instance;
if (nm == null) return; if (nm == null) return;
@@ -41,7 +41,7 @@ public class GameManager : MonoBehaviour
nm.OnDisconnected += HandleDisconnected; nm.OnDisconnected += HandleDisconnected;
} }
void OnDisable() void OnDestroy()
{ {
var nm = NetworkManager.Instance; var nm = NetworkManager.Instance;
if (nm == null) return; if (nm == null) return;
@@ -106,11 +106,12 @@ public class GameManager : MonoBehaviour
} }
} }
void HandleRoundStart(int round, string mode) void HandleRoundStart(int round, string mode, int totalRounds)
{ {
CurrentRound = round; CurrentRound = round;
CurrentMode = mode; CurrentMode = mode;
gameHUD?.SetRoundInfo(round, mode); gameHUD?.SetRoundInfo(round, mode);
gameHUD?.SetTotalRounds(totalRounds);
IsLocalEliminated = false; IsLocalEliminated = false;
} }
@@ -140,7 +141,7 @@ public class GameManager : MonoBehaviour
switch (phase) switch (phase)
{ {
case GamePhase.Lobby: case GamePhase.Lobby:
SetPlayerActive(false); SetPlayerActive(NetworkManager.Instance?.IsConnected ?? false);
SetSpectatorActive(false); SetSpectatorActive(false);
gameHUD?.SetPhase("lobby"); gameHUD?.SetPhase("lobby");
break; break;

View File

@@ -88,6 +88,7 @@ public class LobbyUI : MonoBehaviour
_lobbyActive = false; _lobbyActive = false;
_isConnecting = false; _isConnecting = false;
_statusMessage = ""; _statusMessage = "";
CancelInvoke(nameof(CheckConnectionTimeout));
// --- Activate the player hierarchy --- // --- Activate the player hierarchy ---
if (playerRoot != null) if (playerRoot != null)
@@ -113,6 +114,7 @@ public class LobbyUI : MonoBehaviour
rb.position = spawnPos; rb.position = spawnPos;
} }
pc.transform.position = spawnPos; pc.transform.position = spawnPos;
pc.SetSpawnPosition(spawnPos);
Debug.Log($"[Lobby] Player teleported to spawn: {spawnPos}"); Debug.Log($"[Lobby] Player teleported to spawn: {spawnPos}");
} }
pc.enabled = true; pc.enabled = true;

View File

@@ -44,10 +44,9 @@ public class NetworkManager : MonoBehaviour
public event Action<float> OnCountdownChanged; // seconds remaining public event Action<float> OnCountdownChanged; // seconds remaining
public event Action<string, string> OnEliminated; // sessionId, reason public event Action<string, string> OnEliminated; // sessionId, reason
public event Action<string> OnQualified; // sessionId public event Action<string> OnQualified; // sessionId
public event Action<int, string> OnRoundStart; // roundNumber, mode public event Action<int, string, int> OnRoundStart; // roundNumber, mode, totalRounds
public event Action<int> OnRoundEnd; // roundNumber public event Action<int> OnRoundEnd; // roundNumber
public event Action<string> OnGameEnd; // winnerName public event Action<string> OnGameEnd; // winnerName
public event Action<float> OnDeathZoneYChanged; // for survival mode
// --- Internals --- // --- Internals ---
private Client _client; private Client _client;
@@ -145,7 +144,6 @@ public class NetworkManager : MonoBehaviour
// Game state changes // Game state changes
_callbacks.Listen(state => state.phase, (newValue, prevValue) => _OnPhaseChanged(newValue)); _callbacks.Listen(state => state.phase, (newValue, prevValue) => _OnPhaseChanged(newValue));
_callbacks.Listen(state => state.countdown, (newValue, prevValue) => OnCountdownChanged?.Invoke(newValue)); _callbacks.Listen(state => state.countdown, (newValue, prevValue) => OnCountdownChanged?.Invoke(newValue));
_callbacks.Listen(state => state.deathZoneY, (newValue, prevValue) => OnDeathZoneYChanged?.Invoke(newValue));
// Server messages // Server messages
_room.OnMessage<EliminatedMsg>("eliminated", msg => _room.OnMessage<EliminatedMsg>("eliminated", msg =>
@@ -161,7 +159,7 @@ public class NetworkManager : MonoBehaviour
_room.OnMessage<RoundStartMsg>("roundStart", msg => _room.OnMessage<RoundStartMsg>("roundStart", msg =>
{ {
Debug.Log($"[Network] Round {msg.round} started ({msg.mode})"); 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<RoundEndMsg>("roundEnd", msg => _room.OnMessage<RoundEndMsg>("roundEnd", msg =>
{ {
@@ -208,18 +206,6 @@ public class NetworkManager : MonoBehaviour
await _room.Send("checkpointReached", new { index }); 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 ───────────────────────────────────────────────── // ─── State Callbacks ─────────────────────────────────────────────────
private void _OnPhaseChanged(string phase) private void _OnPhaseChanged(string phase)
@@ -285,11 +271,6 @@ public class NetworkManager : MonoBehaviour
new Vector3(player.avx, player.avy, player.avz) 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); controller.SetVisible(!player.isEliminated);
} }
} }

View File

@@ -25,8 +25,7 @@ public partial class NetworkPlayer : Schema
[Type(18, "boolean")] public bool isEliminated = false; [Type(18, "boolean")] public bool isEliminated = false;
[Type(19, "boolean")] public bool isQualified = false; [Type(19, "boolean")] public bool isQualified = false;
[Type(20, "boolean")] public bool isReady = false; [Type(20, "boolean")] public bool isReady = false;
[Type(21, "int8")] public int team = 0; [Type(21, "int8")] public int checkpointIndex = 0;
[Type(22, "int8")] public int checkpointIndex = 0;
} }
public partial class NetworkState : Schema public partial class NetworkState : Schema
@@ -40,8 +39,5 @@ public partial class NetworkState : Schema
[Type(4, "int8")] public int totalRounds = 4; [Type(4, "int8")] public int totalRounds = 4;
[Type(5, "int8")] public int playersAlive = 0; [Type(5, "int8")] public int playersAlive = 0;
[Type(6, "string")] public string gameMode = "race"; [Type(6, "string")] public string gameMode = "race";
[Type(7, "float32")] public float deathZoneY = -100; [Type(7, "string")] public string winnerName = "";
[Type(8, "int16")] public int teamScoreRed = 0;
[Type(9, "int16")] public int teamScoreBlue = 0;
[Type(10, "string")] public string winnerName = "";
} }

View File

@@ -80,11 +80,10 @@ public class CheckpointSystem : MonoBehaviour
{ {
_finished = true; _finished = true;
Debug.Log("[Checkpoint] FINISH LINE reached!"); Debug.Log("[Checkpoint] FINISH LINE reached!");
StartCoroutine(FinishFlash());
} }
else 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) private static void SetRendererColor(Renderer rend, Color c)
{ {
if (rend.material.HasProperty("_BaseColor")) rend.material.SetColor("_BaseColor", c); if (rend.material.HasProperty("_BaseColor")) rend.material.SetColor("_BaseColor", c);

View File

@@ -1,93 +0,0 @@
using UnityEngine;
/// <summary>
/// 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.
/// </summary>
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<PlayerController>() == null) return;
_hitSent = true;
Debug.Log("[DeathZone] Local player hit the death zone!");
NetworkManager.Instance?.SendDeathZoneHit();
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 99fdfaa3e87a64d4e958f81014e6cdab

View File

@@ -1,125 +0,0 @@
using UnityEngine;
/// <summary>
/// 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.
/// </summary>
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<Renderer>();
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<PlayerController>() != null)
{
_isLocalPlayerInZone = true;
_reportTimer = 0f;
NetworkManager.Instance?.SendInZone(true);
}
// Count remote players in zone
var remote = other.GetComponent<RemotePlayerController>();
if (remote != null)
{
// team info would need to be tracked — skip for now, server handles scoring
}
}
void OnTriggerExit(Collider other)
{
if (other.GetComponent<PlayerController>() != 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;
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 3e7c98b369c3ccf4aac0ad3ad2bcbbff

View File

@@ -91,6 +91,7 @@ public class GameHUD : MonoBehaviour
public void SetPhase(string phase) => _phase = phase; public void SetPhase(string phase) => _phase = phase;
public void SetCountdown(float v) => _countdown = v; public void SetCountdown(float v) => _countdown = v;
public void SetRoundInfo(int round, string mode) { _roundNumber = round; _gameMode = mode; } 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 SetCheckpoint(int current, int total) { _checkpointsCurrent = current; _checkpointsTotal = total; }
public void SetLocalRaceActive(bool active) 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); GUI.Label(new Rect(panelX + 8f, panelY + 32f, panelW - 16f, 24f), modeFull, modeStyle);
// ── Top-right: Players alive ────────────────────────────────────── // ── 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; float prX = Screen.width - 180f;
GUI.color = new Color(0.08f, 0.08f, 0.12f, 0.85f); GUI.color = new Color(0.08f, 0.08f, 0.12f, 0.85f);
GUI.DrawTexture(new Rect(prX, panelY, 168f, panelH), _bgTex); GUI.DrawTexture(new Rect(prX, panelY, 168f, panelH), _bgTex);
@@ -239,44 +231,6 @@ public class GameHUD : MonoBehaviour
$"Checkpoint {_checkpointsCurrent} / {_checkpointsTotal}", cpStyle); $"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 // Static accessors for cross-script use
@@ -285,32 +239,7 @@ public class GameHUD : MonoBehaviour
// Cached values updated from NetworkManager state polling // Cached values updated from NetworkManager state polling
private int _cachedPlayersAlive = 0; 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; public void SetPlayersAlive(int count) => _cachedPlayersAlive = count;
private static void EnsureTextures() private static void EnsureTextures()

View File

@@ -1,16 +1,11 @@
const { Room } = require("@colyseus/core"); const { Room } = require("@colyseus/core");
const { GameState, Player } = require("../schema/GameState"); const { GameState, Player } = require("../schema/GameState");
const ROUND_MODES = ["race", "survival", "teams"]; const LOBBY_TIMEOUT = 30;
const LOBBY_TIMEOUT = 30; // seconds before auto-start
const COUNTDOWN_DURATION = 3; const COUNTDOWN_DURATION = 3;
const ROUND_END_DURATION = 5; const ROUND_END_DURATION = 5;
const RACE_TIMEOUT = 180; // 3 min const RACE_TIMEOUT = 180;
const SURVIVAL_START_DELAY = 20; // seconds before deathzone rises const QUALIFY_RATIO = 0.6;
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
class ArenaRoom extends Room { class ArenaRoom extends Room {
maxClients = 20; maxClients = 20;
@@ -20,10 +15,7 @@ class ArenaRoom extends Room {
this.setPatchRate(16); // ~62.5 Hz this.setPatchRate(16); // ~62.5 Hz
this._phaseTimer = null; this._phaseTimer = null;
this._survivalInterval = null;
this._teamInterval = null;
this._lobbyTimer = null; this._lobbyTimer = null;
this._inZonePlayers = new Set(); // sessionIds currently in zone
console.log(`[ArenaRoom] Room ${this.roomId} created`); console.log(`[ArenaRoom] Room ${this.roomId} created`);
@@ -55,35 +47,17 @@ class ArenaRoom extends Room {
}); });
this.onMessage("checkpointReached", (client, data) => { 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); const player = this.state.players.get(client.sessionId);
if (!player || player.isEliminated || player.isQualified) return; if (!player || player.isEliminated || player.isQualified) return;
const expected = player.checkpointIndex; const expected = player.checkpointIndex;
if (data.index !== expected) return; // must hit in order if (data.index !== expected) return;
player.checkpointIndex = data.index + 1; 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; const TOTAL_CHECKPOINTS = 5;
if (player.checkpointIndex >= TOTAL_CHECKPOINTS) { if (player.checkpointIndex >= TOTAL_CHECKPOINTS) {
this._qualifyPlayer(client.sessionId, "finish"); 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) { onJoin(client, options) {
@@ -101,7 +75,6 @@ class ArenaRoom extends Room {
this.state.players.set(client.sessionId, player); this.state.players.set(client.sessionId, player);
this._updatePlayersAlive(); this._updatePlayersAlive();
// Auto-start lobby timer on first player
if (this.state.players.size === 1 && this.state.phase === "lobby") { if (this.state.players.size === 1 && this.state.phase === "lobby") {
this._startLobbyTimer(); this._startLobbyTimer();
} }
@@ -109,7 +82,6 @@ class ArenaRoom extends Room {
onLeave(client, consented) { onLeave(client, consented) {
console.log(`[ArenaRoom] ${client.sessionId} left`); console.log(`[ArenaRoom] ${client.sessionId} left`);
this._inZonePlayers.delete(client.sessionId);
this.state.players.delete(client.sessionId); this.state.players.delete(client.sessionId);
this._updatePlayersAlive(); this._updatePlayersAlive();
if (this.state.phase === "playing") { if (this.state.phase === "playing") {
@@ -159,29 +131,17 @@ class ArenaRoom extends Room {
} }
_startPlaying() { _startPlaying() {
const modeIndex = (this.state.roundNumber - 1) % ROUND_MODES.length; this.state.gameMode = "race";
this.state.gameMode = ROUND_MODES[modeIndex];
this.state.phase = "playing"; this.state.phase = "playing";
this.state.countdown = 0; this.state.countdown = 0;
// Reset player state for new round this.state.players.forEach((p) => {
let teamToggle = 0;
this.state.players.forEach((p, id) => {
p.isEliminated = false; p.isEliminated = false;
p.isQualified = false; p.isQualified = false;
p.isReady = false; p.isReady = false;
p.checkpointIndex = 0; 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._updatePlayersAlive();
this.broadcast("roundStart", { this.broadcast("roundStart", {
@@ -190,16 +150,9 @@ class ArenaRoom extends Room {
totalRounds: this.state.totalRounds, 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); 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);
}
} }
_endRound() { _endRound() {
@@ -209,7 +162,6 @@ class ArenaRoom extends Room {
this.broadcast("roundEnd", { round: this.state.roundNumber }); this.broadcast("roundEnd", { round: this.state.roundNumber });
console.log(`[ArenaRoom] Round ${this.state.roundNumber} ended`); console.log(`[ArenaRoom] Round ${this.state.roundNumber} ended`);
// Check if all rounds done
if (this.state.roundNumber >= this.state.totalRounds) { if (this.state.roundNumber >= this.state.totalRounds) {
this._phaseTimer = setTimeout(() => this._endGame(), ROUND_END_DURATION * 1000); this._phaseTimer = setTimeout(() => this._endGame(), ROUND_END_DURATION * 1000);
} else { } else {
@@ -220,13 +172,10 @@ class ArenaRoom extends Room {
_nextRound() { _nextRound() {
this.state.roundNumber += 1; this.state.roundNumber += 1;
this.state.phase = "lobby"; this.state.phase = "lobby";
this.state.playersAlive = 0;
this.state.players.forEach((p) => { this.state.players.forEach((p) => {
if (!p.isEliminated) {
p.isReady = false; p.isReady = false;
const spawn = this._findSpawnPosition(); const spawn = this._findSpawnPosition();
p.x = spawn.x; p.y = spawn.y; p.z = spawn.z; p.x = spawn.x; p.y = spawn.y; p.z = spawn.z;
}
}); });
this._updatePlayersAlive(); this._updatePlayersAlive();
this._lobbyTimer = null; this._lobbyTimer = null;
@@ -236,7 +185,6 @@ class ArenaRoom extends Room {
_endGame() { _endGame() {
this.state.phase = "gameEnd"; this.state.phase = "gameEnd";
// Find winner: last qualified player, or player with most checkpoints
let winner = ""; let winner = "";
let best = -1; let best = -1;
this.state.players.forEach((p) => { this.state.players.forEach((p) => {
@@ -251,7 +199,6 @@ class ArenaRoom extends Room {
// ─── Race mode ────────────────────────────────────────────────────── // ─── Race mode ──────────────────────────────────────────────────────
_endRaceTimeout() { _endRaceTimeout() {
// Eliminate anyone who hasn't qualified
this.state.players.forEach((p, id) => { this.state.players.forEach((p, id) => {
if (!p.isQualified && !p.isEliminated) { if (!p.isQualified && !p.isEliminated) {
this._eliminatePlayer(id, "timeout"); this._eliminatePlayer(id, "timeout");
@@ -260,49 +207,6 @@ class ArenaRoom extends Room {
this._endRound(); 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 ───────────────────────────────────────────── // ─── Elimination helpers ─────────────────────────────────────────────
_eliminatePlayer(sessionId, reason) { _eliminatePlayer(sessionId, reason) {
@@ -323,11 +227,8 @@ class ArenaRoom extends Room {
this.broadcast("qualified", { sessionId, name: player.name }); this.broadcast("qualified", { sessionId, name: player.name });
console.log(`[ArenaRoom] ${player.name} (${sessionId}) qualified: ${reason}`); console.log(`[ArenaRoom] ${player.name} (${sessionId}) qualified: ${reason}`);
if (this.state.gameMode === "race") {
const aliveCount = this._getAliveCount();
const totalActive = this._getActiveCount(); const totalActive = this._getActiveCount();
const qualifiedCount = this._getQualifiedCount(); const qualifiedCount = this._getQualifiedCount();
// Eliminate once qualify_ratio reached
const toQualify = Math.ceil(totalActive * QUALIFY_RATIO); const toQualify = Math.ceil(totalActive * QUALIFY_RATIO);
if (qualifiedCount >= toQualify) { if (qualifiedCount >= toQualify) {
this.state.players.forEach((p, id) => { this.state.players.forEach((p, id) => {
@@ -337,31 +238,12 @@ class ArenaRoom extends Room {
}); });
this._endRound(); this._endRound();
} }
} else if (this.state.gameMode === "survival") {
// In survival: only 1 qualifies (last one), rest get eliminated by zone
this._checkRoundEndCondition();
}
} }
_checkRoundEndCondition() { _checkRoundEndCondition() {
if (this.state.phase !== "playing") return; if (this.state.phase !== "playing") return;
const alive = this._getAliveCount(); const alive = this._getAliveCount();
const qualified = this._getQualifiedCount(); if (alive === 0) this._endRound();
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();
}
} }
_getAliveCount() { _getAliveCount() {
@@ -377,7 +259,9 @@ class ArenaRoom extends Room {
} }
_getActiveCount() { _getActiveCount() {
return this.state.players.size; let n = 0;
this.state.players.forEach((p) => { if (!p.isEliminated) n++; });
return n;
} }
_updatePlayersAlive() { _updatePlayersAlive() {
@@ -387,8 +271,6 @@ class ArenaRoom extends Room {
_clearAllTimers() { _clearAllTimers() {
if (this._phaseTimer) { clearTimeout(this._phaseTimer); this._phaseTimer = null; } if (this._phaseTimer) { clearTimeout(this._phaseTimer); this._phaseTimer = null; }
if (this._lobbyTimer) { clearTimeout(this._lobbyTimer); this._lobbyTimer = 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 ──────────────────────────────────────────────────── // ─── Spawn helper ────────────────────────────────────────────────────

View File

@@ -3,57 +3,33 @@ const { Schema, MapSchema, defineTypes } = require("@colyseus/schema");
class Player extends Schema { class Player extends Schema {
constructor() { constructor() {
super(); super();
this.x = 0; this.x = 0; this.y = 5; this.z = 0;
this.y = 5; this.vx = 0; this.vy = 0; this.vz = 0;
this.z = 0; this.rx = 0; this.ry = 0; this.rz = 0; this.rw = 1;
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.t = 0;
this.name = ""; this.name = "";
this.colorR = 1; this.colorR = 1; this.colorG = 1; this.colorB = 1;
this.colorG = 1; this.avx = 0; this.avy = 0; this.avz = 0;
this.colorB = 1;
this.avx = 0;
this.avy = 0;
this.avz = 0;
// Game state
this.isEliminated = false; this.isEliminated = false;
this.isQualified = false; this.isQualified = false;
this.isReady = false; this.isReady = false;
this.team = 0;
this.checkpointIndex = 0; this.checkpointIndex = 0;
} }
} }
// Field order must match NetworkSchema.cs [Type(N)] indices exactly
defineTypes(Player, { defineTypes(Player, {
x: "float32", x: "float32", y: "float32", z: "float32", // 0-2
y: "float32", vx: "float32", vy: "float32", vz: "float32", // 3-5
z: "float32", rx: "float32", ry: "float32", rz: "float32", rw: "float32", // 6-9
vx: "float32", t: "float64", // 10
vy: "float32", name: "string", // 11
vz: "float32", colorR: "float32", colorG: "float32", colorB: "float32", // 12-14
rx: "float32", avx: "float32", avy: "float32", avz: "float32", // 15-17
ry: "float32", isEliminated: "boolean", // 18
rz: "float32", isQualified: "boolean", // 19
rw: "float32", isReady: "boolean", // 20
t: "float64", checkpointIndex: "int8", // 21
name: "string",
colorR: "float32",
colorG: "float32",
colorB: "float32",
avx: "float32",
avy: "float32",
avz: "float32",
isEliminated: "boolean",
isQualified: "boolean",
isReady: "boolean",
team: "int8",
checkpointIndex: "int8",
}); });
class GameState extends Schema { class GameState extends Schema {
@@ -66,25 +42,19 @@ class GameState extends Schema {
this.totalRounds = 3; this.totalRounds = 3;
this.playersAlive = 0; this.playersAlive = 0;
this.gameMode = "race"; this.gameMode = "race";
this.deathZoneY = -50;
this.teamScoreRed = 0;
this.teamScoreBlue = 0;
this.winnerName = ""; this.winnerName = "";
} }
} }
defineTypes(GameState, { defineTypes(GameState, {
players: { map: Player }, players: { map: Player }, // 0
phase: "string", phase: "string", // 1
countdown: "float32", countdown: "float32", // 2
roundNumber: "int8", roundNumber: "int8", // 3
totalRounds: "int8", totalRounds: "int8", // 4
playersAlive: "int8", playersAlive: "int8", // 5
gameMode: "string", gameMode: "string", // 6
deathZoneY: "float32", winnerName: "string", // 7
teamScoreRed: "int16",
teamScoreBlue: "int16",
winnerName: "string",
}); });
module.exports = { GameState, Player }; module.exports = { GameState, Player };