건설 토대 및 상호작용 시스템 추가
This commit is contained in:
@@ -23,6 +23,18 @@ namespace Northbound
|
||||
[Tooltip("Can rotate this building?")]
|
||||
public bool allowRotation = true;
|
||||
|
||||
[Header("Construction Settings")]
|
||||
[Tooltip("건설 완료에 필요한 총 작업량")]
|
||||
public float requiredWorkAmount = 100f;
|
||||
[Tooltip("1회 상호작용당 작업량")]
|
||||
public float workPerInteraction = 10f;
|
||||
[Tooltip("상호작용 쿨다운 (초)")]
|
||||
public float interactionCooldown = 1f;
|
||||
[Tooltip("건설 시 플레이어가 재생할 애니메이션 트리거 (예: Build, Hammer, Construct)")]
|
||||
public string constructionAnimationTrigger = "Build";
|
||||
[Tooltip("건설 시 사용할 도구 (선택사항)")]
|
||||
public InteractionEquipmentData constructionEquipment;
|
||||
|
||||
[Header("Health Settings")]
|
||||
[Tooltip("Maximum health of the building")]
|
||||
public int maxHealth = 100;
|
||||
|
||||
353
Assets/Scripts/BuildingFoundation.cs
Normal file
353
Assets/Scripts/BuildingFoundation.cs
Normal file
@@ -0,0 +1,353 @@
|
||||
using System;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Northbound
|
||||
{
|
||||
/// <summary>
|
||||
/// 건물 토대 - 플레이어가 상호작용하여 건물을 완성시킴
|
||||
/// </summary>
|
||||
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<float> _currentProgress = new NetworkVariable<float>(
|
||||
0f,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
// 건물 소유자
|
||||
private NetworkVariable<ulong> _ownerId = new NetworkVariable<ulong>(
|
||||
0,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
// 팀
|
||||
private NetworkVariable<TeamType> _team = new NetworkVariable<TeamType>(
|
||||
TeamType.Neutral,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
// 이벤트
|
||||
public event Action<float, float> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 토대 초기화
|
||||
/// </summary>
|
||||
public void Initialize(BuildingData data, Vector3Int pos, int rot, ulong ownerId, TeamType team)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
buildingData = data;
|
||||
gridPosition = pos;
|
||||
rotation = rot;
|
||||
_ownerId.Value = ownerId;
|
||||
_team.Value = team;
|
||||
_currentProgress.Value = 0f;
|
||||
|
||||
// BuildingData의 크기를 기반으로 스케일 설정
|
||||
Vector3 size = data.GetSize(rot);
|
||||
|
||||
// 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<BoxCollider>();
|
||||
if (_collider == null)
|
||||
{
|
||||
_collider = gameObject.AddComponent<BoxCollider>();
|
||||
}
|
||||
|
||||
// 상호작용 가능한 크기로 설정 (전체 건물 높이가 아닌 접근 가능한 크기)
|
||||
_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($"<color=yellow>[BuildingFoundation] 토대 생성: {data.buildingName}, 크기: {size}, 위치: {transform.position}, Collider: {_collider.size}, 소유자: {ownerId}, 팀: {team}</color>");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 토대의 그리드 경계 가져오기 (BuildingManager의 충돌 체크용)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
#region IInteractable Implementation
|
||||
|
||||
public bool CanInteract(ulong playerId)
|
||||
{
|
||||
if (buildingData == null)
|
||||
{
|
||||
Debug.LogWarning($"[BuildingFoundation] buildingData is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 쿨다운 체크
|
||||
if (Time.time - _lastInteractionTime < buildingData.interactionCooldown)
|
||||
{
|
||||
Debug.Log($"[BuildingFoundation] Cooldown active: {Time.time - _lastInteractionTime}/{buildingData.interactionCooldown}");
|
||||
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;
|
||||
}
|
||||
|
||||
Debug.Log($"<color=green>[BuildingFoundation] CanInteract = true</color>");
|
||||
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($"<color=green>[BuildingFoundation] 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount} ({(_currentProgress.Value / buildingData.requiredWorkAmount * 100f):F1}%)</color>");
|
||||
|
||||
// 완성 체크
|
||||
if (_currentProgress.Value >= buildingData.requiredWorkAmount)
|
||||
{
|
||||
CompleteConstruction();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetInteractionPrompt()
|
||||
{
|
||||
if (buildingData == null)
|
||||
return "[E] 건설하기";
|
||||
|
||||
float percentage = (_currentProgress.Value / buildingData.requiredWorkAmount) * 100f;
|
||||
return $"[E] 건설하기 ({percentage:F0}%)";
|
||||
}
|
||||
|
||||
public string GetInteractionAnimation()
|
||||
{
|
||||
// BuildingData에서 애니메이션 트리거 가져오기
|
||||
if (buildingData != null && !string.IsNullOrEmpty(buildingData.constructionAnimationTrigger))
|
||||
{
|
||||
return buildingData.constructionAnimationTrigger;
|
||||
}
|
||||
|
||||
// 기본값: 빈 문자열 (애니메이션 없음)
|
||||
return "";
|
||||
}
|
||||
|
||||
public InteractionEquipmentData GetEquipmentData()
|
||||
{
|
||||
// BuildingData에 건설 도구가 정의되어 있으면 반환
|
||||
if (buildingData != null && buildingData.constructionEquipment != null)
|
||||
{
|
||||
return buildingData.constructionEquipment;
|
||||
}
|
||||
|
||||
return null; // 특별한 도구 불필요
|
||||
}
|
||||
|
||||
public Transform GetTransform()
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ITeamMember Implementation
|
||||
|
||||
public TeamType GetTeam()
|
||||
{
|
||||
return _team.Value;
|
||||
}
|
||||
|
||||
public void SetTeam(TeamType team)
|
||||
{
|
||||
if (!IsServer) return;
|
||||
_team.Value = team;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
private void CompleteConstruction()
|
||||
{
|
||||
if (!IsServer) return;
|
||||
|
||||
Debug.Log($"<color=cyan>[BuildingFoundation] 건물 완성! {buildingData.buildingName}</color>");
|
||||
|
||||
OnConstructionComplete?.Invoke();
|
||||
|
||||
// BuildingManager에서 토대 제거
|
||||
var buildingManager = BuildingManager.Instance;
|
||||
if (buildingManager != null)
|
||||
{
|
||||
buildingManager.RemoveFoundation(this);
|
||||
}
|
||||
|
||||
// 완성된 건물 생성
|
||||
SpawnCompletedBuilding();
|
||||
|
||||
// 토대 제거
|
||||
if (NetworkObject != null)
|
||||
{
|
||||
NetworkObject.Despawn(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnCompletedBuilding()
|
||||
{
|
||||
if (!IsServer || buildingData == null || buildingData.prefab == null)
|
||||
return;
|
||||
|
||||
// BuildingManager를 통해 건물 생성
|
||||
var buildingManager = BuildingManager.Instance;
|
||||
if (buildingManager != null)
|
||||
{
|
||||
buildingManager.SpawnCompletedBuildingServerRpc(
|
||||
buildingData.name,
|
||||
gridPosition,
|
||||
rotation,
|
||||
_ownerId.Value,
|
||||
_team.Value
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[BuildingFoundation] BuildingManager를 찾을 수 없습니다!");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnProgressValueChanged(float oldValue, float newValue)
|
||||
{
|
||||
if (buildingData != null)
|
||||
{
|
||||
OnProgressChanged?.Invoke(newValue, buildingData.requiredWorkAmount);
|
||||
}
|
||||
UpdateProgressBar();
|
||||
}
|
||||
|
||||
private void UpdateProgressBar()
|
||||
{
|
||||
if (_progressBarInstance == null || buildingData == null) return;
|
||||
|
||||
// 진행바 UI 업데이트 (BuildingHealthBar와 유사한 구조 사용 가능)
|
||||
var progressBar = _progressBarInstance.GetComponent<BuildingHealthBar>();
|
||||
if (progressBar != null)
|
||||
{
|
||||
// BuildingHealthBar를 재사용하여 진행도 표시
|
||||
progressBar.UpdateHealth((int)_currentProgress.Value, (int)buildingData.requiredWorkAmount);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (buildingData == null) return;
|
||||
|
||||
// 건물 경계 표시 (노란색)
|
||||
Gizmos.color = Color.yellow;
|
||||
Vector3 size = buildingData.GetSize(rotation);
|
||||
Gizmos.DrawWireCube(transform.position + Vector3.up * size.y * 0.5f, size);
|
||||
|
||||
// Collider 경계 표시 (초록색)
|
||||
if (_collider != null)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawWireCube(transform.position + _collider.center, _collider.size);
|
||||
}
|
||||
|
||||
// 상호작용 가능 여부 표시
|
||||
if (_currentProgress.Value < (buildingData?.requiredWorkAmount ?? 100f))
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawSphere(transform.position + Vector3.up, 0.3f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/BuildingFoundation.cs.meta
Normal file
2
Assets/Scripts/BuildingFoundation.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cad87caf8c43a4b41911ae978c10ae0e
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ namespace Northbound
|
||||
return;
|
||||
}
|
||||
|
||||
// 완성 건물 프리팹으로 프리뷰 생성 (사용자가 완성 모습을 볼 수 있도록)
|
||||
previewObject = Instantiate(data.prefab);
|
||||
|
||||
// Remove NetworkObject component from preview
|
||||
@@ -162,6 +163,13 @@ namespace Northbound
|
||||
Destroy(netObj);
|
||||
}
|
||||
|
||||
// Remove Building component from preview
|
||||
Building building = previewObject.GetComponent<Building>();
|
||||
if (building != null)
|
||||
{
|
||||
Destroy(building);
|
||||
}
|
||||
|
||||
// Apply ghost materials
|
||||
previewRenderers = previewObject.GetComponentsInChildren<Renderer>();
|
||||
foreach (var renderer in previewRenderers)
|
||||
@@ -204,7 +212,7 @@ namespace Northbound
|
||||
// Check if placement is valid
|
||||
bool isValid = BuildingManager.Instance.IsValidPlacement(data, hit.point, currentRotation, out Vector3 snappedPosition);
|
||||
|
||||
// Update preview position
|
||||
// Update preview position (placementOffset 적용)
|
||||
previewObject.transform.position = snappedPosition + data.placementOffset;
|
||||
previewObject.transform.rotation = Quaternion.Euler(0, currentRotation * 90f, 0);
|
||||
|
||||
@@ -231,23 +239,25 @@ namespace Northbound
|
||||
|
||||
private void OnBuild(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!isBuildModeActive || previewObject == null || BuildingManager.Instance == null)
|
||||
return;
|
||||
if (!isBuildModeActive || previewObject == null) return;
|
||||
|
||||
BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
|
||||
Vector3 placementPosition = previewObject.transform.position - data.placementOffset;
|
||||
|
||||
// Validate placement one more time
|
||||
if (BuildingManager.Instance.IsValidPlacement(data, placementPosition, currentRotation, out Vector3 snappedPosition))
|
||||
// Get placement position
|
||||
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
|
||||
{
|
||||
// 🔥 변경: PlaceBuildingServerRpc 대신 RequestPlaceBuilding 호출
|
||||
BuildingManager.Instance.RequestPlaceBuilding(selectedBuildingIndex, snappedPosition, currentRotation);
|
||||
BuildingData selectedData = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
|
||||
|
||||
Debug.Log($"<color=cyan>[BuildingPlacement] 건물 배치 요청: {data.buildingName}</color>");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("<color=yellow>[BuildingPlacement] 건물을 배치할 수 없는 위치입니다.</color>");
|
||||
if (BuildingManager.Instance.IsValidPlacement(selectedData, hit.point, currentRotation, out Vector3 groundPosition))
|
||||
{
|
||||
// 토대 배치 요청 (실제로는 토대가 생성됨)
|
||||
BuildingManager.Instance.RequestPlaceFoundation(selectedBuildingIndex, groundPosition, currentRotation);
|
||||
|
||||
Debug.Log($"<color=green>[BuildingPlacement] 토대 배치 요청: {selectedData.buildingName}</color>");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("<color=yellow>[BuildingPlacement] 배치할 수 없는 위치입니다.</color>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,11 @@ namespace Northbound
|
||||
{
|
||||
_isInteracting = true;
|
||||
_pendingEquipmentData = _currentInteractable.GetEquipmentData();
|
||||
|
||||
string animTrigger = _currentInteractable.GetInteractionAnimation();
|
||||
bool hasAnimation = !string.IsNullOrEmpty(animTrigger);
|
||||
|
||||
// 장비 장착 (애니메이션 이벤트 사용 안 할 경우)
|
||||
if (!useAnimationEvents && useEquipment && _equipmentSocket != null && _pendingEquipmentData != null)
|
||||
{
|
||||
if (_pendingEquipmentData.attachOnStart && _pendingEquipmentData.equipmentPrefab != null)
|
||||
@@ -123,15 +127,18 @@ namespace Northbound
|
||||
}
|
||||
}
|
||||
|
||||
if (playAnimations && _animator != null)
|
||||
// 애니메이션 재생
|
||||
if (playAnimations && _animator != null && hasAnimation)
|
||||
{
|
||||
string animTrigger = _currentInteractable.GetInteractionAnimation();
|
||||
if (!string.IsNullOrEmpty(animTrigger))
|
||||
{
|
||||
_animator.SetTrigger(animTrigger);
|
||||
}
|
||||
_animator.SetTrigger(animTrigger);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 애니메이션이 없으면 즉시 상호작용 완료
|
||||
_isInteracting = false;
|
||||
}
|
||||
|
||||
// 상호작용 실행 (서버에서 처리)
|
||||
_currentInteractable.Interact(OwnerClientId);
|
||||
}
|
||||
}
|
||||
@@ -167,6 +174,7 @@ namespace Northbound
|
||||
public void OnInteractionComplete()
|
||||
{
|
||||
_isInteracting = false;
|
||||
Debug.Log("[PlayerInteraction] 상호작용 완료");
|
||||
}
|
||||
|
||||
// ========================================
|
||||
|
||||
Reference in New Issue
Block a user