using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.EventSystems; using System.Collections.Generic; public class BuildManager : MonoBehaviour { public static BuildManager Instance; [System.Serializable] public struct TurretData { public string turretName; public bool isTunnel; public GameObject finalPrefab; public GameObject ghostPrefab; public float buildTime; public Vector2Int size; } [Header("Settings")] public float cellSize = 1f; public float tunnelHeight = 3f; [SerializeField] private LayerMask groundLayer; [SerializeField] private GameObject constructionSitePrefab; [SerializeField] private LayerMask playerLayer; [Header("Turret Library")] [SerializeField] private List turretLibrary; [SerializeField] private TurretData selectedTurret; private GameObject _ghostInstance; private Material _ghostMaterial; private bool _isBuildMode = false; public bool IsBuildMode => _isBuildMode; // 좌표 레지스트리 (물리 탐색 대체) private Dictionary _tunnelRegistry = new Dictionary(); private HashSet _occupiedNodes = new HashSet(); private PlayerInputActions _inputActions; private Vector3Int _currentGridPos; private float _currentY; // 게임 시작 시 기존 터널들 등록 (반드시 필요!) void Start() { // 1. 씬에 배치된 모든 터널을 찾아 등록하고 연결합니다. RegisterAllExistingTunnels(); } public void RegisterTunnel(Vector3Int pos, TunnelNode node) { if (!_tunnelRegistry.ContainsKey(pos)) { _tunnelRegistry.Add(pos, node); _occupiedNodes.Add(pos); Debug.Log($"[Registry] {pos} 좌표에 {node.name} 등록 완료"); } } // 씬에 이미 배치된 터널들을 등록하는 함수 (Start에서 호출) public void RegisterAllExistingTunnels() { _tunnelRegistry.Clear(); _occupiedNodes.Clear(); TunnelNode[] nodes = FindObjectsByType(FindObjectsSortMode.None); HashSet roots = new HashSet(); foreach (var node in nodes) { Transform root = node.transform.parent.parent; // Tunnel (1) if (!roots.Contains(root)) { Vector3Int pos = WorldToGrid3D(root.position); RegisterTunnel(pos, node); roots.Add(root); } } // 모든 등록이 끝난 후 '동시에' 연결 foreach (var node in nodes) node.LinkVertical(); } // [핵심 수정] 노드의 위치가 아닌, 터널 부모의 위치를 넣어도 정확한 격자가 나오도록 함 public Vector3Int WorldToGrid3D(Vector3 worldPos) { int x = Mathf.FloorToInt(worldPos.x / cellSize); int z = Mathf.FloorToInt(worldPos.z / cellSize); // Y값은 tunnelHeight로 나눈 뒤 반올림(Round)하여 오차 극복 int y = Mathf.RoundToInt(worldPos.y / tunnelHeight); return new Vector3Int(x, y, z); } void Awake() { Instance = this; _inputActions = new PlayerInputActions(); } void OnEnable() { // 최신 Input System 콜백 등록 _inputActions.Player.Build.performed += ctx => OnBuildRequested(); _inputActions.Player.Cancel.performed += ctx => ExitBuildMode(); _inputActions.Player.ToggleBuild.performed += ctx => ToggleBuildMode(); _inputActions.Enable(); } void OnDisable() => _inputActions.Disable(); void Update() { if (!_isBuildMode || _ghostInstance == null) return; UpdateGhostPosition(); } 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)) { Vector2Int xz = WorldToGrid(hit.point); float targetY = 0.05f; int floor = 0; // 터널 조준 시 지하로 스냅 if (((1 << hit.collider.gameObject.layer) & tunnelMask) != 0) { xz = WorldToGrid(hit.collider.transform.position); targetY = hit.collider.transform.position.y - tunnelHeight; floor = Mathf.RoundToInt(targetY / tunnelHeight); } _currentGridPos = new Vector3Int(xz.x, floor, xz.y); _currentY = targetY; _ghostInstance.transform.position = new Vector3(xz.x * cellSize, targetY, xz.y * cellSize); bool canBuild = CanBuildVertical(_currentGridPos); _ghostMaterial.color = canBuild ? new Color(0, 1, 0, 0.5f) : new Color(1, 0, 0, 0.5f); } } private bool CanBuildVertical(Vector3Int pos) { if (_occupiedNodes.Contains(pos)) return false; // 지하 건설 시 위층 터널 존재 여부 확인 if (pos.y < 0 && !_occupiedNodes.Contains(pos + Vector3Int.up)) return false; // 플레이어와 겹치는지 확인 return !Physics.CheckBox(new Vector3(pos.x * cellSize, pos.y * tunnelHeight + 1f, pos.z * cellSize), new Vector3(0.45f, 0.5f, 0.45f), Quaternion.identity, playerLayer); } private void OnBuildRequested() { if (!_isBuildMode || EventSystem.current.IsPointerOverGameObject()) return; if (!CanBuildVertical(_currentGridPos)) return; _occupiedNodes.Add(_currentGridPos); GameObject site = Instantiate(constructionSitePrefab, _ghostInstance.transform.position, Quaternion.identity); site.GetComponent().Initialize(selectedTurret.finalPrefab, selectedTurret.buildTime, _currentGridPos); ExitBuildMode(); } public TunnelNode GetTunnelAt(Vector3Int pos) => _tunnelRegistry.GetValueOrDefault(pos); public Vector2Int WorldToGrid(Vector3 pos) => new Vector2Int(Mathf.FloorToInt(pos.x / cellSize), Mathf.FloorToInt(pos.z / cellSize)); private void ToggleBuildMode() { if (_isBuildMode) ExitBuildMode(); else EnterBuildMode(); } private void EnterBuildMode() { _isBuildMode = true; _ghostInstance = Instantiate(selectedTurret.ghostPrefab); _ghostMaterial = _ghostInstance.GetComponentInChildren().material; } private void ExitBuildMode() { _isBuildMode = false; if (_ghostInstance) Destroy(_ghostInstance); } // BuildManager.cs 내부 public void SelectTurret(int index) { if (index >= 0 && index < turretLibrary.Count) { selectedTurret = turretLibrary[index]; // 현재 건설 모드가 아니라면 건설 모드로 진입 if (!_isBuildMode) { EnterBuildMode(); } else { // 이미 건설 모드라면 고스트만 교체 if (_ghostInstance) Destroy(_ghostInstance); _ghostInstance = Instantiate(selectedTurret.ghostPrefab); _ghostMaterial = _ghostInstance.GetComponentInChildren().material; } Debug.Log($"{selectedTurret.turretName} 선택됨"); } } }