네트워크 멀티플레이 대응

This commit is contained in:
2026-01-31 20:49:23 +09:00
parent 1152093521
commit c5bcf265d0
69 changed files with 2766 additions and 1392 deletions

View File

@@ -16,7 +16,7 @@ namespace Northbound
public List<BuildingData> availableBuildings = new List<BuildingData>();
[Header("Foundation Settings")]
public GameObject foundationPrefab; // 토대 프리팹 (Inspector에서 할당)
public GameObject foundationPrefab;
private List<Building> placedBuildings = new List<Building>();
private List<BuildingFoundation> placedFoundations = new List<BuildingFoundation>();
@@ -58,6 +58,53 @@ namespace Northbound
);
}
private bool ValidateClient(ulong clientId)
{
if (!NetworkManager.Singleton.ConnectedClients.ContainsKey(clientId))
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 클라이언트 ID: {clientId}</color>");
return false;
}
return true;
}
private bool ValidateBuildingIndex(int buildingIndex)
{
if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count)
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex}</color>");
return false;
}
return true;
}
private BuildingData GetBuildingData(int buildingIndex)
{
BuildingData data = availableBuildings[buildingIndex];
if (data == null || data.prefab == null)
{
Debug.LogWarning($"<color=red>[BuildingManager] 건물 데이터가 유효하지 않습니다.</color>");
return null;
}
return data;
}
private void SetupSpawnedObject(GameObject obj, ulong ownerId)
{
if (obj.GetComponent<FogOfWarVisibility>() == null)
{
var visibility = obj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = true;
visibility.updateInterval = 0.2f;
}
NetworkObject netObj = obj.GetComponent<NetworkObject>();
if (netObj != null)
{
netObj.SpawnWithOwnership(ownerId);
}
}
public bool IsValidPlacement(BuildingData data, Vector3 position, int rotation, out Vector3 groundPosition)
{
groundPosition = position;
@@ -141,8 +188,10 @@ namespace Northbound
{
groundPosition = position;
// Raycast down to find ground
if (Physics.Raycast(position + Vector3.up * 10f, Vector3.down, out RaycastHit hit, 20f, groundLayer))
float originHeight = 10f;
float rayDistance = 20f;
Vector3 rayOrigin = position + Vector3.up * originHeight;
if (Physics.Raycast(rayOrigin, Vector3.down, out RaycastHit hit, rayDistance, groundLayer))
{
groundPosition = hit.point;
return true;
@@ -169,30 +218,22 @@ namespace Northbound
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void PlaceBuildingServerRpc(int buildingIndex, Vector3 position, int rotation, ulong requestingClientId)
{
// 보안 검증 1: 유효한 클라이언트인지 확인
if (!NetworkManager.Singleton.ConnectedClients.ContainsKey(requestingClientId))
if (!ValidateClient(requestingClientId))
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 클라이언트 ID: {requestingClientId}</color>");
return;
}
// 보안 검증 2: 건물 인덱스 유효성 확인
if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count)
if (!ValidateBuildingIndex(buildingIndex))
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex} (클라이언트: {requestingClientId})</color>");
return;
}
BuildingData data = availableBuildings[buildingIndex];
// 보안 검증 3: 건물 데이터 유효성 확인
if (data == null || data.prefab == null)
BuildingData data = GetBuildingData(buildingIndex);
if (data == null)
{
Debug.LogWarning($"<color=red>[BuildingManager] 건물 데이터가 유효하지 않습니다. (클라이언트: {requestingClientId})</color>");
return;
}
// 배치 가능 여부 확인
if (!IsValidPlacement(data, position, rotation, out Vector3 snappedPosition))
{
Debug.LogWarning($"<color=yellow>[BuildingManager] 건물 배치 불가능 위치 (클라이언트: {requestingClientId})</color>");
@@ -200,41 +241,23 @@ namespace Northbound
}
Vector3Int gridPosition = WorldToGrid(snappedPosition);
// 건물 생성
GameObject buildingObj = Instantiate(data.prefab, snappedPosition + data.placementOffset, Quaternion.Euler(0, rotation * 90f, 0));
NetworkObject netObj = buildingObj.GetComponent<NetworkObject>();
// Add FogOfWarVisibility component to hide buildings in unexplored areas
if (buildingObj.GetComponent<FogOfWarVisibility>() == null)
SetupSpawnedObject(buildingObj, requestingClientId);
Building building = buildingObj.GetComponent<Building>();
if (building == null)
{
var visibility = buildingObj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = true; // Buildings remain visible in explored areas
visibility.updateInterval = 0.2f;
}
if (netObj != null)
{
// 건물의 소유자를 설정
netObj.SpawnWithOwnership(requestingClientId);
Building building = buildingObj.GetComponent<Building>();
if (building == null)
{
building = buildingObj.AddComponent<Building>();
}
// 건물 초기화
building.Initialize(data, gridPosition, rotation, requestingClientId);
placedBuildings.Add(building);
Debug.Log($"<color=green>[BuildingManager] {data.buildingName} 건설 완료 (소유자: {requestingClientId}, 위치: {gridPosition})</color>");
}
else
{
Debug.LogError($"<color=red>[BuildingManager] NetworkObject 컴포넌트가 없습니다! (Prefab: {data.prefab.name})</color>");
Debug.LogError($"<color=red>[BuildingManager] Building prefab must have Building component! (Prefab: {data.prefab.name})</color>");
buildingObj.GetComponent<NetworkObject>()?.Despawn(true);
Destroy(buildingObj);
return;
}
building.Initialize(data, gridPosition, rotation, requestingClientId);
placedBuildings.Add(building);
Debug.Log($"<color=green>[BuildingManager] {data.buildingName} 건설 완료 (소유자: {requestingClientId}, 위치: {gridPosition})</color>");
}
public void RemoveBuilding(Building building)
@@ -321,37 +344,28 @@ namespace Northbound
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void PlaceFoundationServerRpc(int buildingIndex, Vector3 position, int rotation, ulong requestingClientId)
{
// 보안 검증 1: 유효한 클라이언트인지 확인
if (!NetworkManager.Singleton.ConnectedClients.ContainsKey(requestingClientId))
if (!ValidateClient(requestingClientId))
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 클라이언트 ID: {requestingClientId}</color>");
return;
}
// 보안 검증 2: 건물 인덱스 유효성 확인
if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count)
if (!ValidateBuildingIndex(buildingIndex))
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex}</color>");
return;
}
BuildingData data = availableBuildings[buildingIndex];
// 보안 검증 3: 건물 데이터 유효성 확인
BuildingData data = GetBuildingData(buildingIndex);
if (data == null)
{
Debug.LogWarning($"<color=red>[BuildingManager] 건물 데이터가 유효하지 않습니다.</color>");
return;
}
// 토대 프리팹 확인
if (foundationPrefab == null)
{
Debug.LogError("<color=red>[BuildingManager] foundationPrefab이 설정되지 않았습니다!</color>");
return;
}
// 배치 가능 여부 확인
if (!IsValidPlacement(data, position, rotation, out Vector3 snappedPosition))
{
Debug.LogWarning($"<color=yellow>[BuildingManager] 토대 배치 불가능 위치</color>");
@@ -359,43 +373,23 @@ namespace Northbound
}
Vector3Int gridPosition = WorldToGrid(snappedPosition);
// 플레이어 팀 가져오기
TeamType playerTeam = GetPlayerTeam(requestingClientId);
// 토대 생성
GameObject foundationObj = Instantiate(foundationPrefab, snappedPosition + data.placementOffset, Quaternion.Euler(0, rotation * 90f, 0));
NetworkObject netObj = foundationObj.GetComponent<NetworkObject>();
// Add FogOfWarVisibility component to hide foundations in unexplored areas
if (foundationObj.GetComponent<FogOfWarVisibility>() == null)
SetupSpawnedObject(foundationObj, requestingClientId);
BuildingFoundation foundation = foundationObj.GetComponent<BuildingFoundation>();
if (foundation != null)
{
var visibility = foundationObj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = true; // Foundations remain visible in explored areas
visibility.updateInterval = 0.2f;
}
if (netObj != null)
{
netObj.SpawnWithOwnership(requestingClientId);
BuildingFoundation foundation = foundationObj.GetComponent<BuildingFoundation>();
if (foundation != null)
{
foundation.Initialize(data, gridPosition, rotation, requestingClientId, playerTeam);
placedFoundations.Add(foundation); // 토대 목록에 추가
Debug.Log($"<color=yellow>[BuildingManager] {data.buildingName} 토대 생성 (소유자: {requestingClientId}, 위치: {gridPosition})</color>");
}
else
{
Debug.LogError("<color=red>[BuildingManager] BuildingFoundation 컴포넌트가 없습니다!</color>");
netObj.Despawn(true);
}
foundation.Initialize(data, gridPosition, rotation, requestingClientId, playerTeam);
placedFoundations.Add(foundation);
Debug.Log($"<color=yellow>[BuildingManager] {data.buildingName} 토대 생성 (소유자: {requestingClientId}, 위치: {gridPosition})</color>");
}
else
{
Debug.LogError("<color=red>[BuildingManager] NetworkObject 컴포넌트가 없습니다!</color>");
Destroy(foundationObj);
Debug.LogError("<color=red>[BuildingManager] BuildingFoundation 컴포넌트가 없습니다!</color>");
foundationObj.GetComponent<NetworkObject>()?.Despawn(true);
}
}
@@ -427,7 +421,6 @@ namespace Northbound
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
public void SpawnCompletedBuildingServerRpc(string buildingDataName, Vector3Int gridPosition, int rotation, ulong ownerId, TeamType team)
{
// BuildingData 찾기
BuildingData data = availableBuildings.Find(b => b.name == buildingDataName);
if (data == null || data.prefab == null)
{
@@ -436,40 +429,23 @@ namespace Northbound
}
Vector3 worldPosition = GridToWorld(gridPosition);
// 완성된 건물 생성
GameObject buildingObj = Instantiate(data.prefab, worldPosition + data.placementOffset, Quaternion.Euler(0, rotation * 90f, 0));
NetworkObject netObj = buildingObj.GetComponent<NetworkObject>();
// Add FogOfWarVisibility component to hide buildings in unexplored areas
if (buildingObj.GetComponent<FogOfWarVisibility>() == null)
SetupSpawnedObject(buildingObj, ownerId);
Building building = buildingObj.GetComponent<Building>();
if (building == null)
{
var visibility = buildingObj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = true; // Buildings remain visible in explored areas
visibility.updateInterval = 0.2f;
}
if (netObj != null)
{
netObj.SpawnWithOwnership(ownerId);
Building building = buildingObj.GetComponent<Building>();
if (building == null)
{
building = buildingObj.AddComponent<Building>();
}
// 건물 초기화
building.Initialize(data, gridPosition, rotation, ownerId);
placedBuildings.Add(building);
Debug.Log($"<color=green>[BuildingManager] {data.buildingName} 건설 완료! (소유자: {ownerId}, 위치: {gridPosition}, 팀: {team})</color>");
}
else
{
Debug.LogError($"<color=red>[BuildingManager] NetworkObject 컴포넌트가 없습니다!</color>");
Debug.LogError($"<color=red>[BuildingManager] Building prefab must have Building component! (Prefab: {data.prefab.name})</color>");
buildingObj.GetComponent<NetworkObject>()?.Despawn(true);
Destroy(buildingObj);
return;
}
building.Initialize(data, gridPosition, rotation, ownerId);
placedBuildings.Add(building);
Debug.Log($"<color=green>[BuildingManager] {data.buildingName} 건설 완료! (소유자: {ownerId}, 위치: {gridPosition}, 팀: {team})</color>");
}
}
}