using System; using Unity.Netcode; using UnityEngine; namespace Northbound { /// /// 전역 타이머 - 주기적으로 반복되며 모든 클라이언트에 동기화 /// public class GlobalTimer : NetworkBehaviour { public static GlobalTimer Instance { get; private set; } [Header("Timer Settings")] public float cycleLength = 60f; // 한 주기 길이 (초) public bool autoStart = true; public bool pauseOnZero = false; // 0에 도달하면 일시정지 [Header("Debug")] public bool showDebugLogs = false; // 현재 타이머 값 (초) private NetworkVariable _currentTime = new NetworkVariable( 0f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); // 타이머 실행 중 여부 private NetworkVariable _isRunning = new NetworkVariable( false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); // 현재 사이클 번호 private NetworkVariable _cycleCount = new NetworkVariable( 0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); // 이벤트 public event Action OnTimerTick; // 매 프레임 업데이트 public event Action OnCycleComplete; // 사이클 완료 public event Action OnCycleStart; // 새 사이클 시작 public event Action OnHalfwayPoint; // 사이클 중간 지점 private bool _hasReachedHalfway; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; } public override void OnNetworkSpawn() { base.OnNetworkSpawn(); if (IsServer && autoStart) { StartTimer(); } // 클라이언트도 이벤트 수신을 위해 변경 감지 _currentTime.OnValueChanged += OnCurrentTimeChanged; } public override void OnNetworkDespawn() { _currentTime.OnValueChanged -= OnCurrentTimeChanged; } private void Update() { if (!IsServer || !_isRunning.Value) return; _currentTime.Value -= Time.deltaTime; // 중간 지점 체크 if (!_hasReachedHalfway && _currentTime.Value <= cycleLength / 2f) { _hasReachedHalfway = true; OnHalfwayPoint?.Invoke(cycleLength / 2f); NotifyHalfwayClientRpc(); if (showDebugLogs) Debug.Log($"[GlobalTimer] 사이클 중간 지점 도달"); } // 사이클 완료 if (_currentTime.Value <= 0f) { CompleteCycle(); } OnTimerTick?.Invoke(_currentTime.Value); } private void CompleteCycle() { OnCycleComplete?.Invoke(); NotifyCycleCompleteClientRpc(); if (showDebugLogs) Debug.Log($"[GlobalTimer] 사이클 {_cycleCount.Value} 완료"); if (pauseOnZero) { _isRunning.Value = false; _currentTime.Value = 0f; } else { // 다음 사이클 시작 _currentTime.Value = cycleLength; _cycleCount.Value++; _hasReachedHalfway = false; OnCycleStart?.Invoke(_cycleCount.Value); NotifyCycleStartClientRpc(_cycleCount.Value); if (showDebugLogs) Debug.Log($"[GlobalTimer] 사이클 {_cycleCount.Value} 시작"); } } private void OnCurrentTimeChanged(float previousValue, float newValue) { // 클라이언트에서도 Tick 이벤트 발생 if (!IsServer) { OnTimerTick?.Invoke(newValue); } } #region Public API /// /// 타이머 시작 (서버만) /// public void StartTimer() { if (!IsServer) return; _currentTime.Value = cycleLength; _isRunning.Value = true; _cycleCount.Value = 1; _hasReachedHalfway = false; OnCycleStart?.Invoke(_cycleCount.Value); NotifyCycleStartClientRpc(_cycleCount.Value); if (showDebugLogs) Debug.Log($"[GlobalTimer] 타이머 시작: {cycleLength}초"); } /// /// 타이머 일시정지 (서버만) /// public void PauseTimer() { if (!IsServer) return; _isRunning.Value = false; if (showDebugLogs) Debug.Log($"[GlobalTimer] 타이머 일시정지"); } /// /// 타이머 재개 (서버만) /// public void ResumeTimer() { if (!IsServer) return; _isRunning.Value = true; if (showDebugLogs) Debug.Log($"[GlobalTimer] 타이머 재개"); } /// /// 타이머 리셋 (서버만) /// public void ResetTimer() { if (!IsServer) return; _currentTime.Value = cycleLength; _cycleCount.Value = 0; _isRunning.Value = false; _hasReachedHalfway = false; if (showDebugLogs) Debug.Log($"[GlobalTimer] 타이머 리셋"); } /// /// 현재 남은 시간 (초) /// public float GetCurrentTime() => _currentTime.Value; /// /// 현재 남은 시간 (분:초 형식) /// public string GetFormattedTime() { int minutes = Mathf.FloorToInt(_currentTime.Value / 60f); int seconds = Mathf.FloorToInt(_currentTime.Value % 60f); return $"{minutes:00}:{seconds:00}"; } /// /// 진행률 (0.0 ~ 1.0) /// public float GetProgress() => 1f - (_currentTime.Value / cycleLength); /// /// 현재 사이클 번호 /// public int GetCycleCount() => _cycleCount.Value; /// /// 타이머 실행 중 여부 /// public bool IsRunning() => _isRunning.Value; #endregion #region Client RPCs [ClientRpc] private void NotifyCycleCompleteClientRpc() { if (!IsServer) { OnCycleComplete?.Invoke(); } } [ClientRpc] private void NotifyCycleStartClientRpc(int cycleNumber) { if (!IsServer) { OnCycleStart?.Invoke(cycleNumber); } } [ClientRpc] private void NotifyHalfwayClientRpc() { if (!IsServer) { OnHalfwayPoint?.Invoke(cycleLength / 2f); } } #endregion } }