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 (!IsOwner) { InitializeServerRpc(data != null ? data.buildingName : "", pos.x, pos.y, pos.z, rot, ownerId, team); return; } InitializeServerRpc(data != null ? data.buildingName : "", pos.x, pos.y, pos.z, rot, ownerId, team); } [ServerRpc] private void InitializeServerRpc(string buildingName, int posX, int posY, int posZ, int rot, ulong ownerId, TeamType team) { buildingData = BuildingManager.Instance?.availableBuildings.Find(b => b != null && b.buildingName == buildingName); gridPosition = new Vector3Int(posX, posY, posZ); rotation = rot; _ownerId.Value = ownerId; _team.Value = team; _currentProgress.Value = 0f; // BuildingData의 크기를 기반으로 스케일 설정 Vector3 size = buildingData != null ? buildingData.GetSize(rot) : Vector3.one; // 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] 토대 생성: {buildingData?.buildingName ?? "Building"}, 크기: {size}, 위치: {transform.position}, Collider: {_collider.size}, 소유자: {ownerId}, 팀: {TeamManager.GetTeamName(team)}"); } private void UpdateProgressBar() { if (buildingData == null || _progressBarInstance == null) return; float progress = buildingData.requiredWorkAmount > 0 ? _currentProgress.Value / buildingData.requiredWorkAmount : 1f; // 간단한 progress bar update - 필요한 경우 BuildingProgressBar 컴포넌트 사용 var progressBarTransform = _progressBarInstance.transform; progressBarTransform.localScale = new Vector3(progress, 1f, 1f); } private void OnProgressValueChanged(float previousValue, float newValue) { UpdateProgressBar(); float max = buildingData != null ? buildingData.requiredWorkAmount : 100f; OnProgressChanged?.Invoke(newValue, max); } /// /// 토대의 그리드 경계 가져오기 (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); } public Bounds GetBounds() { return GetGridBounds(); } #region IInteractable Implementation public bool CanInteract(ulong playerId) { if (buildingData == null) { Debug.LogWarning($"[BuildingFoundation] buildingData is null"); 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; } 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] {buildingData.buildingName} 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount}"); // 완료 체크 if (_currentProgress.Value >= buildingData.requiredWorkAmount) { CompleteConstruction(playerId); } } private void CompleteConstruction(ulong playerId) { Debug.Log($"[BuildingFoundation] {buildingData.buildingName} 건설 완료! 완성자: {playerId}"); OnConstructionComplete?.Invoke(); // 토대 디스폰 if (IsServer && NetworkObject != null) { NetworkObject.Despawn(true); } // 상호작용 UI 제거 if (_progressBarInstance != null) { Destroy(_progressBarInstance); _progressBarInstance = null; } } private TeamType GetPlayerTeam(ulong playerId) { if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(playerId, out NetworkObject playerObj)) { var teamMember = playerObj.GetComponent(); if (teamMember != null) { return teamMember.GetTeam(); } } return TeamType.Neutral; } #endregion #region IInteractable Implementation - Getters public string GetInteractionPrompt() { if (buildingData == null) return "건설하기"; float workNeeded = buildingData.requiredWorkAmount - _currentProgress.Value; float interactionsNeeded = Mathf.Ceil(workNeeded / buildingData.workPerInteraction); return $"[{interactionsNeeded}] 건설하기"; } public string GetInteractionAnimation() { if (buildingData != null && buildingData.constructionAnimationTrigger != null) return buildingData.constructionAnimationTrigger; return null; } public EquipmentData GetEquipmentData() { return buildingData != null ? buildingData.constructionEquipment : null; } public Transform GetTransform() { return transform; } #endregion #region ITeamMember Implementation public TeamType GetTeam() => _team.Value; public void SetTeam(TeamType team) { if (!IsOwner) { SetTeamServerRpc(team); return; } SetTeamServerRpc(team); } [ServerRpc] private void SetTeamServerRpc(TeamType team) { _team.Value = team; } #endregion } }