- PlayerActionState로 이동, 점프, 스킬 입력 가능 여부를 통합 - 기절과 침묵 이상상태 데이터 및 효과 에셋을 추가 - 로컬 플레이어용 이상상태 디버그 HUD와 자동 검증 러너를 추가 - 플레이어 이동, 스킬 입력, 네트워크 상태를 새 행동 상태 기준으로 정리
443 lines
14 KiB
C#
443 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
using Unity.Netcode;
|
|
|
|
using Colosseum.Abnormalities;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace Colosseum.Player
|
|
{
|
|
/// <summary>
|
|
/// 로컬 플레이어가 자신에게 이상상태를 적용/해제할 수 있는 디버그 HUD.
|
|
/// 이상상태 이름, 에셋 이름, 인덱스로 검색해 적용할 수 있습니다.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public class PlayerAbnormalityDebugHUD : NetworkBehaviour
|
|
{
|
|
[Header("References")]
|
|
[Tooltip("이상상태 관리자")]
|
|
[SerializeField] private AbnormalityManager abnormalityManager;
|
|
|
|
[Tooltip("플레이어 네트워크 상태")]
|
|
[SerializeField] private PlayerNetworkController networkController;
|
|
|
|
[Header("Display")]
|
|
[Tooltip("시작 시 HUD 표시 여부")]
|
|
[SerializeField] private bool showOnStart = false;
|
|
|
|
[Tooltip("로그 출력 여부")]
|
|
[SerializeField] private bool debugLogs = true;
|
|
|
|
[Header("Catalog")]
|
|
[Tooltip("디버그 HUD에서 검색 가능한 이상상태 목록")]
|
|
[SerializeField] private List<AbnormalityData> abnormalityCatalog = new List<AbnormalityData>();
|
|
|
|
private Rect windowRect = new Rect(20f, 20f, 420f, 520f);
|
|
private Vector2 catalogScroll;
|
|
private string abnormalityInput = string.Empty;
|
|
private string statusMessage = "이상상태 이름, 에셋 이름, 인덱스를 입력하세요.";
|
|
private bool isVisible;
|
|
private InputSystem_Actions inputActions;
|
|
|
|
private void Awake()
|
|
{
|
|
if (abnormalityManager == null)
|
|
abnormalityManager = GetComponent<AbnormalityManager>();
|
|
|
|
if (networkController == null)
|
|
networkController = GetComponent<PlayerNetworkController>();
|
|
}
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
if (!IsOwner || !ShouldEnableDebugHud())
|
|
{
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
isVisible = showOnStart;
|
|
RefreshCatalog();
|
|
InitializeInputActions();
|
|
|
|
if (debugLogs)
|
|
{
|
|
Debug.Log("[AbnormalityDebugHUD] DebugHUD 액션으로 HUD를 열고, 이상상태 이름/에셋명/인덱스로 자신에게 적용할 수 있습니다.");
|
|
}
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
CleanupInputActions();
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
if (!IsOwner || !isVisible || !ShouldEnableDebugHud())
|
|
return;
|
|
|
|
windowRect = GUI.Window(GetInstanceID(), windowRect, DrawWindow, "Abnormality Debug HUD");
|
|
}
|
|
|
|
private void DrawWindow(int windowId)
|
|
{
|
|
GUILayout.BeginVertical();
|
|
|
|
GUILayout.Label($"사망 상태: {(networkController != null && networkController.IsDead ? "Dead" : "Alive")}");
|
|
GUILayout.Label("입력 예시: 기절 / Data_Abnormality_Player_Stun / 0");
|
|
|
|
GUI.SetNextControlName("AbnormalityInputField");
|
|
abnormalityInput = GUILayout.TextField(abnormalityInput ?? string.Empty);
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("적용", GUILayout.Height(28f)))
|
|
{
|
|
ApplyFromInput();
|
|
}
|
|
|
|
if (GUILayout.Button("해제", GUILayout.Height(28f)))
|
|
{
|
|
RemoveFromInput();
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("모두 해제", GUILayout.Height(24f)))
|
|
{
|
|
if (abnormalityManager != null)
|
|
{
|
|
abnormalityManager.RemoveAllAbnormalities();
|
|
SetStatus("활성 이상상태를 모두 해제했습니다.");
|
|
}
|
|
}
|
|
|
|
if (GUILayout.Button("즉사", GUILayout.Height(24f)))
|
|
{
|
|
KillSelf();
|
|
}
|
|
|
|
if (GUILayout.Button("리스폰", GUILayout.Height(24f)))
|
|
{
|
|
RequestRespawnRpc();
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.Space(6f);
|
|
GUILayout.Label($"상태: {statusMessage}");
|
|
GUILayout.Space(6f);
|
|
|
|
GUILayout.Label("활성 이상상태");
|
|
DrawActiveAbnormalities();
|
|
|
|
GUILayout.Space(6f);
|
|
GUILayout.Label("카탈로그");
|
|
catalogScroll = GUILayout.BeginScrollView(catalogScroll, GUILayout.Height(220f));
|
|
for (int i = 0; i < abnormalityCatalog.Count; i++)
|
|
{
|
|
AbnormalityData data = abnormalityCatalog[i];
|
|
if (data == null)
|
|
continue;
|
|
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label($"[{i}] {data.abnormalityName} ({data.name})", GUILayout.Width(280f));
|
|
if (GUILayout.Button("적용", GUILayout.Width(50f)))
|
|
{
|
|
ApplyAbnormality(data);
|
|
}
|
|
if (GUILayout.Button("해제", GUILayout.Width(50f)))
|
|
{
|
|
RemoveAbnormality(data);
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
GUILayout.EndScrollView();
|
|
|
|
GUILayout.EndVertical();
|
|
GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f));
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (IsOwner && inputActions != null)
|
|
{
|
|
inputActions.Player.Enable();
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
CleanupInputActions();
|
|
}
|
|
|
|
private void InitializeInputActions()
|
|
{
|
|
if (inputActions == null)
|
|
{
|
|
inputActions = new InputSystem_Actions();
|
|
inputActions.Player.DebugHUD.performed += OnDebugHudPerformed;
|
|
}
|
|
|
|
inputActions.Player.Enable();
|
|
}
|
|
|
|
private void CleanupInputActions()
|
|
{
|
|
if (inputActions != null)
|
|
{
|
|
inputActions.Player.Disable();
|
|
}
|
|
}
|
|
|
|
private void OnDebugHudPerformed(InputAction.CallbackContext context)
|
|
{
|
|
if (!IsOwner)
|
|
return;
|
|
|
|
isVisible = !isVisible;
|
|
}
|
|
|
|
private void DrawActiveAbnormalities()
|
|
{
|
|
if (abnormalityManager == null || abnormalityManager.ActiveAbnormalities.Count == 0)
|
|
{
|
|
GUILayout.Label("- 없음");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < abnormalityManager.ActiveAbnormalities.Count; i++)
|
|
{
|
|
var active = abnormalityManager.ActiveAbnormalities[i];
|
|
if (active == null || active.Data == null)
|
|
continue;
|
|
|
|
string durationText = active.Data.IsPermanent ? "영구" : $"{active.RemainingDuration:F1}s";
|
|
GUILayout.Label($"- {active.Data.abnormalityName} / {durationText}");
|
|
}
|
|
}
|
|
|
|
private void ApplyFromInput()
|
|
{
|
|
if (!TryResolveAbnormality(abnormalityInput, out AbnormalityData data, out string message))
|
|
{
|
|
SetStatus(message);
|
|
return;
|
|
}
|
|
|
|
ApplyAbnormality(data);
|
|
}
|
|
|
|
private void RemoveFromInput()
|
|
{
|
|
if (!TryResolveAbnormality(abnormalityInput, out AbnormalityData data, out string message))
|
|
{
|
|
SetStatus(message);
|
|
return;
|
|
}
|
|
|
|
RemoveAbnormality(data);
|
|
}
|
|
|
|
private void ApplyAbnormality(AbnormalityData data)
|
|
{
|
|
if (data == null)
|
|
{
|
|
SetStatus("적용할 이상상태를 찾지 못했습니다.");
|
|
return;
|
|
}
|
|
|
|
if (abnormalityManager == null)
|
|
{
|
|
SetStatus("AbnormalityManager 참조가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
abnormalityManager.ApplyAbnormality(data, gameObject);
|
|
SetStatus($"'{data.abnormalityName}' 적용 요청을 보냈습니다.");
|
|
}
|
|
|
|
private void RemoveAbnormality(AbnormalityData data)
|
|
{
|
|
if (data == null)
|
|
{
|
|
SetStatus("해제할 이상상태를 찾지 못했습니다.");
|
|
return;
|
|
}
|
|
|
|
if (abnormalityManager == null)
|
|
{
|
|
SetStatus("AbnormalityManager 참조가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
abnormalityManager.RemoveAbnormality(data);
|
|
SetStatus($"'{data.abnormalityName}' 해제 요청을 보냈습니다.");
|
|
}
|
|
|
|
private bool TryResolveAbnormality(string input, out AbnormalityData resolved, out string message)
|
|
{
|
|
resolved = null;
|
|
message = string.Empty;
|
|
|
|
string normalizedInput = NormalizeIdentifier(input);
|
|
if (string.IsNullOrWhiteSpace(normalizedInput))
|
|
{
|
|
message = "이상상태 이름, 에셋 이름, 인덱스를 입력하세요.";
|
|
return false;
|
|
}
|
|
|
|
if (int.TryParse(normalizedInput, out int index))
|
|
{
|
|
if (index >= 0 && index < abnormalityCatalog.Count && abnormalityCatalog[index] != null)
|
|
{
|
|
resolved = abnormalityCatalog[index];
|
|
return true;
|
|
}
|
|
|
|
message = $"인덱스 {index} 에 해당하는 이상상태가 없습니다.";
|
|
return false;
|
|
}
|
|
|
|
List<AbnormalityData> partialMatches = new List<AbnormalityData>();
|
|
for (int i = 0; i < abnormalityCatalog.Count; i++)
|
|
{
|
|
AbnormalityData candidate = abnormalityCatalog[i];
|
|
if (candidate == null)
|
|
continue;
|
|
|
|
string normalizedName = NormalizeIdentifier(candidate.abnormalityName);
|
|
string normalizedAssetName = NormalizeIdentifier(candidate.name);
|
|
|
|
if (normalizedInput == normalizedName || normalizedInput == normalizedAssetName)
|
|
{
|
|
resolved = candidate;
|
|
return true;
|
|
}
|
|
|
|
if (normalizedName.Contains(normalizedInput) || normalizedAssetName.Contains(normalizedInput))
|
|
{
|
|
partialMatches.Add(candidate);
|
|
}
|
|
}
|
|
|
|
if (partialMatches.Count == 1)
|
|
{
|
|
resolved = partialMatches[0];
|
|
return true;
|
|
}
|
|
|
|
if (partialMatches.Count > 1)
|
|
{
|
|
message = BuildAmbiguousMessage(partialMatches);
|
|
return false;
|
|
}
|
|
|
|
message = $"'{input}' 에 해당하는 이상상태를 찾지 못했습니다.";
|
|
return false;
|
|
}
|
|
|
|
private string BuildAmbiguousMessage(List<AbnormalityData> matches)
|
|
{
|
|
int previewCount = Mathf.Min(3, matches.Count);
|
|
List<string> previewNames = new List<string>(previewCount);
|
|
for (int i = 0; i < previewCount; i++)
|
|
{
|
|
AbnormalityData match = matches[i];
|
|
previewNames.Add(match != null ? match.abnormalityName : "null");
|
|
}
|
|
|
|
string preview = string.Join(", ", previewNames);
|
|
if (matches.Count > previewCount)
|
|
{
|
|
preview += ", ...";
|
|
}
|
|
|
|
return $"여러 후보가 있습니다: {preview}";
|
|
}
|
|
|
|
private string NormalizeIdentifier(string value)
|
|
{
|
|
return string.IsNullOrWhiteSpace(value)
|
|
? string.Empty
|
|
: value.Trim().Replace(" ", string.Empty).ToLowerInvariant();
|
|
}
|
|
|
|
private void SetStatus(string message)
|
|
{
|
|
statusMessage = message;
|
|
|
|
if (debugLogs)
|
|
{
|
|
Debug.Log($"[AbnormalityDebugHUD] {message}");
|
|
}
|
|
}
|
|
|
|
private void KillSelf()
|
|
{
|
|
if (networkController == null)
|
|
{
|
|
SetStatus("PlayerNetworkController 참조가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
if (networkController.IsDead)
|
|
{
|
|
SetStatus("이미 사망한 상태입니다.");
|
|
return;
|
|
}
|
|
|
|
networkController.TakeDamageRpc(networkController.Health + 1f);
|
|
SetStatus("즉사 요청을 보냈습니다.");
|
|
}
|
|
|
|
[Rpc(SendTo.Server)]
|
|
private void RequestRespawnRpc()
|
|
{
|
|
if (networkController == null)
|
|
return;
|
|
|
|
networkController.Respawn();
|
|
}
|
|
|
|
private bool ShouldEnableDebugHud()
|
|
{
|
|
#if UNITY_EDITOR
|
|
return true;
|
|
#else
|
|
return Debug.isDebugBuild;
|
|
#endif
|
|
}
|
|
|
|
private void RefreshCatalog()
|
|
{
|
|
abnormalityCatalog.RemoveAll(data => data == null);
|
|
|
|
#if UNITY_EDITOR
|
|
string[] guids = AssetDatabase.FindAssets("t:AbnormalityData", new[] { "Assets/_Game/Data/Abnormalities" });
|
|
List<AbnormalityData> loadedAssets = new List<AbnormalityData>(guids.Length);
|
|
|
|
for (int i = 0; i < guids.Length; i++)
|
|
{
|
|
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
|
|
AbnormalityData data = AssetDatabase.LoadAssetAtPath<AbnormalityData>(path);
|
|
if (data != null)
|
|
{
|
|
loadedAssets.Add(data);
|
|
}
|
|
}
|
|
|
|
loadedAssets.Sort((left, right) =>
|
|
string.Compare(left != null ? left.name : string.Empty, right != null ? right.name : string.Empty, StringComparison.OrdinalIgnoreCase));
|
|
|
|
abnormalityCatalog = loadedAssets;
|
|
#endif
|
|
}
|
|
}
|
|
}
|