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