2025-06-03 10:57:14 +02:00

361 lines
12 KiB
C#

using UnityEngine;
using Fusion;
using System.Collections;
using System.Linq;
using TMPro;
using UnityEngine.UI;
namespace Scripts
{
public class GameManager : NetworkBehaviour
{
[Header("Cube Prefabs")]
[SerializeField] NetworkObject redCube;
[SerializeField] NetworkObject blueCube;
[SerializeField] NetworkObject greenCube;
[Header("Plane Prefabs")]
[SerializeField] NetworkObject redPlane;
[SerializeField] NetworkObject bluePlane;
[SerializeField] NetworkObject greenPlane;
[Header("Spawn Settings")]
[SerializeField] float spawnDistance = 0.5f;
[SerializeField] float spawnHeight = 1f;
[SerializeField] Vector3 cubeSpawnPosition = new Vector3(0, 1, 1);
[SerializeField] Vector3 planeSpawnPosition = new Vector3(-1, 0, 0);
[SerializeField] float despawnDelay = 2.0f;
[Header("UI")]
[SerializeField] GameObject gameStartCanvas;
[SerializeField] TextMeshProUGUI timerText;
[SerializeField] GameObject winPanel;
[SerializeField] GameObject losePanel;
[Networked] public bool IsMenuVisible { get; set; } = true; // Maintain network synchronisation properties
[Header("Game Settings")]
[SerializeField] float gameDuration = 300f; // Total length of game (seconds)
[Networked] private TickTimer gameTimer { get; set; }
[Networked] private int currentStage { get; set; } = 0;
private ChangeDetector _changeDetector;// Change Detector for Fusion 2
private PlayerRef[] players;
// Store currently active squares and platforms
private NetworkObject[] activeCubes = new NetworkObject[2];
private NetworkObject[] activePlanes = new NetworkObject[2];
private bool gameEnded = false;
private bool isSinglePlayer = false;
public override void Spawned()
{
_changeDetector = GetChangeDetector(ChangeDetector.Source.SimulationState);
if (Object.HasStateAuthority)
{
players = Runner.ActivePlayers.ToArray();
IsMenuVisible = true; // Host sets initial state
isSinglePlayer = players.Length == 1;
}
// Initial UI state for all clients
UpdateMenuVisibility();
// Reset the UI state
winPanel.SetActive(false);
losePanel.SetActive(false);
timerText.gameObject.SetActive(true);
}
// Add render loop to handle change detection
public override void Render()
{
base.Render();
// Detect network state changes with ChangeDetector
foreach (var change in _changeDetector.DetectChanges(this))
{
switch (change)
{
case nameof(IsMenuVisible):
UpdateMenuVisibility();
break;
// You can add other properties that need to be detected
}
}
}
// Ways to update menu visibility
private void UpdateMenuVisibility()
{
gameStartCanvas.SetActive(IsMenuVisible);
}
IEnumerator InitialSpawn()
{
// Initial wait 5 seconds
yield return new WaitForSeconds(5f);
// Phase 1: Generate red squares and red platforms for player 1
yield return SpawnForPlayer(0, redCube, redPlane);
currentStage = 1;
}
// RPC methods called by platform triggers
[Rpc(sources: RpcSources.All, targets: RpcTargets.StateAuthority)]
public void RPC_OnPuzzleSolved(int stage)
{
if (stage != currentStage || gameEnded) return;
StartCoroutine(HandlePuzzleSolved());
}
IEnumerator HandlePuzzleSolved()
{
// Destroy the current stage's blocks and platforms
yield return DespawnCurrentPuzzle();
// Delay the generation of the next puzzle for a while
yield return new WaitForSeconds(despawnDelay);
switch (currentStage)
{
case 1: // Red puzzle complete
// Single-player mode: generate blue puzzles for the same player
// Multiplayer mode: generates blue puzzles for player 2
int nextPlayerIndex = isSinglePlayer ? 0 : 1;
yield return SpawnForPlayer(nextPlayerIndex, blueCube, bluePlane);
Debug.Log($"-----------for the player {(isSinglePlayer ? "1" : "2")} generate blue puzzle-----------");
currentStage = 2;
break;
case 2: // Blue puzzle complete
// Generate green puzzles for player 1 (single/multiplayer both player 1)
yield return SpawnForPlayer(0, greenCube, greenPlane);
Debug.Log("--------Spawning GreenCubes for player1----------");
currentStage = 3;
break;
case 3: //
Debug.Log("---------All Puzzle Solved!------------");
EndGame(true); // Winning the game
break;
}
}
// Destroy the current puzzle
IEnumerator DespawnCurrentPuzzle()
{
int playerIndex = currentStage switch
{
1 => 0, // Red puzzles belong to player 0
2 => isSinglePlayer ? 0 : 1, // Blue puzzles: single player mode player 0, multiplayer mode player 1
_ => -1
};
if (playerIndex >= 0)
{
// Destroy the cube
if (activeCubes[playerIndex] != null && activeCubes[playerIndex].IsValid)
{
Runner.Despawn(activeCubes[playerIndex]);
activeCubes[playerIndex] = null;
}
// Destruction platform
if (activePlanes[playerIndex] != null && activePlanes[playerIndex].IsValid)
{
Runner.Despawn(activePlanes[playerIndex]);
activePlanes[playerIndex] = null;
}
// Wait one frame to ensure destruction is complete
yield return null;
}
}
IEnumerator SpawnForPlayer(int playerIndex, NetworkObject cubePrefab, NetworkObject planePrefab)
{
// Ensure that the player index is valid
if (playerIndex >= players.Length)
{
Debug.LogError($"----------Invalid player index: {playerIndex}, total number of players: {players.Length}------------");
yield break;
}
PlayerRef player = players[playerIndex];
// Get the position in front of the player's viewpoint
if (Runner.TryGetPlayerObject(player, out var playerObj))
{
Vector3 spawnPos = playerObj.transform.position +
playerObj.transform.forward * spawnDistance +
Vector3.up * spawnHeight;
// Generate Cube
activeCubes[playerIndex] = Runner.Spawn(
cubePrefab,
spawnPos,
Quaternion.identity,
player
);
// Spawn platforms (placed on the ground directly in front of the player)
activePlanes[playerIndex] = Runner.Spawn(
planePrefab,
spawnPos - Vector3.up * spawnHeight + new Vector3(0, 1, 0),
Quaternion.identity,
player
);
}
else // normal generation
{
activeCubes[playerIndex] = Runner.Spawn(
cubePrefab,
cubeSpawnPosition,
Quaternion.identity,
player
);
activePlanes[playerIndex] = Runner.Spawn(
planePrefab,
planeSpawnPosition,
Quaternion.identity,
player
);
}
yield return null;
}
public override void FixedUpdateNetwork()
{
if (gameEnded || !Object.HasStateAuthority) return;
// Update and check game timer
if (gameTimer.Expired(Runner))
{
// Check if the game is finished
if (currentStage < 3)
{
EndGame(false); // Game Failure
}
else
{
EndGame(true); // Winning the game
}
gameTimer = TickTimer.None;
}
else if (gameTimer.IsRunning)
{
// Update the UI of all clients
UpdateTimerUI();
}
}
// Update the timer UI
private void UpdateTimerUI()
{
float remainingTime = gameTimer.RemainingTime(Runner) ?? 0;
int minutes = Mathf.FloorToInt(remainingTime / 60);
int seconds = Mathf.FloorToInt(remainingTime % 60);
timerText.text = $"{minutes:00}:{seconds:00}";
// Turns red when the remaining time is less than 10 seconds.
if (remainingTime <= 10f)
{
timerText.color = Color.red;
}
}
// Game Over
private void EndGame(bool isWin)
{
if (gameEnded) return;
gameEnded = true;
gameTimer = TickTimer.None;
RPC_ShowGameResult(isWin);
// Stop all puzzle generation
StopAllCoroutines();
// Destroy all existing puzzles
for (int i = 0; i < activeCubes.Length; i++)
{
if (activeCubes[i] != null && activeCubes[i].IsValid)
{
Runner.Despawn(activeCubes[i]);
}
if (activePlanes[i] != null && activePlanes[i].IsValid)
{
Runner.Despawn(activePlanes[i]);
}
}
}
[Rpc(sources: RpcSources.StateAuthority, targets: RpcTargets.All)]
private void RPC_ShowGameResult(bool isWin)
{
timerText.gameObject.SetActive(false);
if (isWin)
{
winPanel.SetActive(true);
Debug.Log("----------------YOU WIN!!!----------------------");
}
else
{
losePanel.SetActive(true);
Debug.Log("----------------YOU LOSE!!!----------------------");
}
}
[Rpc(RpcSources.All, RpcTargets.All)]
public void RPC_HideMenu()
{
// Immediate local hidden menu for all clients (provides instant feedback)
gameStartCanvas.SetActive(IsMenuVisible);
// Ensure that only the host actually modifies the network state
if (Object.HasStateAuthority)
{
IsMenuVisible = false;
}
}
public void StartGame()
{
// Local hidden menus for all clients
gameStartCanvas.SetActive(false);
// Host sets network state
if (Object.HasStateAuthority)
{
IsMenuVisible = false;
}
// Initialize the timer
gameTimer = TickTimer.CreateFromSeconds(Runner, gameDuration);
gameEnded = false;
StartCoroutine(InitialSpawn());
Debug.Log("----------------------GameStart------------------------");
//gameStartCanvas.SetActive(false);
// Reset timer color
timerText.color = Color.white;
if (Object.HasStateAuthority)
{
players = Runner.ActivePlayers.ToArray();
}
}
}
}