using System; using Unity.Netcode; using UnityEngine; namespace Northbound { /// /// 건물 토대 - 플레이어가 상호작용하여 건물을 완성시킴 /// public class BuildingFoundation : NetworkBehaviour, IInteractable, ITeamMember { [Header("Building Info")] public BuildingData buildingData; public Vector3Int gridPosition; public int rotation; [Header("Visual")] public GameObject foundationVisual; public GameObject progressBarPrefab; // 현재 건설 진행도 private NetworkVariable _currentProgress = new NetworkVariable( 0f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); // 건물 소유자 private NetworkVariable _ownerId = new NetworkVariable( 0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); // 팀 private NetworkVariable _team = new NetworkVariable( TeamType.Neutral, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); // 이벤트 public event Action OnProgressChanged; // (current, max) public event Action OnConstructionComplete; private GameObject _progressBarInstance; private float _lastInteractionTime; private BoxCollider _collider; public ulong OwnerId => _ownerId.Value; public override void OnNetworkSpawn() { base.OnNetworkSpawn(); _currentProgress.OnValueChanged += OnProgressValueChanged; // 진행 UI 생성 if (progressBarPrefab != null) { _progressBarInstance = Instantiate(progressBarPrefab, transform); UpdateProgressBar(); } } public override void OnNetworkDespawn() { _currentProgress.OnValueChanged -= OnProgressValueChanged; base.OnNetworkDespawn(); } /// /// 토대 초기화 /// public void Initialize(BuildingData data, Vector3Int pos, int rot, ulong ownerId, TeamType team) { if (!IsServer) return; buildingData = data; gridPosition = pos; rotation = rot; _ownerId.Value = ownerId; _team.Value = team; _currentProgress.Value = 0f; // BuildingData의 크기를 기반으로 스케일 설정 Vector3 size = data.GetSize(rot); // foundationVisual의 스케일만 조정 (토대 자체의 pivot은 중앙에 유지) if (foundationVisual != null) { // 토대 비주얼을 건물 크기에 맞게 조정 (높이는 얇게) foundationVisual.transform.localScale = new Vector3(size.x, 0.2f, size.z); foundationVisual.transform.localPosition = new Vector3(0, 0.1f, 0); // 바닥에서 약간 위 } // BoxCollider 추가 및 크기 설정 (상호작용용) _collider = GetComponent(); if (_collider == null) { _collider = gameObject.AddComponent(); } // 상호작용 가능한 크기로 설정 (전체 건물 높이가 아닌 접근 가능한 크기) _collider.size = new Vector3(size.x, 2f, size.z); // 높이를 2m로 설정하여 상호작용 가능 _collider.center = new Vector3(0, 1f, 0); // 중심을 1m 높이에 배치 _collider.isTrigger = false; // Trigger가 아닌 일반 Collider로 설정 Debug.Log($"[BuildingFoundation] 토대 생성: {data.buildingName}, 크기: {size}, 위치: {transform.position}, Collider: {_collider.size}, 소유자: {ownerId}, 팀: {team}"); } /// /// 토대의 그리드 경계 가져오기 (BuildingManager의 충돌 체크용) /// public Bounds GetGridBounds() { if (buildingData == null) return new Bounds(transform.position, Vector3.one); Vector3 size = buildingData.GetSize(rotation); // 토대의 위치를 중심으로 건물이 차지할 공간 반환 return new Bounds(transform.position + Vector3.up * size.y * 0.5f, size); } #region IInteractable Implementation public bool CanInteract(ulong playerId) { if (buildingData == null) { Debug.LogWarning($"[BuildingFoundation] buildingData is null"); return false; } // 쿨다운 체크 if (Time.time - _lastInteractionTime < buildingData.interactionCooldown) { Debug.Log($"[BuildingFoundation] Cooldown active: {Time.time - _lastInteractionTime}/{buildingData.interactionCooldown}"); return false; } // 이미 완성됨 if (_currentProgress.Value >= buildingData.requiredWorkAmount) { Debug.Log($"[BuildingFoundation] Already completed"); return false; } // 같은 팀만 건설 가능 - 플레이어의 팀을 가져와서 비교 TeamType playerTeam = GetPlayerTeam(playerId); if (playerTeam != _team.Value) { Debug.LogWarning($"[BuildingFoundation] Wrong team: player={playerTeam}, foundation={_team.Value}"); return false; } Debug.Log($"[BuildingFoundation] CanInteract = true"); return true; } public void Interact(ulong playerId) { if (!IsServer || buildingData == null) return; if (!CanInteract(playerId)) return; _lastInteractionTime = Time.time; // 건설 진행 _currentProgress.Value += buildingData.workPerInteraction; Debug.Log($"[BuildingFoundation] 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount} ({(_currentProgress.Value / buildingData.requiredWorkAmount * 100f):F1}%)"); // 완성 체크 if (_currentProgress.Value >= buildingData.requiredWorkAmount) { CompleteConstruction(); } } public string GetInteractionPrompt() { if (buildingData == null) return "[E] 건설하기"; float percentage = (_currentProgress.Value / buildingData.requiredWorkAmount) * 100f; return $"[E] 건설하기 ({percentage:F0}%)"; } public string GetInteractionAnimation() { // BuildingData에서 애니메이션 트리거 가져오기 if (buildingData != null && !string.IsNullOrEmpty(buildingData.constructionAnimationTrigger)) { return buildingData.constructionAnimationTrigger; } // 기본값: 빈 문자열 (애니메이션 없음) return ""; } public InteractionEquipmentData GetEquipmentData() { // BuildingData에 건설 도구가 정의되어 있으면 반환 if (buildingData != null && buildingData.constructionEquipment != null) { return buildingData.constructionEquipment; } return null; // 특별한 도구 불필요 } public Transform GetTransform() { return transform; } #endregion #region ITeamMember Implementation public TeamType GetTeam() { return _team.Value; } public void SetTeam(TeamType team) { if (!IsServer) return; _team.Value = team; } #endregion /// /// 플레이어의 팀 가져오기 /// private TeamType GetPlayerTeam(ulong playerId) { // 플레이어의 NetworkObject 찾기 if (NetworkManager.Singleton != null && NetworkManager.Singleton.SpawnManager != null) { if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(playerId, out NetworkObject playerNetObj)) { var teamMember = playerNetObj.GetComponent(); if (teamMember != null) { return teamMember.GetTeam(); } } } // 기본값: 플레이어 팀 return TeamType.Player; } private void CompleteConstruction() { if (!IsServer) return; Debug.Log($"[BuildingFoundation] 건물 완성! {buildingData.buildingName}"); OnConstructionComplete?.Invoke(); // BuildingManager에서 토대 제거 var buildingManager = BuildingManager.Instance; if (buildingManager != null) { buildingManager.RemoveFoundation(this); } // 완성된 건물 생성 SpawnCompletedBuilding(); // 토대 제거 if (NetworkObject != null) { NetworkObject.Despawn(true); } } private void SpawnCompletedBuilding() { if (!IsServer || buildingData == null || buildingData.prefab == null) return; // BuildingManager를 통해 건물 생성 var buildingManager = BuildingManager.Instance; if (buildingManager != null) { buildingManager.SpawnCompletedBuildingServerRpc( buildingData.name, gridPosition, rotation, _ownerId.Value, _team.Value ); } else { Debug.LogError("[BuildingFoundation] BuildingManager를 찾을 수 없습니다!"); } } private void OnProgressValueChanged(float oldValue, float newValue) { if (buildingData != null) { OnProgressChanged?.Invoke(newValue, buildingData.requiredWorkAmount); } UpdateProgressBar(); } private void UpdateProgressBar() { if (_progressBarInstance == null || buildingData == null) return; // 진행바 UI 업데이트 (BuildingHealthBar와 유사한 구조 사용 가능) var progressBar = _progressBarInstance.GetComponent(); if (progressBar != null) { // BuildingHealthBar를 재사용하여 진행도 표시 progressBar.UpdateHealth((int)_currentProgress.Value, (int)buildingData.requiredWorkAmount); } } private void OnDrawGizmos() { if (buildingData == null) return; // 건물 경계 표시 (노란색) Gizmos.color = Color.yellow; Vector3 size = buildingData.GetSize(rotation); Gizmos.DrawWireCube(transform.position + Vector3.up * size.y * 0.5f, size); // Collider 경계 표시 (초록색) if (_collider != null) { Gizmos.color = Color.green; Gizmos.DrawWireCube(transform.position + _collider.center, _collider.size); } // 상호작용 가능 여부 표시 if (_currentProgress.Value < (buildingData?.requiredWorkAmount ?? 100f)) { Gizmos.color = Color.cyan; Gizmos.DrawSphere(transform.position + Vector3.up, 0.3f); } } } }