팀 체력 UI
This commit is contained in:
@@ -82,6 +82,7 @@
|
||||
<Compile Include="Assets\Scripts\Resource.cs" />
|
||||
<Compile Include="Assets\Data\Scripts\DataClasses\UpgradeData.cs" />
|
||||
<Compile Include="Assets\Scripts\UpgradeDatabase.cs" />
|
||||
<Compile Include="Assets\Scripts\UI\TeamHealthUI.cs" />
|
||||
<Compile Include="Assets\Scripts\AutoTargetSystem.cs" />
|
||||
<Compile Include="Assets\Scripts\CreepDataComponent.cs" />
|
||||
<Compile Include="Assets\Scripts\BuildingSlotButton.cs" />
|
||||
|
||||
@@ -1183,6 +1183,7 @@ GameObject:
|
||||
- component: {fileID: 665708330}
|
||||
- component: {fileID: 665708331}
|
||||
- component: {fileID: 665708332}
|
||||
- component: {fileID: 665708333}
|
||||
m_Layer: 5
|
||||
m_Name: TeamCardsRoot
|
||||
m_TagString: Untagged
|
||||
@@ -1253,6 +1254,22 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
|
||||
m_HorizontalFit: 2
|
||||
m_VerticalFit: 0
|
||||
--- !u!114 &665708333
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 665708329}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b5135790bbd96064aab844c2ba2aaa32, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Northbound.TeamHealthUI
|
||||
slot1: {fileID: 0}
|
||||
slot2: {fileID: 0}
|
||||
slot3: {fileID: 0}
|
||||
slot4: {fileID: 0}
|
||||
--- !u!1 &672563220
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
391
Assets/Scripts/UI/TeamHealthUI.cs
Normal file
391
Assets/Scripts/UI/TeamHealthUI.cs
Normal file
@@ -0,0 +1,391 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using Unity.Netcode;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 팀원 체력 UI 관리
|
||||
/// Slot 1: 로컬 플레이어 (본인)
|
||||
/// Slot 2~4: 다른 플레이어 (플레이어 ID 순)
|
||||
/// </summary>
|
||||
public class TeamHealthUI : MonoBehaviour
|
||||
{
|
||||
[Header("Slot References (자동 할당됨)")]
|
||||
[SerializeField] private TeamCardSlot slot1; // 로컬 플레이어 (본인)
|
||||
[SerializeField] private TeamCardSlot slot2; // 다른 플레이어 1
|
||||
[SerializeField] private TeamCardSlot slot3; // 다른 플레이어 2
|
||||
[SerializeField] private TeamCardSlot slot4; // 다른 플레이어 3
|
||||
|
||||
private Dictionary<ulong, NetworkPlayerController> _playerControllers = new Dictionary<ulong, NetworkPlayerController>();
|
||||
private Dictionary<ulong, TeamCardSlot> _playerSlotMapping = new Dictionary<ulong, TeamCardSlot>();
|
||||
private ulong _localPlayerId = ulong.MaxValue;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 슬롯 초기화
|
||||
InitializeSlots();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 네트워크 이벤트 구독
|
||||
if (NetworkManager.Singleton != null)
|
||||
{
|
||||
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
|
||||
NetworkManager.Singleton.OnClientDisconnectCallback += OnClientDisconnect;
|
||||
}
|
||||
|
||||
// 이미 연결된 플레이어들 처리
|
||||
RefreshAllPlayers();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 이벤트 구독 해제
|
||||
if (NetworkManager.Singleton != null)
|
||||
{
|
||||
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
|
||||
NetworkManager.Singleton.OnClientDisconnectCallback -= OnClientDisconnect;
|
||||
}
|
||||
|
||||
// 모든 플레이어 체력 변경 이벤트 구독 해제
|
||||
foreach (var kvp in _playerControllers)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
kvp.Value.OnOwnerChanged -= OnPlayerOwnerChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeSlots()
|
||||
{
|
||||
// 슬롯 자동 할당 - 이름으로 찾기
|
||||
if (slot1 == null) slot1 = FindSlotByName("Slot_1");
|
||||
if (slot2 == null) slot2 = FindSlotByName("Slot_2");
|
||||
if (slot3 == null) slot3 = FindSlotByName("Slot_3");
|
||||
if (slot4 == null) slot4 = FindSlotByName("Slot_4");
|
||||
|
||||
// 모든 슬롯 초기화 (비어있음)
|
||||
slot1?.Clear();
|
||||
slot2?.Clear();
|
||||
slot3?.Clear();
|
||||
slot4?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이름으로 슬롯 찾기 (자식의 자식 프리팹까지 검색)
|
||||
/// </summary>
|
||||
private TeamCardSlot FindSlotByName(string slotName)
|
||||
{
|
||||
// 직접 자식에서 찾기
|
||||
Transform slotTransform = transform.Find(slotName);
|
||||
if (slotTransform == null) return null;
|
||||
|
||||
// 자식 프리팹에서 TeamCardSlot 찾기 또는 생성
|
||||
TeamCardSlot slot = slotTransform.GetComponentInChildren<TeamCardSlot>(true);
|
||||
if (slot == null)
|
||||
{
|
||||
// TeamCardSlot 컴포넌트를 자식 프리팹의 루트에 추가
|
||||
// 프리팹 인스턴스의 첫 번째 자식이 실제 카드 프리팹
|
||||
if (slotTransform.childCount > 0)
|
||||
{
|
||||
GameObject cardObj = slotTransform.GetChild(0).gameObject;
|
||||
slot = cardObj.AddComponent<TeamCardSlot>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 자식이 없으면 슬롯 자체에 추가
|
||||
slot = slotTransform.gameObject.AddComponent<TeamCardSlot>();
|
||||
}
|
||||
}
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
private void OnClientConnected(ulong clientId)
|
||||
{
|
||||
// 약간의 딜레이 후 플레이어 컨트롤러가 생성될 때까지 대기
|
||||
Invoke(nameof(RefreshAllPlayers), 0.5f);
|
||||
}
|
||||
|
||||
private void OnClientDisconnect(ulong clientId)
|
||||
{
|
||||
// 플레이어 제거
|
||||
RemovePlayer(clientId);
|
||||
}
|
||||
|
||||
private void RefreshAllPlayers()
|
||||
{
|
||||
// 로컬 플레이어 ID 확인
|
||||
if (NetworkManager.Singleton != null)
|
||||
{
|
||||
_localPlayerId = NetworkManager.Singleton.LocalClientId;
|
||||
}
|
||||
|
||||
// 모든 NetworkPlayerController 찾기
|
||||
var allPlayers = FindObjectsByType<NetworkPlayerController>(FindObjectsSortMode.None);
|
||||
|
||||
// 기존 매핑 클리어
|
||||
_playerControllers.Clear();
|
||||
|
||||
// 플레이어 등록
|
||||
foreach (var player in allPlayers)
|
||||
{
|
||||
ulong playerId = player.OwnerPlayerId;
|
||||
if (playerId != ulong.MaxValue)
|
||||
{
|
||||
_playerControllers[playerId] = player;
|
||||
player.OnOwnerChanged -= OnPlayerOwnerChanged;
|
||||
player.OnOwnerChanged += OnPlayerOwnerChanged;
|
||||
}
|
||||
}
|
||||
|
||||
// UI 업데이트
|
||||
UpdateAllSlots();
|
||||
}
|
||||
|
||||
private void OnPlayerOwnerChanged(ulong newOwnerId)
|
||||
{
|
||||
// 플레이어 소유자 변경 시 갱신
|
||||
Invoke(nameof(RefreshAllPlayers), 0.1f);
|
||||
}
|
||||
|
||||
private void UpdateAllSlots()
|
||||
{
|
||||
// 기존 매핑 클리어
|
||||
_playerSlotMapping.Clear();
|
||||
slot1?.Clear();
|
||||
slot2?.Clear();
|
||||
slot3?.Clear();
|
||||
slot4?.Clear();
|
||||
|
||||
// 로컬 플레이어를 Slot 1에 배치
|
||||
if (_playerControllers.TryGetValue(_localPlayerId, out var localPlayer))
|
||||
{
|
||||
slot1?.SetPlayer(localPlayer, true);
|
||||
_playerSlotMapping[_localPlayerId] = slot1;
|
||||
}
|
||||
|
||||
// 다른 플레이어들을 ID 순으로 Slot 2~4에 배치
|
||||
var otherPlayers = new List<KeyValuePair<ulong, NetworkPlayerController>>();
|
||||
foreach (var kvp in _playerControllers)
|
||||
{
|
||||
if (kvp.Key != _localPlayerId)
|
||||
{
|
||||
otherPlayers.Add(kvp);
|
||||
}
|
||||
}
|
||||
|
||||
// ID 순으로 정렬
|
||||
otherPlayers.Sort((a, b) => a.Key.CompareTo(b.Key));
|
||||
|
||||
// 슬롯에 배치
|
||||
var slots = new[] { slot2, slot3, slot4 };
|
||||
for (int i = 0; i < otherPlayers.Count && i < slots.Length; i++)
|
||||
{
|
||||
var player = otherPlayers[i];
|
||||
slots[i]?.SetPlayer(player.Value, false);
|
||||
_playerSlotMapping[player.Key] = slots[i];
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePlayer(ulong playerId)
|
||||
{
|
||||
if (_playerControllers.TryGetValue(playerId, out var player))
|
||||
{
|
||||
player.OnOwnerChanged -= OnPlayerOwnerChanged;
|
||||
_playerControllers.Remove(playerId);
|
||||
}
|
||||
|
||||
if (_playerSlotMapping.TryGetValue(playerId, out var slot))
|
||||
{
|
||||
slot?.Clear();
|
||||
_playerSlotMapping.Remove(playerId);
|
||||
}
|
||||
|
||||
// 슬롯 재배치
|
||||
UpdateAllSlots();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 개별 팀 카드 슬롯
|
||||
/// TeamCard_Large 또는 TeamCard_Small 프리팹에 붙여서 사용
|
||||
/// </summary>
|
||||
public class TeamCardSlot : MonoBehaviour
|
||||
{
|
||||
[Header("UI References (자동 할당됨)")]
|
||||
[SerializeField] private TextMeshProUGUI _playerNameText;
|
||||
[SerializeField] private Image _hpBarFill;
|
||||
[SerializeField] private TextMeshProUGUI _hpText;
|
||||
|
||||
private NetworkPlayerController _player;
|
||||
private bool _isLocalPlayer;
|
||||
private int _lastHealth = -1;
|
||||
private int _lastMaxHealth = -1;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 자동으로 UI 요소 찾기
|
||||
FindUIElements();
|
||||
}
|
||||
|
||||
private void FindUIElements()
|
||||
{
|
||||
// PlayerNameText 찾기 - 이름으로 정확히 찾기
|
||||
if (_playerNameText == null)
|
||||
{
|
||||
Transform nameText = FindDeepChild(transform, "PlayerNameText");
|
||||
if (nameText != null)
|
||||
{
|
||||
_playerNameText = nameText.GetComponent<TextMeshProUGUI>();
|
||||
}
|
||||
}
|
||||
|
||||
// HPBarFill 찾기
|
||||
if (_hpBarFill == null)
|
||||
{
|
||||
Transform hpFill = FindDeepChild(transform, "HPBarFill");
|
||||
if (hpFill != null)
|
||||
{
|
||||
_hpBarFill = hpFill.GetComponent<Image>();
|
||||
}
|
||||
}
|
||||
|
||||
// HPText 찾기
|
||||
if (_hpText == null)
|
||||
{
|
||||
Transform hpText = FindDeepChild(transform, "HPText");
|
||||
if (hpText != null)
|
||||
{
|
||||
_hpText = hpText.GetComponent<TextMeshProUGUI>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 깊이 우선 탐색으로 자식 찾기
|
||||
/// </summary>
|
||||
private Transform FindDeepChild(Transform parent, string name)
|
||||
{
|
||||
// 직접 자식에서 찾기
|
||||
Transform found = parent.Find(name);
|
||||
if (found != null) return found;
|
||||
|
||||
// 재귀적으로 자식의 자식에서 찾기
|
||||
for (int i = 0; i < parent.childCount; i++)
|
||||
{
|
||||
found = FindDeepChild(parent.GetChild(i), name);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetPlayer(NetworkPlayerController player, bool isLocalPlayer)
|
||||
{
|
||||
_player = player;
|
||||
_isLocalPlayer = isLocalPlayer;
|
||||
_lastHealth = -1; // 강제 업데이트를 위해 리셋
|
||||
_lastMaxHealth = -1;
|
||||
|
||||
if (player == null)
|
||||
{
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// UI 요소가 없으면 다시 찾기
|
||||
FindUIElements();
|
||||
|
||||
// 플레이어 이름 설정
|
||||
if (_playerNameText != null)
|
||||
{
|
||||
string playerName = $"Player {player.OwnerPlayerId + 1}";
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
playerName += " (YOU)";
|
||||
}
|
||||
_playerNameText.text = playerName;
|
||||
}
|
||||
|
||||
// 초기 체력 업데이트
|
||||
UpdateHealth();
|
||||
|
||||
// 활성화
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_player = null;
|
||||
_lastHealth = -1;
|
||||
_lastMaxHealth = -1;
|
||||
|
||||
if (_playerNameText != null)
|
||||
{
|
||||
_playerNameText.text = "Waiting...";
|
||||
}
|
||||
|
||||
if (_hpBarFill != null)
|
||||
{
|
||||
_hpBarFill.fillAmount = 0f;
|
||||
}
|
||||
|
||||
if (_hpText != null)
|
||||
{
|
||||
_hpText.text = "--- / ---";
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
// 체력 업데이트 (변경 시에만)
|
||||
if (_player != null)
|
||||
{
|
||||
int currentHp = _player.GetCurrentHealth();
|
||||
int maxHp = _player.GetMaxHealth();
|
||||
|
||||
// 체력이나 최대 체력이 변경된 경우에만 UI 업데이트
|
||||
if (currentHp != _lastHealth || maxHp != _lastMaxHealth)
|
||||
{
|
||||
UpdateHealth();
|
||||
_lastHealth = currentHp;
|
||||
_lastMaxHealth = maxHp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHealth()
|
||||
{
|
||||
if (_player == null) return;
|
||||
|
||||
int currentHp = _player.GetCurrentHealth();
|
||||
int maxHp = _player.GetMaxHealth();
|
||||
float hpPercent = maxHp > 0 ? (float)currentHp / maxHp : 0f;
|
||||
|
||||
if (_hpBarFill != null)
|
||||
{
|
||||
_hpBarFill.fillAmount = hpPercent;
|
||||
}
|
||||
|
||||
if (_hpText != null)
|
||||
{
|
||||
_hpText.text = $"{currentHp} / {maxHp}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 에디터에서 UI 요소 자동 할당
|
||||
/// </summary>
|
||||
[ContextMenu("Auto Find UI Elements")]
|
||||
private void AutoFindUIElements()
|
||||
{
|
||||
FindUIElements();
|
||||
Debug.Log($"[TeamCardSlot] UI Elements Found - Name: {_playerNameText != null}, HPFill: {_hpBarFill != null}, HPText: {_hpText != null}");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/TeamHealthUI.cs.meta
Normal file
2
Assets/Scripts/UI/TeamHealthUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5135790bbd96064aab844c2ba2aaa32
|
||||
Reference in New Issue
Block a user