284 lines
8.1 KiB
C#
284 lines
8.1 KiB
C#
using System;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
|
|
namespace Northbound
|
|
{
|
|
/// <summary>
|
|
/// 전역 타이머 - 주기적으로 반복되며 모든 클라이언트에 동기화
|
|
/// </summary>
|
|
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<float> _currentTime = new NetworkVariable<float>(
|
|
0f,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server
|
|
);
|
|
|
|
// 타이머 실행 중 여부
|
|
private NetworkVariable<bool> _isRunning = new NetworkVariable<bool>(
|
|
false,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server
|
|
);
|
|
|
|
// 현재 사이클 번호
|
|
private NetworkVariable<int> _cycleCount = new NetworkVariable<int>(
|
|
0,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server
|
|
);
|
|
|
|
// 이벤트
|
|
public event Action<float> OnTimerTick; // 매 프레임 업데이트
|
|
public event Action OnCycleComplete; // 사이클 완료
|
|
public event Action<int> OnCycleStart; // 새 사이클 시작
|
|
public event Action<float> 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($"<color=yellow>[GlobalTimer] 사이클 중간 지점 도달</color>");
|
|
}
|
|
|
|
// 사이클 완료
|
|
if (_currentTime.Value <= 0f)
|
|
{
|
|
CompleteCycle();
|
|
}
|
|
}
|
|
OnTimerTick?.Invoke(_currentTime.Value);
|
|
}
|
|
|
|
private void CompleteCycle()
|
|
{
|
|
OnCycleComplete?.Invoke();
|
|
NotifyCycleCompleteClientRpc();
|
|
|
|
if (showDebugLogs)
|
|
Debug.Log($"<color=green>[GlobalTimer] 사이클 {_cycleCount.Value} 완료</color>");
|
|
|
|
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($"<color=cyan>[GlobalTimer] 사이클 {_cycleCount.Value} 시작</color>");
|
|
}
|
|
}
|
|
|
|
private void OnCurrentTimeChanged(float previousValue, float newValue)
|
|
{
|
|
// 클라이언트에서도 Tick 이벤트 발생
|
|
if (!IsOwner)
|
|
{
|
|
OnTimerTick?.Invoke(newValue);
|
|
}
|
|
}
|
|
|
|
#region Public API
|
|
|
|
/// <summary>
|
|
/// 타이머 시작 (서버만)
|
|
/// </summary>
|
|
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($"<color=green>[GlobalTimer] 타이머 시작: {cycleLength}초</color>");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 타이머 일시정지 (서버만)
|
|
/// </summary>
|
|
public void PauseTimer()
|
|
{
|
|
if (!IsOwner) return;
|
|
|
|
_isRunning.Value = false;
|
|
|
|
if (showDebugLogs)
|
|
Debug.Log($"<color=yellow>[GlobalTimer] 타이머 일시정지</color>");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 타이머 재개 (서버만)
|
|
/// </summary>
|
|
public void ResumeTimer()
|
|
{
|
|
if (!IsOwner) return;
|
|
|
|
_isRunning.Value = true;
|
|
|
|
if (showDebugLogs)
|
|
Debug.Log($"<color=green>[GlobalTimer] 타이머 재개</color>");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 타이머 리셋 (서버만)
|
|
/// </summary>
|
|
public void ResetTimer()
|
|
{
|
|
if (!IsOwner) return;
|
|
|
|
_currentTime.Value = cycleLength;
|
|
_cycleCount.Value = 0;
|
|
_isRunning.Value = false;
|
|
_hasReachedHalfway = false;
|
|
|
|
if (showDebugLogs)
|
|
Debug.Log($"<color=yellow>[GlobalTimer] 타이머 리셋</color>");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 남은 시간 (초)
|
|
/// </summary>
|
|
public float GetCurrentTime() => _currentTime.Value;
|
|
|
|
/// <summary>
|
|
/// 현재 남은 시간 (분:초 형식)
|
|
/// </summary>
|
|
public string GetFormattedTime()
|
|
{
|
|
int minutes = Mathf.FloorToInt(_currentTime.Value / 60f);
|
|
int seconds = Mathf.FloorToInt(_currentTime.Value % 60f);
|
|
return $"{minutes:00}:{seconds:00}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 진행률 (0.0 ~ 1.0)
|
|
/// </summary>
|
|
public float GetProgress() => 1f - (_currentTime.Value / cycleLength);
|
|
|
|
/// <summary>
|
|
/// 현재 사이클 번호
|
|
/// </summary>
|
|
public int GetCycleCount() => _cycleCount.Value;
|
|
|
|
/// <summary>
|
|
/// 타이머 실행 중 여부
|
|
/// </summary>
|
|
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
|
|
}
|
|
} |