From 9a47af431757f8b6fb5c5557a93d18693aad7f18 Mon Sep 17 00:00:00 2001 From: dal4segno Date: Tue, 27 Jan 2026 13:14:43 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9B=A8=EC=9D=B4=EB=B8=8C=20=EC=86=8C?= =?UTF-8?q?=ED=99=98=EC=9A=A9=20=EA=B8=80=EB=A1=9C=EB=B2=8C=20=ED=83=80?= =?UTF-8?q?=EC=9D=B4=EB=A8=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scripts/GlobalTimer.cs | 269 +++++++++++++++++++++++++++++ Assets/Scripts/GlobalTimer.cs.meta | 2 + 2 files changed, 271 insertions(+) create mode 100644 Assets/Scripts/GlobalTimer.cs create mode 100644 Assets/Scripts/GlobalTimer.cs.meta diff --git a/Assets/Scripts/GlobalTimer.cs b/Assets/Scripts/GlobalTimer.cs new file mode 100644 index 0000000..f575666 --- /dev/null +++ b/Assets/Scripts/GlobalTimer.cs @@ -0,0 +1,269 @@ +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 + } +} \ No newline at end of file diff --git a/Assets/Scripts/GlobalTimer.cs.meta b/Assets/Scripts/GlobalTimer.cs.meta new file mode 100644 index 0000000..b1ee10d --- /dev/null +++ b/Assets/Scripts/GlobalTimer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b643f7c446ade3f448e57e7501ac5a67 \ No newline at end of file