Files
soccar2d/Assets/Scripts/GameManager.cs
T
2026-03-29 02:27:55 +05:30

508 lines
14 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using TMPro;
using Mirror;
public class GameManager : NetworkBehaviour
{
public static Action<Team> OnTeamChanged;
public static bool isMoving{
get{
return instance.IsMoving();
}
}
[SyncVar]
public bool m_isMoving =false;
[SyncVar(hook = nameof(OnGameStartedChanged))]
public bool gameStarted = false;
void OnGameStartedChanged(bool oldStarted, bool newStarted){
if(newStarted){
GameCanvas.instance.HideWaitingForOpponentPanel();
if (AudioManager.instance != null)
AudioManager.instance.SetCrowdNoiseType(CrowdNoiseType.Normal);
}else{
GameCanvas.instance.ShowWaitingForOpponentPanel();
}
}
[SyncVar(hook = nameof(OnSelectedTeamChanged))]
public Team SelectedTeam = Team.Red;
void OnSelectedTeamChanged(Team oldTeam, Team newTeam){
Debug.Log($"Selected team changed from {oldTeam} to {newTeam}");
OnTeamChanged?.Invoke(newTeam);
if (!isClient || !gameStarted || oldTeam == newTeam)
return;
if (AudioManager.instance != null)
AudioManager.instance.PlayRefereeWhistle();
}
public static bool NoPuckSelected {
get{
return instance.selectedPuck == null;
}
}
public int turnTimer = 10;
[SyncVar]
public float turnTimerCounter;
public Rigidbody2D ball;
public float puckForce = 20f;
public AnimationCurve puckForceCurve = AnimationCurve.Linear(0,3,1,1);
public float ballMoveTime = 3f;
public float puckDragClampMax = 5f;
public float puckDragMinClamp = 1f;
[SyncVar(hook = nameof(OnScoreChanged))]
public int redScore=0;
[SyncVar(hook = nameof(OnScoreChanged))]
public int blueScore=0;
[Header("UI")]
public TMP_Text redScoreText;
public TMP_Text blueScoreText;
public GameObject gameOverPanel;
public GameObject blueWon,redWon;
public CanvasGroupUtils blueTimerGroup;
[FormerlySerializedAs("timerGroup")] public CanvasGroupUtils redTimerGroup;
[Header("Timer Flash Settings")]
[Tooltip("Minimum flash speed when timer is at 5 seconds")]
public float minFlashSpeed = 10f;
[Tooltip("Maximum flash speed when timer is at 0 seconds")]
public float maxFlashSpeed = 20f;
[Header("Misc")]
public List<Puck> pucks = new List<Puck>();
public float curPuckPullForce;
[SyncVar]
public int hitCounter = 0;
public static GameManager instance;
public float puckMass{
get{
if(pucks.Count > 0){return pucks[0].rb.mass;}else{return 1f;}
}set{
foreach(Puck puck in pucks){
puck.rb.mass = value;
}
}
}
public float puckDrag{
get{
if(pucks.Count > 0){return pucks[0].rb.linearDamping;}else{return 0f;}
}set{
foreach(Puck puck in pucks){
puck.rb.linearDamping = value;
}
}
}
void Awake()
{
instance = this;
}
void Start()
{
GameEvents.OnSelectedPuckChanged?.Invoke(null);
#if UNITY_EDITOR
#else
Application.targetFrameRate = isServer ? 30 : 100;
#endif
}
public void RegisterPuck(Puck puck){
pucks.Add(puck);
}
public void DisposePuck(Puck puck){
pucks.Remove(puck);
}
[SyncVar(hook = nameof(OnSelectedPuckChanged))]
[SerializeField]Puck selectedPuck;
void OnSelectedPuckChanged(Puck oldPuck, Puck newPuck){
// Debug.Log($"Selected puck changed from {oldPuck} to {newPuck}");
GameEvents.OnSelectedPuckChanged?.Invoke(newPuck);
}
public static Puck SelectedPuck{
get{
return instance.selectedPuck;
}
set{
instance.SetSelectedPuck(value);
}
}
void SetSelectedPuck(Puck puck)
{
selectedPuck = puck;
GameEvents.OnSelectedPuckChanged?.Invoke(puck);
}
public Puck GetClosestPuck(Vector2 position){
if(IsMoving()){return null;} //Do not do anything if shits moving
// Convert screen position to world position
// Vector3 worldPosition = Camera.main.ScreenToWorldPoint(new Vector3(position.x, position.y, Camera.main.nearClipPlane));
// Find all pucks in the scene
Puck[] pucks = FindObjectsOfType<Puck>();
float minDistance = float.MaxValue;
Puck closestPuck = null;
foreach (Puck puck in pucks)
{
if(puck.team != SelectedTeam){continue;}
float dist = Vector2.Distance(position, puck.transform.position);
if (dist < minDistance)
{
minDistance = dist;
closestPuck = puck;
}
}
return closestPuck;
}
Coroutine coroutinePostLaunch;
public void OnPointerUp(Vector2 direction)
{
if(direction.magnitude < puckDragMinClamp){
selectedPuck = null;
GameEvents.OnSelectedPuckChanged?.Invoke(null);
return;
}
if(coroutinePostLaunch != null){
StopCoroutine(coroutinePostLaunch);
}
coroutinePostLaunch = StartCoroutine(CoroutinePostLaunch());
curPuckPullForce = direction.magnitude;
float force = puckForce * puckForceCurve.Evaluate(curPuckPullForce);
Debug.Log($"force = {puckForce} * {puckForceCurve.Evaluate(curPuckPullForce)} = {force}");
Debug.Log($"launching puck at {direction} with {force * -direction} force");
selectedPuck.GetComponent<Rigidbody2D>().AddForce(-direction * force, ForceMode2D.Impulse);
hitCounter++;
selectedPuck = null;
GameEvents.OnSelectedPuckChanged?.Invoke(null);
SwitchTeams();
}
void SwitchTeams(){
if(!isServer){
Debug.LogWarning("SwitchTeams called on client, skipping");
return;
}
StartCoroutine(CoroutineSwitchTeams());
}
bool switchingTeams=false;
IEnumerator CoroutineSwitchTeams(){
switchingTeams=true;
for(int i=0; i < 3; i++){
yield return null;
}
while(m_isMoving){
yield return null;
}
turnTimerCounter = 0;
SelectedTeam = SelectedTeam == Team.Red ? Team.Blue : Team.Red;
OnTeamChanged?.Invoke(SelectedTeam);
switchingTeams=false;
}
IEnumerator CoroutinePostLaunch(){
float t = 0;
while ( t < 1){
t += Time.deltaTime / ballMoveTime;
ball.linearDamping = Mathf.Lerp(0, 10, t);
yield return null;
}
}
bool IsMoving(){
if(freezeInput){return true;}
float minMagnitude = 0.01f;
foreach(Puck puck in pucks){
if(puck.rb.linearVelocity.magnitude > minMagnitude){
return true;
}
}
if(ball.linearVelocity.magnitude > minMagnitude){
return true;
}
return false;
}
void Update()
{
if(isServer){
m_isMoving=IsMoving();
HandleTurnTimer();
if(!gameStarted){
NetPlayer[] players = FindObjectsOfType<NetPlayer>();
#if UNITY_EDITOR
if(Input.GetKeyDown(KeyCode.Space)){
gameStarted = true;
GameCanvas.instance.HideWaitingForOpponentPanel();
Debug.Log("Game started On Server, EDITOR ONLY, DEBUG ONLY");
}
#endif
if (players.Length == 2)
{
gameStarted = true;
GameCanvas.instance.HideWaitingForOpponentPanel();
Debug.Log("Game started On Server");
}
}
}
UpdateLocalTurnTimerBeep();
UpdateTimerColor();
}
void UpdateLocalTurnTimerBeep()
{
if (!isClient || NetPlayer.localPlayer == null)
return;
if (!gameStarted || SelectedTeam != NetPlayer.localPlayer.myTeam)
{
_lastTurnTimerBeepFloor = -1;
return;
}
if (m_isMoving || turnTimerCounter >= turnTimer)
return;
float remaining = turnTimer - turnTimerCounter;
int floorSec = Mathf.FloorToInt(remaining);
if (floorSec != _lastTurnTimerBeepFloor)
{
if (_lastTurnTimerBeepFloor >= 0 && floorSec < _lastTurnTimerBeepFloor
&& AudioManager.instance != null)
AudioManager.instance.PlayTimerBeep();
_lastTurnTimerBeepFloor = floorSec;
}
}
void HandleTurnTimer(){
if(turnTimerCounter < turnTimer){
if(!isMoving && !switchingTeams && gameStarted){
turnTimerCounter += Time.deltaTime;
}
}else{
SwitchTeams();
turnTimerCounter = 0;
}
}
float flashPhase = 0f;
float lastUpdateTime = 0f;
float lastRemainingTime = 10f;
int _lastTurnTimerBeepFloor = -1;
void UpdateTimerColor(){
float remainingTime = turnTimer - turnTimerCounter;
// Detect timer reset: if remainingTime jumped from low to high, reset phase
if(remainingTime > lastRemainingTime + 2f){
flashPhase = 0f;
lastUpdateTime = 0f;
}
lastRemainingTime = remainingTime;
CanvasGroupUtils activeTimerGroup = SelectedTeam == Team.Red ? redTimerGroup : blueTimerGroup;
CanvasGroupUtils inactiveTimerGroup = SelectedTeam == Team.Red ? blueTimerGroup : redTimerGroup;
if(inactiveTimerGroup != null){
inactiveTimerGroup.overrideColor = Color.white;
}
if(activeTimerGroup == null){
if(remainingTime >= 5f || !gameStarted){
flashPhase = 0f;
lastUpdateTime = 0f;
}
return;
}
if(remainingTime < 5f && gameStarted){
// Calculate intensity based on how close timer is to 0
// Intensity increases from 0 to 1 as timer goes from 5 to 0
float intensity = 1f - (remainingTime / 5f);
// Create flashing effect using sine wave
// Flash speed increases as timer approaches 0
float flashSpeed = Mathf.Lerp(minFlashSpeed, maxFlashSpeed, intensity);
// Update phase based on delta time and current flash speed
// This ensures smooth flashing that resets with the timer
if(lastUpdateTime > 0f){
float deltaTime = Time.time - lastUpdateTime;
flashPhase += flashSpeed * deltaTime;
} else {
// First frame in warning zone, initialize phase
flashPhase = 0f;
}
lastUpdateTime = Time.time;
float flashValue = (Mathf.Sin(flashPhase) + 1f) * 0.5f; // 0 to 1
// Combine intensity with flash for final red intensity
// As timer approaches 0, intensity increases and flash becomes more prominent
float finalRedIntensity = intensity * (0.3f + flashValue * 0.7f); // Range: intensity*0.3 to intensity*1
// Lerp between white and red based on final intensity, keeping alpha at 1
Color white = Color.white;
Color red = Color.red;
activeTimerGroup.overrideColor = Color.Lerp(white, red, finalRedIntensity);
}else{
// Reset phase when timer is above 5 seconds or game hasn't started
flashPhase = 0f;
lastUpdateTime = 0f;
activeTimerGroup.overrideColor = Color.white;
}
}
public void OnGoal(Team team){
if(!isServer){
Debug.LogWarning("OnGoal called on client, skipping");
return;}
RpcPlayGoalScoredSfx();
freezeInput=true;
if(hitCounter == 1){
//kickoff goal
Debug.Log("Kickoff goal");
StartCoroutine(CoroutineOnGoal(team,true));//true = Kickoff goal, reset only the ball
hitCounter = 0;
return;
}
if(team == Team.Blue){
blueScore++;
blueScoreText.text = blueScore.ToString();
}else{
redScore++;
redScoreText.text = redScore.ToString();
}
SwitchTeams();
if(blueScore >= 3){
RpcGameOver(Team.Blue);
gameOver(Team.Blue);
}else if(redScore >= 3){
RpcGameOver(Team.Red);
gameOver(Team.Red);
}else{
StartCoroutine(CoroutineOnGoal(team));
}
hitCounter = 0;
}
[ClientRpc]
void RpcPlayGoalScoredSfx()
{
if (AudioManager.instance != null)
AudioManager.instance.PlayGoalScoredSfxSequence();
}
[ClientRpc]
void RpcGameOver(Team team){
gameOver(team);
}
void gameOver(Team team){
gameOverPanel.SetActive(true);
if(team == Team.Blue){
blueWon.SetActive(true);
redWon.SetActive(false);
}else{
blueWon.SetActive(false);
redWon.SetActive(true);
}
}
bool freezeInput = false;
IEnumerator CoroutineOnGoal(Team team, bool kickoff = false){
if(coroutinePostLaunch!=null){
StopCoroutine(coroutinePostLaunch);
}
float t=0;
while(t < 1){
t +=Time.deltaTime * 2f;
ball.linearDamping = Mathf.Lerp(0, 100, t);
yield return null;
}
yield return new WaitForSeconds(2f);
Reset();
}
void OnScoreChanged(int oldScore, int newScore){
Debug.Log($"Score changed from {oldScore} to {newScore}");
redScoreText.text = redScore.ToString();
blueScoreText.text = blueScore.ToString();
}
public void Reset(bool kickoff = false)
{
StartCoroutine(CoroutineReset(kickoff));
}
IEnumerator CoroutineReset(bool kickoff = false){
float resetDuration = 0.5f;
if(!kickoff){
foreach(Puck puck in pucks){
puck.Reset(null, resetDuration);
}
}
ball.GetComponent<Ball>().Reset(resetDuration);
yield return new WaitForSeconds(resetDuration);
freezeInput=false;
}
}