Files
Colosseum/Assets/_Game/Scripts/UI/AbnormalityListUI.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

304 lines
9.1 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using Colosseum.Abnormalities;
namespace Colosseum.UI
{
/// <summary>
/// 이상 상태 목록 UI 관리자
/// 버프/디버프 목록을 표시하고 관리합니다.
/// </summary>
public class AbnormalityListUI : MonoBehaviour
{
[Header("Containers")]
[Tooltip("버프 컨테이너")]
[SerializeField] private Transform buffContainer;
[Tooltip("디버프 컨테이너")]
[SerializeField] private Transform debuffContainer;
[Header("Prefab")]
[Tooltip("이상 상태 슬롯 프리팹")]
[SerializeField] private AbnormalitySlotUI slotPrefab;
[Header("Settings")]
[Tooltip("최대 표시 개수")]
[SerializeField] private int maxSlots = 10;
[Tooltip("자동으로 플레이어 추적")]
[SerializeField] private bool autoFindPlayer = true;
// 추적 중인 AbnormalityManager
private AbnormalityManager targetManager;
// 생성된 슬롯 풀
private readonly List<AbnormalitySlotUI> slotPool = new List<AbnormalitySlotUI>();
// 현재 활성화된 슬롯 목록
private readonly List<AbnormalitySlotUI> activeSlots = new List<AbnormalitySlotUI>();
// 이전 프레임의 효과 수 (변경 감지용)
private int lastAbnormalityCount = -1;
private void Start()
{
if (autoFindPlayer)
{
// 로컬 플레이어 찾기
FindLocalPlayer();
}
// 슬롯 풀 초기화
InitializeSlotPool();
}
private void OnDestroy()
{
// 이벤트 구독 해제
if (targetManager != null)
{
targetManager.OnAbnormalityAdded -= OnAbnormalityAdded;
targetManager.OnAbnormalityRemoved -= OnAbnormalityRemoved;
targetManager.OnAbnormalitiesChanged -= OnAbnormalitiesChanged;
}
}
private void Update()
{
// 타겟이 없으면 주기적으로 플레이어 찾기
if (targetManager == null && autoFindPlayer && Time.frameCount % 30 == 0)
{
FindLocalPlayer();
}
// 주기적으로 UI 갱신 (성능 최적화를 위해 매 프레임이 아닌 일정 간격으로)
if (Time.frameCount % 10 == 0) // 10프레임마다 한 번
{
RefreshUI();
}
}
/// <summary>
/// 로컬 플레이어 찾기
/// </summary>
private void FindLocalPlayer()
{
var playerObjects = FindObjectsByType<AbnormalityManager>(FindObjectsSortMode.None);
foreach (var manager in playerObjects)
{
// 네트워크 오브젝트인 경우 로컬 플레이어 확인
if (manager.TryGetComponent<Unity.Netcode.NetworkObject>(out var netObj))
{
if (netObj.IsOwner)
{
SetTarget(manager);
return;
}
}
}
// 네트워크 오브젝트가 없거나 로컬 플레이어를 찾지 못한 경우
// 첫 번째 플레이어 사용 (싱글플레이어용)
if (playerObjects.Length > 0)
{
SetTarget(playerObjects[0]);
}
}
/// <summary>
/// 추적 대상 설정
/// </summary>
/// <param name="manager">추적할 AbnormalityManager</param>
public void SetTarget(AbnormalityManager manager)
{
// 기존 구독 해제
if (targetManager != null)
{
targetManager.OnAbnormalityAdded -= OnAbnormalityAdded;
targetManager.OnAbnormalityRemoved -= OnAbnormalityRemoved;
targetManager.OnAbnormalitiesChanged -= OnAbnormalitiesChanged;
}
targetManager = manager;
// 새로운 대상 구독
if (targetManager != null)
{
targetManager.OnAbnormalityAdded += OnAbnormalityAdded;
targetManager.OnAbnormalityRemoved += OnAbnormalityRemoved;
targetManager.OnAbnormalitiesChanged += OnAbnormalitiesChanged;
}
// 즉시 UI 갱신
ForceRefreshUI();
}
/// <summary>
/// 슬롯 풀 초기화
/// </summary>
private void InitializeSlotPool()
{
if (slotPrefab == null) return;
// 필요한 만큼 슬롯 미리 생성
for (int i = 0; i < maxSlots; i++)
{
var slot = CreateSlot();
slot.gameObject.SetActive(false);
slotPool.Add(slot);
}
}
/// <summary>
/// 새 슬롯 생성
/// </summary>
private AbnormalitySlotUI CreateSlot()
{
var go = Instantiate(slotPrefab.gameObject, transform);
return go.GetComponent<AbnormalitySlotUI>();
}
/// <summary>
/// 슬롯 가져오기 (풀에서 또는 새로 생성)
/// </summary>
private AbnormalitySlotUI GetSlot()
{
// 풀에서 비활성화된 슬롯 찾기
foreach (var slot in slotPool)
{
if (!slot.gameObject.activeSelf)
{
return slot;
}
}
// 풀에 없으면 새로 생성
if (slotPool.Count < maxSlots)
{
var newSlot = CreateSlot();
slotPool.Add(newSlot);
return newSlot;
}
return null;
}
/// <summary>
/// 슬롯 반환 (비활성화)
/// </summary>
private void ReturnSlot(AbnormalitySlotUI slot)
{
slot.gameObject.SetActive(false);
activeSlots.Remove(slot);
}
/// <summary>
/// 이상 상태 추가 시 호출
/// </summary>
private void OnAbnormalityAdded(ActiveAbnormality abnormality)
{
ForceRefreshUI();
}
/// <summary>
/// 이상 상태 제거 시 호출
/// </summary>
private void OnAbnormalityRemoved(ActiveAbnormality abnormality)
{
ForceRefreshUI();
}
/// <summary>
/// 이상 상태 변경 시 호출
/// </summary>
private void OnAbnormalitiesChanged()
{
ForceRefreshUI();
}
/// <summary>
/// UI 강제 갱신
/// </summary>
public void ForceRefreshUI()
{
if (targetManager == null) return;
// 모든 슬롯 반환
foreach (var slot in activeSlots.ToArray())
{
ReturnSlot(slot);
}
activeSlots.Clear();
// 활성화된 이상 상태 표시
var abnormalities = targetManager.ActiveAbnormalities;
foreach (var abnormality in abnormalities)
{
var slot = GetSlot();
if (slot == null) continue;
// 버프/디버프에 따라 적절한 컨테이너에 배치
Transform container = abnormality.Data.isDebuff ? debuffContainer : buffContainer;
if (container == null) container = transform;
slot.transform.SetParent(container, false);
slot.Initialize(abnormality);
slot.gameObject.SetActive(true);
activeSlots.Add(slot);
}
lastAbnormalityCount = abnormalities.Count;
}
/// <summary>
/// UI 주기적 갱신 (변경 감지 시에만)
/// </summary>
private void RefreshUI()
{
if (targetManager == null) return;
int currentCount = targetManager.ActiveAbnormalities.Count;
// 이상 상태 수가 변경되었으면 갱신
if (currentCount != lastAbnormalityCount)
{
ForceRefreshUI();
}
// 활성 슬롯들의 지속 시간 업데이트
UpdateSlotDurations();
}
/// <summary>
/// 모든 활성 슬롯의 지속 시간 업데이트
/// </summary>
private void UpdateSlotDurations()
{
foreach (var slot in activeSlots)
{
if (slot != null && slot.TrackedAbnormality != null)
{
var abnormality = slot.TrackedAbnormality;
slot.UpdateDisplay(abnormality.RemainingDuration, abnormality.Data.duration);
}
}
}
/// <summary>
/// 모든 슬롯 숨기기
/// </summary>
public void HideAll()
{
foreach (var slot in activeSlots.ToArray())
{
ReturnSlot(slot);
}
activeSlots.Clear();
}
}
}