213 lines
7.0 KiB
C#
213 lines
7.0 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; // NetworkObject 필수
|
|
public GameObject ghostPrefab; // 로컬 프리뷰용
|
|
public float buildTime;
|
|
}
|
|
|
|
[Header("Grid Settings")]
|
|
public float cellSize = 1f;
|
|
public float tunnelHeight = 3f;
|
|
[SerializeField] private float yOffset = 0.5f; // 터널 중심점 오프셋
|
|
|
|
[Header("Prefabs & Layers")]
|
|
[SerializeField] private GameObject constructionSitePrefab;
|
|
[SerializeField] private List<TurretData> turretLibrary = new List<TurretData>();
|
|
[SerializeField] private LayerMask groundLayer;
|
|
[SerializeField] private LayerMask playerLayer;
|
|
|
|
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()
|
|
{
|
|
// 최신 Action-based 이벤트 바인딩
|
|
_inputActions.Player.ToggleBuild.performed += ctx => ToggleBuildMode();
|
|
_inputActions.Player.Build.performed += ctx => OnBuildRequested();
|
|
_inputActions.Player.Cancel.performed += ctx => ExitBuildMode();
|
|
|
|
// 숫자키 슬롯 선택 (Input Action 에셋 설정에 따라 Select1, Select2... 등 사용)
|
|
_inputActions.Player.Select1.performed += ctx => SelectTurret(0);
|
|
_inputActions.Player.Select2.performed += ctx => SelectTurret(1);
|
|
_inputActions.Player.Select3.performed += ctx => SelectTurret(2);
|
|
|
|
_inputActions.Enable();
|
|
|
|
// 시작 시 씬에 배치된 터널 자동 등록
|
|
RegisterAllExistingTunnels();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (!_isBuildMode || _ghostInstance == null) return;
|
|
UpdateGhostPosition();
|
|
}
|
|
|
|
#region Selection & Build Logic
|
|
|
|
public void SelectTurret(int index)
|
|
{
|
|
if (index < 0 || index >= turretLibrary.Count) return;
|
|
|
|
_selectedTurretIndex = index;
|
|
Debug.Log($"타워 선택: {turretLibrary[_selectedTurretIndex].turretName}");
|
|
|
|
if (_isBuildMode)
|
|
{
|
|
if (_ghostInstance != null) Destroy(_ghostInstance);
|
|
_ghostInstance = Instantiate(turretLibrary[_selectedTurretIndex].ghostPrefab);
|
|
_ghostInstance.transform.position = GridToWorld(_currentGridPos);
|
|
}
|
|
}
|
|
|
|
private void UpdateGhostPosition()
|
|
{
|
|
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
|
int tunnelMask = LayerMask.GetMask("Tunnel");
|
|
|
|
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, groundLayer | tunnelMask))
|
|
{
|
|
Vector3Int gridPos = WorldToGrid3D(hit.point);
|
|
|
|
// 터널 조준 시 해당 터널의 한 칸 아래로 스냅
|
|
if (((1 << hit.collider.gameObject.layer) & tunnelMask) != 0)
|
|
{
|
|
gridPos = WorldToGrid3D(hit.collider.transform.position) + Vector3Int.down;
|
|
}
|
|
|
|
_currentGridPos = gridPos;
|
|
_ghostInstance.transform.position = GridToWorld(gridPos);
|
|
}
|
|
}
|
|
|
|
private void OnBuildRequested()
|
|
{
|
|
if (!_isBuildMode || EventSystem.current.IsPointerOverGameObject()) return;
|
|
|
|
// 서버에 건설 가능 여부 확인 및 생성 요청
|
|
RequestBuildRpc(_selectedTurretIndex, _currentGridPos);
|
|
|
|
ExitBuildMode();
|
|
}
|
|
|
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
private void RequestBuildRpc(int index, Vector3Int gridPos)
|
|
{
|
|
// 서버 측 중복 점유 확인
|
|
if (_occupiedNodes.Contains(gridPos)) return;
|
|
|
|
Vector3 spawnPos = GridToWorld(gridPos);
|
|
|
|
// 1. 토대 생성 및 네트워크 스폰
|
|
GameObject siteObj = Instantiate(constructionSitePrefab, spawnPos, Quaternion.identity);
|
|
siteObj.GetComponent<NetworkObject>().Spawn();
|
|
|
|
// 2. 토대 데이터 초기화
|
|
ConstructionSite site = siteObj.GetComponent<ConstructionSite>();
|
|
if (site != null)
|
|
{
|
|
site.Initialize(index, gridPos);
|
|
}
|
|
|
|
_occupiedNodes.Add(gridPos);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utility Functions (External References)
|
|
|
|
// ConstructionSite에서 프리팹 정보를 가져갈 때 사용
|
|
public TurretData GetTurretData(int index) => turretLibrary[index];
|
|
|
|
// TunnelNode가 스폰될 때 자신을 등록하기 위해 사용
|
|
public void RegisterTunnel(Vector3Int pos, TunnelNode node)
|
|
{
|
|
if (!_tunnelRegistry.ContainsKey(pos))
|
|
{
|
|
_tunnelRegistry.Add(pos, node);
|
|
_occupiedNodes.Add(pos); // 건설된 구역으로 마킹
|
|
}
|
|
}
|
|
|
|
// TunnelNode가 위아래 노드를 찾기 위해 사용
|
|
public TunnelNode GetTunnelAt(Vector3Int pos) => _tunnelRegistry.GetValueOrDefault(pos);
|
|
|
|
// 좌표 변환: 월드 -> 격자 인덱스 (0.5 오프셋 반영)
|
|
public Vector3Int WorldToGrid3D(Vector3 worldPos)
|
|
{
|
|
return new Vector3Int(
|
|
Mathf.FloorToInt(worldPos.x / cellSize),
|
|
Mathf.RoundToInt((worldPos.y - yOffset) / tunnelHeight),
|
|
Mathf.FloorToInt(worldPos.z / cellSize)
|
|
);
|
|
}
|
|
|
|
// 좌표 변환: 격자 인덱스 -> 월드 (0.5 오프셋 반영)
|
|
public Vector3 GridToWorld(Vector3Int gridPos)
|
|
{
|
|
return new Vector3(
|
|
gridPos.x * cellSize,
|
|
(gridPos.y * tunnelHeight) + yOffset,
|
|
gridPos.z * cellSize
|
|
);
|
|
}
|
|
|
|
// 씬에 미리 배치된 터널들을 한꺼번에 등록
|
|
public void RegisterAllExistingTunnels()
|
|
{
|
|
TunnelNode[] nodes = FindObjectsByType<TunnelNode>(FindObjectsSortMode.None);
|
|
foreach (var node in nodes)
|
|
{
|
|
Vector3Int pos = WorldToGrid3D(node.transform.position);
|
|
RegisterTunnel(pos, node);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Mode Switching
|
|
|
|
public void ToggleBuildMode() { if (_isBuildMode) ExitBuildMode(); else EnterBuildMode(); }
|
|
private void EnterBuildMode()
|
|
{
|
|
if (turretLibrary.Count == 0) return;
|
|
_isBuildMode = true;
|
|
_ghostInstance = Instantiate(turretLibrary[_selectedTurretIndex].ghostPrefab);
|
|
}
|
|
private void ExitBuildMode()
|
|
{
|
|
_isBuildMode = false;
|
|
if (_ghostInstance) Destroy(_ghostInstance);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public override void OnNetworkDespawn() => _inputActions.Disable();
|
|
} |