diff --git a/Assets/Data/Scripts/DataClasses/TowerDataExtensions.cs b/Assets/Data/Scripts/DataClasses/TowerDataExtensions.cs index a188c6a..70e2c2d 100644 --- a/Assets/Data/Scripts/DataClasses/TowerDataExtensions.cs +++ b/Assets/Data/Scripts/DataClasses/TowerDataExtensions.cs @@ -36,7 +36,7 @@ namespace Northbound.Data [Header("Properties for convenience")] public int width => sizeX; public int length => sizeY; - public float height => sizeY; + public float height => sizeZ; // height는 sizeZ로 수정 public int maxHealth => maxHp; public float visionRange => atkRange; public float requiredWorkAmount => manpower; @@ -46,7 +46,7 @@ namespace Northbound.Data bool isRotated = (rotation == 1 || rotation == 3); float w = isRotated ? length : width; float l = isRotated ? width : length; - return new Vector3(w, sizeY, l); + return new Vector3(w, sizeZ, l); // 세 번째 차원도 sizeZ 사용 } } } diff --git a/Assets/Prefabs/BuildingFoundation.prefab b/Assets/Prefabs/BuildingFoundation.prefab index 77d54d0..2471676 100644 --- a/Assets/Prefabs/BuildingFoundation.prefab +++ b/Assets/Prefabs/BuildingFoundation.prefab @@ -11,6 +11,7 @@ GameObject: - component: {fileID: 320439620877427584} - component: {fileID: 7934051929434515110} - component: {fileID: 1469866302769820338} + - component: {fileID: -4242933244907694767} m_Layer: 7 m_Name: BuildingFoundation m_TagString: Untagged @@ -75,6 +76,7 @@ MonoBehaviour: buildingData: {fileID: 11400000, guid: 23c12a82ea534b34299700b86fffd524, type: 2} gridPosition: {x: 0, y: 0, z: 0} rotation: 0 + interactionCooldown: 1 constructionAnimationTrigger: Mining constructionEquipment: socketName: handslot.r @@ -86,6 +88,27 @@ MonoBehaviour: detachDelay: 0 foundationVisual: {fileID: 2851644658348875061} progressBarPrefab: {fileID: 0} +--- !u!65 &-4242933244907694767 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1340458267086560577} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} --- !u!1001 &3121692064363536484 PrefabInstance: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/BuildingFoundation.cs b/Assets/Scripts/BuildingFoundation.cs index b3924a3..ad7d972 100644 --- a/Assets/Scripts/BuildingFoundation.cs +++ b/Assets/Scripts/BuildingFoundation.cs @@ -27,6 +27,13 @@ namespace Northbound public GameObject foundationVisual; public GameObject progressBarPrefab; + // 건물 데이터 인덱스 (네트워크 동기화용) + private NetworkVariable _buildingDataIndex = new NetworkVariable( + -1, + NetworkVariableReadPermission.Everyone, + NetworkVariableWritePermission.Server + ); + // 현재 건설 진행도 private NetworkVariable _currentProgress = new NetworkVariable( 0f, @@ -63,6 +70,12 @@ namespace Northbound base.OnNetworkSpawn(); _currentProgress.OnValueChanged += OnProgressValueChanged; + _buildingDataIndex.OnValueChanged += OnBuildingDataIndexChanged; + + // 초기값 로드 시도 (Host/Client 모두 동일하게 처리) + // NetworkVariable 초기값은 스폰 시 동기화되지만, + // OnValueChanged는 변경 시만 발생하므로 초기값은 직접 로드해야 함 + LoadBuildingDataFromIndex(_buildingDataIndex.Value); // 진행 UI 생성 if (progressBarPrefab != null) @@ -75,10 +88,90 @@ namespace Northbound public override void OnNetworkDespawn() { _currentProgress.OnValueChanged -= OnProgressValueChanged; + _buildingDataIndex.OnValueChanged -= OnBuildingDataIndexChanged; base.OnNetworkDespawn(); } + private void OnBuildingDataIndexChanged(int oldValue, int newValue) + { + LoadBuildingDataFromIndex(newValue); + UpdateCollider(); + } + + private void LoadBuildingDataFromIndex(int index) + { + var buildingManager = BuildingManager.Instance; + if (buildingManager == null) + { + return; + } + + if (index < 0) + { + return; + } + + if (index >= buildingManager.availableBuildings.Count) + { + return; + } + + // 이미 로드된 데이터와 동일하면 건너뜀 + TowerData newData = buildingManager.availableBuildings[index]; + if (buildingData == newData) + { + return; + } + + buildingData = newData; + + // buildingData 로드 후 업데이트 + UpdateCollider(); + UpdateVisual(); + } + + /// + /// BoxCollider 업데이트 (buildingData 기반) + /// + private void UpdateCollider() + { + if (buildingData == null) + return; + + Vector3 size = buildingData.GetSize(rotation); + + // BoxCollider가 없으면 추가 + if (_collider == null) + { + _collider = GetComponent(); + if (_collider == null) + { + _collider = gameObject.AddComponent(); + } + } + + // 상호작용 가능한 크기로 설정 (전체 건물 높이가 아닌 접근 가능한 크기) + _collider.size = new Vector3(size.x, 2f, size.z); + _collider.center = new Vector3(0, 1f, 0); + _collider.isTrigger = false; + } + + /// + /// Visual 스케일 업데이트 (buildingData 기반) + /// + private void UpdateVisual() + { + if (buildingData == null || foundationVisual == null) + return; + + Vector3 size = buildingData.GetSize(rotation); + + // 토대 비주얼을 건물 크기에 맞게 조정 (높이는 얇게) + foundationVisual.transform.localScale = new Vector3(size.x, 0.2f, size.z); + foundationVisual.transform.localPosition = new Vector3(0, 0.1f, 0); + } + /// /// 토대 초기화 /// @@ -86,7 +179,31 @@ namespace Northbound { if (!IsServer) return; + // buildingData null 체크 + if (data == null) + { + return; + } + + // buildingData 인덱스 찾기 + var buildingManager = BuildingManager.Instance; + if (buildingManager == null) + { + return; + } + + int dataIndex = buildingManager.availableBuildings.IndexOf(data); + if (dataIndex < 0) + { + return; + } + + // 인덱스 설정 (네트워크 동기화됨) + _buildingDataIndex.Value = dataIndex; + + // 서버에서도 직접 데이터 로드 buildingData = data; + gridPosition = pos; rotation = rot; _ownerId.Value = ownerId; @@ -95,7 +212,7 @@ namespace Northbound // TowerData의 크기를 기반으로 스케일 설정 Vector3 size = data.GetSize(rot); - + // foundationVisual의 스케일만 조정 (토대 자체의 pivot은 중앙에 유지) if (foundationVisual != null) { @@ -115,8 +232,6 @@ namespace Northbound _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}"); } /// @@ -136,10 +251,15 @@ namespace Northbound public bool CanInteract(ulong playerId) { + // buildingData가 없으면 상호작용 불가 + if (buildingData == null) + { + return false; + } + // 이미 완성됨 if (_currentProgress.Value >= buildingData.requiredWorkAmount) { - Debug.Log($"[BuildingFoundation] Already completed"); return false; } @@ -153,7 +273,6 @@ namespace Northbound TeamType playerTeam = GetPlayerTeam(playerId); if (playerTeam != _team.Value) { - Debug.LogWarning($"[BuildingFoundation] Wrong team: player={playerTeam}, foundation={_team.Value}"); return false; } @@ -162,11 +281,28 @@ namespace Northbound public void Interact(ulong playerId) { - if (!IsServer) return; + // 네트워크 게임에서는 ServerRpc을 통해서만 서버에서 실행하도록 함 + RequestInteractServerRpc(playerId); + } + + [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] + private void RequestInteractServerRpc(ulong playerId) + { + // 서버에서만 건설 진행 가능 + if (!IsServer) + { + return; + } if (!CanInteract(playerId)) return; + // buildingData null 체크 + if (buildingData == null) + { + return; + } + _lastInteractionTime = Time.time; // 플레이어의 작업량 가져오기 @@ -175,8 +311,6 @@ namespace Northbound // 건설 진행 _currentProgress.Value += playerWorkPower; - Debug.Log($"[BuildingFoundation] 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount} ({(_currentProgress.Value / buildingData.requiredWorkAmount * 100f):F1}%) - 작업량: {playerWorkPower}"); - // 완성 체크 if (_currentProgress.Value >= buildingData.requiredWorkAmount) { @@ -187,7 +321,8 @@ namespace Northbound public string GetInteractionPrompt() { string buildingName = buildingData != null ? buildingData.buildingName : "건물"; - float percentage = (_currentProgress.Value / buildingData.requiredWorkAmount) * 100f; + float requiredWork = buildingData?.requiredWorkAmount ?? 100f; + float percentage = (_currentProgress.Value / requiredWork) * 100f; return $"[E] {buildingName} 건설 ({percentage:F0}%)"; } @@ -270,7 +405,6 @@ namespace Northbound } // 기본값: 10 - Debug.LogWarning($"[BuildingFoundation] 플레이어 {playerId}의 workPower를 찾을 수 없어 기본값 10을 사용합니다."); return 10f; } @@ -278,7 +412,10 @@ namespace Northbound { if (!IsServer) return; - Debug.Log($"[BuildingFoundation] 건물 완성! {buildingData.buildingName}"); + if (buildingData == null) + { + return; + } OnConstructionComplete?.Invoke(); @@ -324,7 +461,8 @@ namespace Northbound private void OnProgressValueChanged(float oldValue, float newValue) { - OnProgressChanged?.Invoke(newValue, buildingData.requiredWorkAmount); + float requiredWork = buildingData?.requiredWorkAmount ?? 100f; + OnProgressChanged?.Invoke(newValue, requiredWork); UpdateProgressBar(); } @@ -337,7 +475,8 @@ namespace Northbound if (progressBar != null) { // BuildingHealthBar를 재사용하여 진행도 표시 - progressBar.UpdateHealth((int)_currentProgress.Value, (int)buildingData.requiredWorkAmount); + float requiredWork = buildingData?.requiredWorkAmount ?? 100f; + progressBar.UpdateHealth((int)_currentProgress.Value, (int)requiredWork); } } diff --git a/Assets/Scripts/BuildingManager.cs b/Assets/Scripts/BuildingManager.cs index eaf48b5..d7d9c79 100644 --- a/Assets/Scripts/BuildingManager.cs +++ b/Assets/Scripts/BuildingManager.cs @@ -400,34 +400,35 @@ namespace Northbound GameObject foundationObj = Instantiate(foundationPrefab, snappedPosition + data.placementOffset, Quaternion.Euler(0, rotation * 90f, 0)); NetworkObject netObj = foundationObj.GetComponent(); - // Add FogOfWarVisibility component to hide foundations in unexplored areas - if (foundationObj.GetComponent() == null) - { - var visibility = foundationObj.AddComponent(); - visibility.showInExploredAreas = true; // Foundations remain visible in explored areas - visibility.updateInterval = 0.2f; - } - if (netObj != null) { - netObj.SpawnWithOwnership(requestingClientId); + // 스폰 먼저 실행 (서버 소유권으로 스폰) + // 소유자 정보는 _ownerId NetworkVariable에 별도로 저장하므로 NetworkObject 소유권은 서버 유지 + netObj.Spawn(); + // 스폰 후에 초기화 (OnNetworkSpawn 이후에 호출되어 타이밍 문제 해결) BuildingFoundation foundation = foundationObj.GetComponent(); - if (foundation != null) + if (foundation == null) { - foundation.Initialize(data, gridPosition, rotation, requestingClientId, playerTeam); - placedFoundations.Add(foundation); // 토대 목록에 추가 - Debug.Log($"[BuildingManager] {data.buildingName} 토대 생성 (소유자: {requestingClientId}, 위치: {gridPosition})"); + Destroy(foundationObj); + return; } - else + + // Initialize에서 NetworkVariable을 설정하고 데이터를 로드함 + foundation.Initialize(data, gridPosition, rotation, requestingClientId, playerTeam); + + // Add FogOfWarVisibility component to hide foundations in unexplored areas + if (foundationObj.GetComponent() == null) { - Debug.LogError("[BuildingManager] BuildingFoundation 컴포넌트가 없습니다!"); - netObj.Despawn(true); + var visibility = foundationObj.AddComponent(); + visibility.showInExploredAreas = true; // Foundations remain visible in explored areas + visibility.updateInterval = 0.2f; } + + placedFoundations.Add(foundation); // 토대 목록에 추가 } else { - Debug.LogError("[BuildingManager] NetworkObject 컴포넌트가 없습니다!"); Destroy(foundationObj); } }