건설 토대 및 상호작용 시스템 추가

This commit is contained in:
2026-01-27 22:50:57 +09:00
parent 805d526b27
commit 387991caab
17 changed files with 1779 additions and 184 deletions

View File

@@ -15,7 +15,11 @@ namespace Northbound
[Header("Building Database")]
public List<BuildingData> availableBuildings = new List<BuildingData>();
[Header("Foundation Settings")]
public GameObject foundationPrefab; // 토대 프리팹 (Inspector에서 할당)
private List<Building> placedBuildings = new List<Building>();
private List<BuildingFoundation> placedFoundations = new List<BuildingFoundation>();
private void Awake()
{
@@ -70,11 +74,18 @@ namespace Northbound
// Overlap check using GRID SIZE from BuildingData (not actual colliders)
Vector3 gridSize = data.GetSize(rotation);
// 프리팹의 실제 배치 위치 계산 (placementOffset 포함)
Vector3 actualBuildingPosition = snappedPosition + data.placementOffset;
// Bounds의 중심을 실제 건물/토대 위치를 기준으로 계산
Vector3 boundsCenter = actualBuildingPosition + Vector3.up * gridSize.y * 0.5f;
// Shrink bounds slightly to allow buildings to touch without overlapping
// This prevents Bounds.Intersects() from returning true for adjacent buildings
Vector3 shrunkSize = gridSize - Vector3.one * 0.01f;
Bounds checkBounds = new Bounds(snappedPosition + Vector3.up * gridSize.y * 0.5f, shrunkSize);
Bounds checkBounds = new Bounds(boundsCenter, shrunkSize);
// 기존 건물과의 충돌 체크
foreach (var building in placedBuildings)
{
if (building == null) continue;
@@ -86,6 +97,17 @@ namespace Northbound
return false;
}
// 토대와의 충돌 체크
foreach (var foundation in placedFoundations)
{
if (foundation == null) continue;
Bounds foundationGridBounds = foundation.GetGridBounds();
if (checkBounds.Intersects(foundationGridBounds))
return false;
}
return true;
}
@@ -96,6 +118,7 @@ namespace Northbound
public Bounds GetPlacementBounds(BuildingData data, Vector3 position, int rotation)
{
Vector3 gridSize = data.GetSize(rotation);
// position은 이미 placementOffset이 적용된 위치
return new Bounds(position + Vector3.up * gridSize.y * 0.5f, gridSize);
}
@@ -200,6 +223,18 @@ namespace Northbound
}
}
/// <summary>
/// 토대 제거 (건설 완료 또는 취소 시)
/// </summary>
public void RemoveFoundation(BuildingFoundation foundation)
{
if (placedFoundations.Contains(foundation))
{
placedFoundations.Remove(foundation);
Debug.Log($"<color=yellow>[BuildingManager] 토대 제거됨: {foundation.buildingData?.buildingName ?? "Unknown"}</color>");
}
}
/// <summary>
/// 사전 배치 건물 등록 (씬에 미리 있는 건물용)
/// </summary>
@@ -244,5 +279,158 @@ namespace Northbound
return ownedBuildings;
}
/// <summary>
/// 건물 토대 배치 요청 (클라이언트에서 호출)
/// </summary>
public void RequestPlaceFoundation(int buildingIndex, Vector3 position, int rotation)
{
if (!NetworkManager.Singleton.IsClient)
{
Debug.LogWarning("[BuildingManager] 클라이언트가 아닙니다.");
return;
}
ulong clientId = NetworkManager.Singleton.LocalClientId;
PlaceFoundationServerRpc(buildingIndex, position, rotation, clientId);
}
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void PlaceFoundationServerRpc(int buildingIndex, Vector3 position, int rotation, ulong requestingClientId)
{
// 보안 검증 1: 유효한 클라이언트인지 확인
if (!NetworkManager.Singleton.ConnectedClients.ContainsKey(requestingClientId))
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 클라이언트 ID: {requestingClientId}</color>");
return;
}
// 보안 검증 2: 건물 인덱스 유효성 확인
if (buildingIndex < 0 || buildingIndex >= availableBuildings.Count)
{
Debug.LogWarning($"<color=red>[BuildingManager] 유효하지 않은 건물 인덱스: {buildingIndex}</color>");
return;
}
BuildingData data = availableBuildings[buildingIndex];
// 보안 검증 3: 건물 데이터 유효성 확인
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>");
return;
}
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>();
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);
}
}
else
{
Debug.LogError("<color=red>[BuildingManager] NetworkObject 컴포넌트가 없습니다!</color>");
Destroy(foundationObj);
}
}
/// <summary>
/// 플레이어의 팀 가져오기
/// </summary>
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<ITeamMember>();
if (teamMember != null)
{
return teamMember.GetTeam();
}
}
}
// 기본값: 플레이어 팀
return TeamType.Player;
}
/// <summary>
/// 건설 완료 시 완성된 건물 생성 (BuildingFoundation에서 호출)
/// </summary>
[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)
{
Debug.LogError($"<color=red>[BuildingManager] 건물 데이터를 찾을 수 없습니다: {buildingDataName}</color>");
return;
}
Vector3 worldPosition = GridToWorld(gridPosition);
// 완성된 건물 생성
GameObject buildingObj = Instantiate(data.prefab, worldPosition + data.placementOffset, Quaternion.Euler(0, rotation * 90f, 0));
NetworkObject netObj = buildingObj.GetComponent<NetworkObject>();
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>");
Destroy(buildingObj);
}
}
}
}