- 이상 상태 데이터 (버프/디버프) ScriptableObject 정의 - 런타임임 활성 이상 상태 관리 (ActiveAbnormality) - 캐릭터터별 AbnormalityManager 컴포넌트로 이상 상태 적용/제거 - 스킬 효과(AbnormalityEffect)로 스킬에 이상 상태 연동 - UI 슬롯 및 목록 표시 구현 (버프/디버프 구분) - 테스트 코드 및 씬 설정 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
280 lines
8.3 KiB
C#
280 lines
8.3 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()
|
|
{
|
|
// 주기적으로 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) && 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)
|
|
{
|
|
Debug.LogWarning("[AbnormalityListUI] Slot prefab is not assigned");
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 슬롯 숨기기
|
|
/// </summary>
|
|
public void HideAll()
|
|
{
|
|
foreach (var slot in activeSlots.ToArray())
|
|
{
|
|
ReturnSlot(slot);
|
|
}
|
|
|
|
activeSlots.Clear();
|
|
}
|
|
}
|
|
}
|