Files
Colosseum/Assets/_Game/Scripts/Player/PlayerSpectator.cs
dal4segno c265f980db chore: Assets 디렉토리 구조 정리 및 네이밍 컨벤션 적용
- Assets/_Game/ 하위로 게임 에셋 통합
- External/ 패키지 벤더별 분류 (Synty, Animations, UI)
- 에셋 네이밍 컨벤션 확립 및 적용
  (Data_Skill_, Data_SkillEffect_, Prefab_, Anim_, Model_, BT_ 등)
- pre-commit hook으로 네이밍 컨벤션 자동 검사 추가
- RESTRUCTURE_CHECKLIST.md 작성

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 19:08:27 +09:00

196 lines
5.9 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using Colosseum.Player;
namespace Colosseum.Player
{
/// <summary>
/// 플레이어 관전 시스템.
/// 사망한 플레이어가 살아있는 플레이어를 관찰할 수 있게 합니다.
/// </summary>
public class PlayerSpectator : MonoBehaviour
{
[Header("References")]
[Tooltip("PlayerCamera 컴포넌트")]
[SerializeField] private PlayerCamera playerCamera;
[Tooltip("PlayerNetworkController 컴포넌트")]
[SerializeField] private PlayerNetworkController networkController;
[Header("Spectate Settings")]
[Tooltip("관전 대상 전환 키")]
[SerializeField] private KeyCode nextTargetKey = KeyCode.Tab;
[Tooltip("관전 UI 표시 여부")]
[SerializeField] private bool showSpectateUI = true;
// 관전 상태
private bool isSpectating = false;
private int currentSpectateIndex = 0;
private List<PlayerNetworkController> alivePlayers = new List<PlayerNetworkController>();
// 이벤트
public event System.Action<bool> OnSpectateModeChanged; // (isSpectating)
public event System.Action<PlayerNetworkController> OnSpectateTargetChanged;
// Properties
public bool IsSpectating => isSpectating;
public PlayerNetworkController CurrentTarget => alivePlayers.Count > currentSpectateIndex ? alivePlayers[currentSpectateIndex] : null;
private void Awake()
{
// 컴포넌트 자동 참조
if (playerCamera == null)
playerCamera = GetComponent<PlayerCamera>();
if (networkController == null)
networkController = GetComponentInParent<PlayerNetworkController>();
}
private void Start()
{
if (networkController != null)
{
networkController.OnDeathStateChanged += HandleDeathStateChanged;
}
}
private void OnDestroy()
{
if (networkController != null)
{
networkController.OnDeathStateChanged -= HandleDeathStateChanged;
}
}
private void Update()
{
if (!isSpectating) return;
// Tab 키로 다음 관전 대상 전환
if (Input.GetKeyDown(nextTargetKey))
{
CycleToNextTarget();
}
}
private void HandleDeathStateChanged(bool dead)
{
if (dead)
{
StartSpectating();
}
else
{
StopSpectating();
}
}
/// <summary>
/// 관전 모드 시작
/// </summary>
private void StartSpectating()
{
// 살아있는 플레이어 목록 갱신
RefreshAlivePlayers();
if (alivePlayers.Count == 0)
{
// 관전할 플레이어가 없음 (게임 오버)
Debug.Log("[PlayerSpectator] No alive players to spectate");
return;
}
isSpectating = true;
currentSpectateIndex = 0;
// 첫 번째 살아있는 플레이어 관전
SetSpectateTarget(alivePlayers[currentSpectateIndex]);
OnSpectateModeChanged?.Invoke(true);
Debug.Log($"[PlayerSpectator] Started spectating. {alivePlayers.Count} players alive.");
}
/// <summary>
/// 관전 모드 종료
/// </summary>
private void StopSpectating()
{
isSpectating = false;
alivePlayers.Clear();
currentSpectateIndex = 0;
// 원래 플레이어로 카메라 복귀
if (playerCamera != null && networkController != null)
{
playerCamera.ResetToPlayer(networkController.transform);
}
OnSpectateModeChanged?.Invoke(false);
Debug.Log("[PlayerSpectator] Stopped spectating");
}
/// <summary>
/// 살아있는 플레이어 목록 갱신
/// </summary>
private void RefreshAlivePlayers()
{
alivePlayers.Clear();
var allPlayers = FindObjectsByType<PlayerNetworkController>(FindObjectsSortMode.None);
foreach (var player in allPlayers)
{
// 자신이 아니고, 살아있는 플레이어만 추가
if (player != networkController && !player.IsDead)
{
alivePlayers.Add(player);
}
}
}
/// <summary>
/// 다음 관전 대상으로 전환
/// </summary>
private void CycleToNextTarget()
{
if (alivePlayers.Count == 0) return;
// 목록 갱신 (중간에 사망했을 수 있음)
RefreshAlivePlayers();
if (alivePlayers.Count == 0)
{
Debug.Log("[PlayerSpectator] No more alive players");
return;
}
currentSpectateIndex = (currentSpectateIndex + 1) % alivePlayers.Count;
SetSpectateTarget(alivePlayers[currentSpectateIndex]);
}
/// <summary>
/// 관전 대상 설정
/// </summary>
private void SetSpectateTarget(PlayerNetworkController target)
{
if (target == null || playerCamera == null) return;
playerCamera.SetTarget(target.transform);
OnSpectateTargetChanged?.Invoke(target);
Debug.Log($"[PlayerSpectator] Now spectating: Player {target.OwnerClientId}");
}
/// <summary>
/// 현재 관전 대상 이름 반환 (UI용)
/// </summary>
public string GetCurrentTargetName()
{
var target = CurrentTarget;
if (target == null) return "None";
return $"Player {target.OwnerClientId}";
}
}
}