179 lines
6.5 KiB
C#
179 lines
6.5 KiB
C#
using UnityEngine;
|
|
using Unity.Netcode;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.EventSystems;
|
|
using System.Collections.Generic;
|
|
|
|
public class BuildManager : NetworkBehaviour
|
|
{
|
|
public static BuildManager Instance;
|
|
|
|
[System.Serializable]
|
|
public struct TurretData
|
|
{
|
|
public string turretName;
|
|
public GameObject finalPrefab;
|
|
public GameObject ghostPrefab;
|
|
public float buildTime;
|
|
}
|
|
|
|
[Header("Uniform Grid Settings")]
|
|
public float cellSize = 1f;
|
|
[SerializeField] private float pivotOffset = 0.5f; // 블록/터널 에셋의 중심점
|
|
|
|
[Header("Tunnel Settings")]
|
|
public int tunnelLengthInBlocks = 3;
|
|
|
|
[Header("Prefabs & Layers")]
|
|
[SerializeField] private GameObject constructionSitePrefab;
|
|
[SerializeField] private List<TurretData> turretLibrary = new List<TurretData>();
|
|
[SerializeField] private LayerMask groundLayer;
|
|
[SerializeField] private LayerMask tunnelLayer;
|
|
|
|
private int _selectedTurretIndex = 0;
|
|
private bool _isBuildMode = false;
|
|
private GameObject _ghostInstance;
|
|
private Vector3Int _currentGridPos;
|
|
private PlayerInputActions _inputActions;
|
|
|
|
private Dictionary<Vector3Int, TunnelNode> _tunnelRegistry = new Dictionary<Vector3Int, TunnelNode>();
|
|
private HashSet<Vector3Int> _occupiedNodes = new HashSet<Vector3Int>();
|
|
|
|
void Awake()
|
|
{
|
|
if (Instance == null) Instance = this;
|
|
else Destroy(gameObject);
|
|
_inputActions = new PlayerInputActions();
|
|
}
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
_inputActions.Player.ToggleBuild.performed += ctx => ToggleBuildMode();
|
|
_inputActions.Player.Build.performed += ctx => OnBuildRequested();
|
|
_inputActions.Player.Cancel.performed += ctx => ExitBuildMode();
|
|
|
|
_inputActions.Player.Select1.performed += ctx => SelectTurret(0);
|
|
_inputActions.Player.Select2.performed += ctx => SelectTurret(1);
|
|
_inputActions.Player.Select3.performed += ctx => SelectTurret(2);
|
|
|
|
_inputActions.Enable();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (!_isBuildMode || _ghostInstance == null) return;
|
|
UpdateGhostPosition();
|
|
}
|
|
|
|
private void UpdateGhostPosition()
|
|
{
|
|
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
|
|
|
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, groundLayer | tunnelLayer))
|
|
{
|
|
Vector3 targetPos;
|
|
|
|
// 터널 조준 시: 복잡한 계산 없이 X, Z는 유지, Y만 3 낮춤
|
|
if (((1 << hit.collider.gameObject.layer) & tunnelLayer) != 0)
|
|
{
|
|
Vector3 parentPos = hit.collider.transform.position;
|
|
targetPos = new Vector3(parentPos.x, parentPos.y - (tunnelLengthInBlocks * cellSize), parentPos.z);
|
|
}
|
|
else
|
|
{
|
|
// 지형 조준 시: 격자에 맞춤
|
|
_currentGridPos = WorldToGrid3D(hit.point + hit.normal * 0.01f);
|
|
targetPos = GridToWorld(_currentGridPos);
|
|
}
|
|
|
|
_currentGridPos = WorldToGrid3D(targetPos);
|
|
_ghostInstance.transform.position = targetPos;
|
|
}
|
|
}
|
|
|
|
// 모든 좌표를 반올림하여 0.5 단위 오차를 제거
|
|
public Vector3Int WorldToGrid3D(Vector3 worldPos)
|
|
{
|
|
return new Vector3Int(
|
|
Mathf.RoundToInt((worldPos.x - pivotOffset) / cellSize),
|
|
Mathf.RoundToInt((worldPos.y - pivotOffset) / cellSize),
|
|
Mathf.RoundToInt((worldPos.z - pivotOffset) / cellSize)
|
|
);
|
|
}
|
|
|
|
public Vector3 GridToWorld(Vector3Int gridPos)
|
|
{
|
|
return new Vector3(
|
|
(gridPos.x * cellSize) + pivotOffset,
|
|
(gridPos.y * cellSize) + pivotOffset,
|
|
(gridPos.z * cellSize) + pivotOffset
|
|
);
|
|
}
|
|
|
|
// [에러 해결] ConstructionSite.cs 참조용
|
|
public TurretData GetTurretData(int index)
|
|
{
|
|
if (index < 0 || index >= turretLibrary.Count) return default;
|
|
return turretLibrary[index];
|
|
}
|
|
|
|
// BuildManager.cs 내 핵심 수정 부분
|
|
// [수정] 서버와 클라이언트 모두에서 터널 위치를 기록하도록 함
|
|
// BuildManager.cs 내부
|
|
public void RegisterTunnel(Vector3Int topPos, TunnelNode node)
|
|
{
|
|
// 터널이 점유하는 Y, Y-1, Y-2 세 칸 모두에 노드를 등록합니다.
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
Vector3Int cellPos = topPos + (Vector3Int.down * i);
|
|
if (!_tunnelRegistry.ContainsKey(cellPos))
|
|
{
|
|
_tunnelRegistry.Add(cellPos, node);
|
|
}
|
|
}
|
|
}
|
|
|
|
// [수정] 특정 좌표의 터널을 찾는 공용 함수
|
|
public TunnelNode GetTunnelAt(Vector3Int pos)
|
|
{
|
|
return _tunnelRegistry.GetValueOrDefault(pos);
|
|
}
|
|
|
|
// 1. 건설 요청 시 실제 계산된 worldPos를 넘겨줍니다.
|
|
private void OnBuildRequested()
|
|
{
|
|
if (!_isBuildMode || EventSystem.current.IsPointerOverGameObject()) return;
|
|
|
|
// 고스트가 현재 위치한 '그 좌표'를 그대로 보냅니다.
|
|
RequestBuildRpc(_selectedTurretIndex, _currentGridPos, _ghostInstance.transform.position);
|
|
ExitBuildMode();
|
|
}
|
|
|
|
// 2. RPC에서 넘겨받은 worldPos를 사용하여 토대를 생성합니다.
|
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
private void RequestBuildRpc(int index, Vector3Int gridPos, Vector3 worldPos)
|
|
{
|
|
// GridToWorld를 다시 계산하지 않고 전달받은 worldPos를 그대로 사용합니다.
|
|
GameObject siteObj = Instantiate(constructionSitePrefab, worldPos, Quaternion.identity);
|
|
siteObj.GetComponent<NetworkObject>().Spawn();
|
|
|
|
ConstructionSite site = siteObj.GetComponent<ConstructionSite>();
|
|
if (site != null) site.Initialize(index, gridPos);
|
|
}
|
|
|
|
public void SelectTurret(int index)
|
|
{
|
|
if (index < 0 || index >= turretLibrary.Count) return;
|
|
_selectedTurretIndex = index;
|
|
if (_isBuildMode && _ghostInstance != null)
|
|
{
|
|
Destroy(_ghostInstance);
|
|
_ghostInstance = Instantiate(turretLibrary[_selectedTurretIndex].ghostPrefab);
|
|
}
|
|
}
|
|
|
|
private void ToggleBuildMode() { if (_isBuildMode) ExitBuildMode(); else EnterBuildMode(); }
|
|
private void EnterBuildMode() { _isBuildMode = true; if (turretLibrary.Count > 0) _ghostInstance = Instantiate(turretLibrary[_selectedTurretIndex].ghostPrefab); }
|
|
private void ExitBuildMode() { _isBuildMode = false; if (_ghostInstance) Destroy(_ghostInstance); }
|
|
public override void OnNetworkDespawn() => _inputActions.Disable();
|
|
} |