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에 도달하면 일시정지
public float exceptionalFirstCycleLength = 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 bool _isFirstTime = true;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
_isFirstTime = exceptionalFirstCycleLength > 0 ? true : false;
}
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
if (IsOwner && autoStart)
{
StartTimer();
}
// 클라이언트도 이벤트 수신을 위해 변경 감지
_currentTime.OnValueChanged += OnCurrentTimeChanged;
}
public override void OnNetworkDespawn()
{
_currentTime.OnValueChanged -= OnCurrentTimeChanged;
}
private void Update()
{
if (!IsOwner || !_isRunning.Value)
return;
_currentTime.Value -= Time.deltaTime;
if (_isFirstTime)
{
if (_currentTime.Value + exceptionalFirstCycleLength < cycleLength)
{
_isFirstTime = false;
CompleteCycle();
}
}
else
{
// 중간 지점 체크
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 (!IsOwner)
{
OnTimerTick?.Invoke(newValue);
}
}
#region Public API
///
/// 타이머 시작 (서버만)
///
public void StartTimer()
{
if (!IsOwner) 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 (!IsOwner) return;
_isRunning.Value = false;
if (showDebugLogs)
Debug.Log($"[GlobalTimer] 타이머 일시정지");
}
///
/// 타이머 재개 (서버만)
///
public void ResumeTimer()
{
if (!IsOwner) return;
_isRunning.Value = true;
if (showDebugLogs)
Debug.Log($"[GlobalTimer] 타이머 재개");
}
///
/// 타이머 리셋 (서버만)
///
public void ResetTimer()
{
if (!IsOwner) 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 (!IsOwner)
{
OnCycleComplete?.Invoke();
}
}
[ClientRpc]
private void NotifyCycleStartClientRpc(int cycleNumber)
{
if (!IsOwner)
{
OnCycleStart?.Invoke(cycleNumber);
}
}
[ClientRpc]
private void NotifyHalfwayClientRpc()
{
if (!IsOwner)
{
OnHalfwayPoint?.Invoke(cycleLength / 2f);
}
}
#endregion
}
}