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:
@@ -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<Rigidbody>();
|
||||
_meshTransform = transform;
|
||||
}
|
||||
|
||||
public void SetSpawnPosition(Vector3 pos) => _spawnPos = pos;
|
||||
|
||||
/// <summary>
|
||||
/// 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<Rigidbody>();
|
||||
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>();
|
||||
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<Rigidbody>();
|
||||
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<Rigidbody>();
|
||||
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<Rigidbody>();
|
||||
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<Rigidbody>();
|
||||
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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31c6f8ef706b51448b461b2b027e2ea8
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -44,10 +44,9 @@ public class NetworkManager : MonoBehaviour
|
||||
public event Action<float> OnCountdownChanged; // seconds remaining
|
||||
public event Action<string, string> OnEliminated; // sessionId, reason
|
||||
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<string> OnGameEnd; // winnerName
|
||||
public event Action<float> 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<EliminatedMsg>("eliminated", msg =>
|
||||
@@ -161,7 +159,7 @@ public class NetworkManager : MonoBehaviour
|
||||
_room.OnMessage<RoundStartMsg>("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<RoundEndMsg>("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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = "";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99fdfaa3e87a64d4e958f81014e6cdab
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e7c98b369c3ccf4aac0ad3ad2bcbbff
|
||||
@@ -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()
|
||||
|
||||
@@ -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 ────────────────────────────────────────────────────
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user