729 lines
26 KiB
C#
729 lines
26 KiB
C#
using System.Collections.Generic;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.EventSystems;
|
|
using Northbound.Data;
|
|
|
|
namespace Northbound
|
|
{
|
|
public class BuildingPlacement : NetworkBehaviour
|
|
{
|
|
[Header("Settings")]
|
|
public LayerMask groundLayer;
|
|
public float maxPlacementDistance = 100f;
|
|
|
|
[Header("Preview Materials")]
|
|
public Material validMaterial;
|
|
public Material invalidMaterial;
|
|
|
|
[Header("Current Selection")]
|
|
public int selectedBuildingIndex = 0;
|
|
|
|
[Header("Debug Visualization")]
|
|
public bool showGridBounds = true;
|
|
|
|
[Header("Drag Building")]
|
|
[Tooltip("드래그 건설 활성화")]
|
|
public bool enableDragBuilding = true;
|
|
[Tooltip("드래그 건설 시 최대 건물 수")]
|
|
public int maxDragBuildingCount = 50;
|
|
|
|
private bool isBuildModeActive = false;
|
|
private GameObject previewObject;
|
|
private int currentRotation = 0; // 0-3
|
|
private Material[] originalMaterials;
|
|
private Renderer[] previewRenderers;
|
|
|
|
// 드래그 건설 관련
|
|
private bool isDragging = false;
|
|
private Vector3 dragStartPosition;
|
|
private List<GameObject> dragPreviewObjects = new List<GameObject>();
|
|
private List<Vector3> dragBuildingPositions = new List<Vector3>();
|
|
|
|
// UI 체크 캐싱 (Input System 콜백 내에서 IsPointerOverGameObject() 사용 불가)
|
|
private bool _isPointerOverUI = false;
|
|
|
|
// 플레이어 컨트롤러 참조 (로컬 플레이어 체크용)
|
|
private NetworkPlayerController _playerController;
|
|
private bool _isInitialized = false;
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
_playerController = GetComponent<NetworkPlayerController>();
|
|
|
|
if (_playerController != null)
|
|
{
|
|
_playerController.OnInputInitialized += TryInitializeInput;
|
|
}
|
|
|
|
// 이미 로컬 플레이어면 입력 초기화 시도
|
|
TryInitializeInput();
|
|
}
|
|
|
|
private void TryInitializeInput()
|
|
{
|
|
if (_isInitialized) return; // 이미 초기화됨
|
|
if (_playerController == null || !_playerController.IsLocalPlayer) return;
|
|
if (_playerController.InputActions == null) return;
|
|
|
|
_isInitialized = true;
|
|
|
|
var inputActions = _playerController.InputActions;
|
|
inputActions.Player.ToggleBuildMode.performed += OnToggleBuildMode;
|
|
inputActions.Player.Rotate.performed += OnRotate;
|
|
inputActions.Player.Build.performed += OnBuildPressed;
|
|
inputActions.Player.Build.canceled += OnBuildReleased;
|
|
inputActions.Player.Cancel.performed += OnCancel;
|
|
|
|
// Create default materials if not assigned
|
|
if (validMaterial == null)
|
|
{
|
|
validMaterial = CreateGhostMaterial(new Color(0, 1, 0, 0.5f));
|
|
}
|
|
|
|
if (invalidMaterial == null)
|
|
{
|
|
invalidMaterial = CreateGhostMaterial(new Color(1, 0, 0, 0.5f));
|
|
}
|
|
}
|
|
|
|
private Material CreateGhostMaterial(Color color)
|
|
{
|
|
// Try to find appropriate shader for current render pipeline
|
|
Shader shader = Shader.Find("Universal Render Pipeline/Lit");
|
|
if (shader == null)
|
|
shader = Shader.Find("Standard");
|
|
if (shader == null)
|
|
shader = Shader.Find("Diffuse");
|
|
|
|
Material mat = new Material(shader);
|
|
|
|
// Set base color
|
|
if (mat.HasProperty("_BaseColor"))
|
|
mat.SetColor("_BaseColor", color); // URP
|
|
else if (mat.HasProperty("_Color"))
|
|
mat.SetColor("_Color", color); // Standard
|
|
|
|
// Enable transparency
|
|
if (mat.HasProperty("_Surface"))
|
|
{
|
|
// URP Transparency
|
|
mat.SetFloat("_Surface", 1); // Transparent
|
|
mat.SetFloat("_Blend", 0); // Alpha
|
|
mat.SetFloat("_AlphaClip", 0);
|
|
mat.SetFloat("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
|
|
mat.SetFloat("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
|
|
mat.SetFloat("_ZWrite", 0);
|
|
mat.renderQueue = 3000;
|
|
mat.EnableKeyword("_SURFACE_TYPE_TRANSPARENT");
|
|
mat.EnableKeyword("_ALPHAPREMULTIPLY_ON");
|
|
}
|
|
else
|
|
{
|
|
// Standard RP Transparency
|
|
mat.SetFloat("_Mode", 3); // Transparent mode
|
|
mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
|
|
mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
|
|
mat.SetInt("_ZWrite", 0);
|
|
mat.DisableKeyword("_ALPHATEST_ON");
|
|
mat.EnableKeyword("_ALPHABLEND_ON");
|
|
mat.DisableKeyword("_ALPHAPREMULTIPLY_ON");
|
|
mat.renderQueue = 3000;
|
|
}
|
|
|
|
return mat;
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
if (_playerController != null)
|
|
{
|
|
_playerController.OnInputInitialized -= TryInitializeInput;
|
|
|
|
if (_isInitialized && _playerController.InputActions != null)
|
|
{
|
|
var inputActions = _playerController.InputActions;
|
|
inputActions.Player.ToggleBuildMode.performed -= OnToggleBuildMode;
|
|
inputActions.Player.Rotate.performed -= OnRotate;
|
|
inputActions.Player.Build.performed -= OnBuildPressed;
|
|
inputActions.Player.Build.canceled -= OnBuildReleased;
|
|
inputActions.Player.Cancel.performed -= OnCancel;
|
|
}
|
|
}
|
|
|
|
ClearDragPreviews();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!_isInitialized) return;
|
|
|
|
// UI 체크 캐싱 (Input System 콜백 내에서 사용하기 위해)
|
|
_isPointerOverUI = CheckPointerOverUI();
|
|
|
|
if (isBuildModeActive)
|
|
{
|
|
if (isDragging && enableDragBuilding)
|
|
{
|
|
UpdateDragPreview();
|
|
}
|
|
else
|
|
{
|
|
UpdatePreviewPosition();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnCancel(InputAction.CallbackContext context)
|
|
{
|
|
// 드래그 중일 때 드래그 취소
|
|
if (isDragging && isBuildModeActive)
|
|
{
|
|
CancelDrag();
|
|
}
|
|
// 건설 모드 활성화 상태면 건설 모드 종료
|
|
else if (isBuildModeActive)
|
|
{
|
|
isBuildModeActive = false;
|
|
if (BuildingQuickslotUI.Instance != null)
|
|
{
|
|
BuildingQuickslotUI.Instance.HideQuickslot();
|
|
}
|
|
DestroyPreview();
|
|
ClearDragPreviews();
|
|
}
|
|
}
|
|
|
|
private void OnToggleBuildMode(InputAction.CallbackContext context)
|
|
{
|
|
isBuildModeActive = !isBuildModeActive;
|
|
|
|
if (isBuildModeActive)
|
|
{
|
|
// UI 표시
|
|
if (BuildingQuickslotUI.Instance != null)
|
|
{
|
|
BuildingQuickslotUI.Instance.ShowQuickslot(this);
|
|
}
|
|
|
|
CreatePreview();
|
|
}
|
|
else
|
|
{
|
|
// UI 숨김
|
|
if (BuildingQuickslotUI.Instance != null)
|
|
{
|
|
BuildingQuickslotUI.Instance.HideQuickslot();
|
|
}
|
|
|
|
// 드래그 중이었다면 취소
|
|
if (isDragging)
|
|
{
|
|
CancelDrag();
|
|
}
|
|
|
|
DestroyPreview();
|
|
ClearDragPreviews();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UI에서 건물 선택 시 호출
|
|
/// </summary>
|
|
public void SetSelectedBuilding(int index)
|
|
{
|
|
if (index < 0 || BuildingManager.Instance == null ||
|
|
index >= BuildingManager.Instance.GetBuildableBuildings().Count)
|
|
{
|
|
Debug.LogWarning($"[BuildingPlacement] 유효하지 않은 건물 인덱스: {index}");
|
|
return;
|
|
}
|
|
|
|
selectedBuildingIndex = index;
|
|
currentRotation = 0; // 회전 초기화
|
|
|
|
// 드래그 중이었다면 취소
|
|
if (isDragging)
|
|
{
|
|
CancelDrag();
|
|
}
|
|
|
|
// 프리뷰 다시 생성
|
|
if (isBuildModeActive)
|
|
{
|
|
DestroyPreview();
|
|
CreatePreview();
|
|
}
|
|
|
|
}
|
|
|
|
private void CreatePreview()
|
|
{
|
|
// 기존 프리뷰가 있다면 먼저 정리 (누적 방지)
|
|
DestroyPreview();
|
|
|
|
if (BuildingManager.Instance == null)
|
|
{
|
|
Debug.LogWarning("[BuildingPlacement] BuildingManager가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
if (selectedBuildingIndex < 0 || selectedBuildingIndex >= BuildingManager.Instance.GetBuildableBuildings().Count)
|
|
{
|
|
Debug.LogWarning($"[BuildingPlacement] 유효하지 않은 건물 인덱스: {selectedBuildingIndex}");
|
|
return;
|
|
}
|
|
|
|
TowerData data = BuildingManager.Instance.GetBuildableBuildings()[selectedBuildingIndex];
|
|
if (data == null)
|
|
{
|
|
Debug.LogError($"[BuildingPlacement] TowerData is NULL at index {selectedBuildingIndex}");
|
|
return;
|
|
}
|
|
|
|
if (data.prefab == null)
|
|
{
|
|
Debug.LogError($"[BuildingPlacement] TowerData.prefab is NULL at index {selectedBuildingIndex}. Run 'Northbound > Diagnose Tower System'");
|
|
return;
|
|
}
|
|
|
|
// 완성 건물 프리팹으로 프리뷰 생성 (사용자가 완성 모습을 볼 수 있도록)
|
|
previewObject = Instantiate(data.prefab);
|
|
|
|
// (0,0,0)에 고스트가 보이지 않도록 처음에는 비활성화
|
|
previewObject.SetActive(false);
|
|
|
|
// Remove NetworkObject component from preview
|
|
NetworkObject netObj = previewObject.GetComponent<NetworkObject>();
|
|
if (netObj != null)
|
|
{
|
|
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)
|
|
{
|
|
Material[] mats = new Material[renderer.materials.Length];
|
|
for (int i = 0; i < mats.Length; i++)
|
|
{
|
|
mats[i] = validMaterial;
|
|
}
|
|
renderer.materials = mats;
|
|
}
|
|
|
|
// Disable colliders in preview
|
|
foreach (var collider in previewObject.GetComponentsInChildren<Collider>())
|
|
{
|
|
collider.enabled = false;
|
|
}
|
|
|
|
// Disable NavMeshObstacles in preview (they act as obstacles for NavMeshAgent)
|
|
foreach (var obstacle in previewObject.GetComponentsInChildren<UnityEngine.AI.NavMeshObstacle>())
|
|
{
|
|
obstacle.enabled = false;
|
|
}
|
|
|
|
}
|
|
|
|
private void DestroyPreview()
|
|
{
|
|
if (previewObject != null)
|
|
{
|
|
Destroy(previewObject);
|
|
previewObject = null;
|
|
}
|
|
}
|
|
|
|
private void UpdatePreviewPosition()
|
|
{
|
|
if (previewObject == null || BuildingManager.Instance == null)
|
|
return;
|
|
|
|
if (selectedBuildingIndex < 0 || selectedBuildingIndex >= BuildingManager.Instance.GetBuildableBuildings().Count)
|
|
return;
|
|
|
|
TowerData data = BuildingManager.Instance.GetBuildableBuildings()[selectedBuildingIndex];
|
|
if (data == null || data.prefab == null)
|
|
return;
|
|
|
|
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
|
|
|
if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
|
|
{
|
|
// Raycast 성공 시 프리뷰 표시
|
|
previewObject.SetActive(true);
|
|
|
|
// Check if placement is valid
|
|
bool isValid = BuildingManager.Instance.IsValidPlacement(data, hit.point, currentRotation, out Vector3 snappedPosition);
|
|
|
|
// Check affordability
|
|
var coreResourceManager = CoreResourceManager.Instance;
|
|
bool canAfford = coreResourceManager != null && coreResourceManager.CanAfford(data.mana);
|
|
|
|
// Update preview position (placementOffset 적용)
|
|
previewObject.transform.position = snappedPosition + data.placementOffset;
|
|
previewObject.transform.rotation = Quaternion.Euler(0, currentRotation * 90f, 0);
|
|
|
|
// Update material based on validity and affordability
|
|
Material targetMat = (isValid && canAfford) ? validMaterial : invalidMaterial;
|
|
foreach (var renderer in previewRenderers)
|
|
{
|
|
Material[] mats = new Material[renderer.materials.Length];
|
|
for (int i = 0; i < mats.Length; i++)
|
|
{
|
|
mats[i] = targetMat;
|
|
}
|
|
renderer.materials = mats;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Raycast 실패 시 (0,0,0)에 고스트가 보이지 않도록 프리뷰 숨김
|
|
previewObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
private void OnRotate(InputAction.CallbackContext context)
|
|
{
|
|
if (!isBuildModeActive) return;
|
|
|
|
// 드래그 중에는 회전 불가
|
|
if (isDragging) return;
|
|
|
|
currentRotation = (currentRotation + 1) % 4;
|
|
}
|
|
|
|
private void OnBuildPressed(InputAction.CallbackContext context)
|
|
{
|
|
if (!isBuildModeActive || _isPointerOverUI)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (enableDragBuilding)
|
|
{
|
|
// 드래그 시작
|
|
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
|
if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
|
|
{
|
|
isDragging = true;
|
|
dragStartPosition = hit.point;
|
|
|
|
// 메인 프리뷰 숨기기
|
|
if (previewObject != null)
|
|
{
|
|
previewObject.SetActive(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnBuildReleased(InputAction.CallbackContext context)
|
|
{
|
|
if (!isBuildModeActive) return;
|
|
|
|
if (enableDragBuilding && isDragging)
|
|
{
|
|
// 드래그 종료 - 배치 실행
|
|
ExecuteDragBuild();
|
|
isDragging = false;
|
|
|
|
// 메인 프리뷰 다시 표시
|
|
if (previewObject != null)
|
|
{
|
|
previewObject.SetActive(true);
|
|
}
|
|
|
|
ClearDragPreviews();
|
|
}
|
|
else if (!enableDragBuilding)
|
|
{
|
|
// 단일 건물 배치 (드래그 비활성화 시)
|
|
OnBuild();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 취소
|
|
/// </summary>
|
|
private void CancelDrag()
|
|
{
|
|
if (!isDragging) return;
|
|
|
|
isDragging = false;
|
|
|
|
// 메인 프리뷰 다시 표시
|
|
if (previewObject != null)
|
|
{
|
|
previewObject.SetActive(true);
|
|
}
|
|
|
|
// 드래그 프리뷰 정리
|
|
ClearDragPreviews();
|
|
}
|
|
|
|
private void UpdateDragPreview()
|
|
{
|
|
if (BuildingManager.Instance == null) return;
|
|
|
|
if (selectedBuildingIndex < 0 || selectedBuildingIndex >= BuildingManager.Instance.GetBuildableBuildings().Count)
|
|
{
|
|
Debug.LogWarning($"[BuildingPlacement] Invalid building index: {selectedBuildingIndex}");
|
|
return;
|
|
}
|
|
|
|
TowerData data = BuildingManager.Instance.GetBuildableBuildings()[selectedBuildingIndex];
|
|
if (data == null || data.prefab == null)
|
|
{
|
|
Debug.LogError($"[BuildingPlacement] TowerData or prefab is null at index {selectedBuildingIndex}. Please run 'Northbound > Populate Towers from Prefabs' and update BuildingManager.");
|
|
return;
|
|
}
|
|
|
|
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
|
if (!Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 드래그 영역 계산
|
|
Vector3 dragEndPosition = hit.point;
|
|
List<Vector3> positions = CalculateDragBuildingPositions(dragStartPosition, dragEndPosition, data);
|
|
|
|
// Check affordability for all buildings
|
|
var coreResourceManager = CoreResourceManager.Instance;
|
|
bool canAffordAll = coreResourceManager != null && coreResourceManager.CanAfford(data.mana * positions.Count);
|
|
|
|
// 기존 프리뷰 정리
|
|
ClearDragPreviews();
|
|
|
|
// 새로운 프리뷰 생성
|
|
dragBuildingPositions.Clear();
|
|
foreach (var pos in positions)
|
|
{
|
|
if (dragPreviewObjects.Count >= maxDragBuildingCount)
|
|
{
|
|
Debug.LogWarning($"[BuildingPlacement] 드래그 건설 최대 개수 도달: {maxDragBuildingCount}");
|
|
break;
|
|
}
|
|
|
|
bool isValid = BuildingManager.Instance.IsValidPlacement(data, pos, currentRotation, out Vector3 snappedPosition);
|
|
|
|
GameObject preview = Instantiate(data.prefab);
|
|
|
|
// 프리뷰가 보이도록 명시적으로 활성화
|
|
preview.SetActive(true);
|
|
|
|
// Remove NetworkObject and Building components
|
|
if (preview.GetComponent<NetworkObject>() != null)
|
|
Destroy(preview.GetComponent<NetworkObject>());
|
|
if (preview.GetComponent<Building>() != null)
|
|
Destroy(preview.GetComponent<Building>());
|
|
|
|
// Set position and rotation
|
|
preview.transform.position = snappedPosition + data.placementOffset;
|
|
preview.transform.rotation = Quaternion.Euler(0, currentRotation * 90f, 0);
|
|
|
|
// Apply materials
|
|
Material targetMat = (isValid && canAffordAll) ? validMaterial : invalidMaterial;
|
|
Renderer[] renderers = preview.GetComponentsInChildren<Renderer>();
|
|
foreach (var renderer in renderers)
|
|
{
|
|
Material[] mats = new Material[renderer.materials.Length];
|
|
for (int i = 0; i < mats.Length; i++)
|
|
{
|
|
mats[i] = targetMat;
|
|
}
|
|
renderer.materials = mats;
|
|
}
|
|
|
|
// Disable colliders
|
|
foreach (var collider in preview.GetComponentsInChildren<Collider>())
|
|
{
|
|
collider.enabled = false;
|
|
}
|
|
|
|
// Disable NavMeshObstacles in preview (they act as obstacles for NavMeshAgent)
|
|
foreach (var obstacle in preview.GetComponentsInChildren<UnityEngine.AI.NavMeshObstacle>())
|
|
{
|
|
obstacle.enabled = false;
|
|
}
|
|
|
|
dragPreviewObjects.Add(preview);
|
|
|
|
if (isValid)
|
|
{
|
|
dragBuildingPositions.Add(snappedPosition);
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<Vector3> CalculateDragBuildingPositions(Vector3 start, Vector3 end, TowerData data)
|
|
{
|
|
List<Vector3> positions = new List<Vector3>();
|
|
|
|
// 그리드에 스냅
|
|
Vector3 snappedStart = BuildingManager.Instance.SnapToGrid(start);
|
|
Vector3 snappedEnd = BuildingManager.Instance.SnapToGrid(end);
|
|
|
|
// 건물 크기 고려
|
|
Vector3 size = data.GetSize(currentRotation);
|
|
float gridSize = BuildingManager.Instance.gridSize;
|
|
float stepX = Mathf.Max(size.x, gridSize);
|
|
float stepZ = Mathf.Max(size.z, gridSize);
|
|
|
|
// 드래그 방향 계산
|
|
Vector3 direction = snappedEnd - snappedStart;
|
|
float distanceX = Mathf.Abs(direction.x);
|
|
float distanceZ = Mathf.Abs(direction.z);
|
|
|
|
int countX = Mathf.Max(1, Mathf.RoundToInt(distanceX / stepX) + 1);
|
|
int countZ = Mathf.Max(1, Mathf.RoundToInt(distanceZ / stepZ) + 1);
|
|
|
|
float dirX = direction.x >= 0 ? 1 : -1;
|
|
float dirZ = direction.z >= 0 ? 1 : -1;
|
|
|
|
// 그리드 패턴으로 위치 생성
|
|
for (int x = 0; x < countX; x++)
|
|
{
|
|
for (int z = 0; z < countZ; z++)
|
|
{
|
|
Vector3 pos = snappedStart + new Vector3(
|
|
x * stepX * dirX,
|
|
0,
|
|
z * stepZ * dirZ
|
|
);
|
|
positions.Add(pos);
|
|
}
|
|
}
|
|
|
|
return positions;
|
|
}
|
|
|
|
private void ExecuteDragBuild()
|
|
{
|
|
TowerData selectedData = BuildingManager.Instance.GetBuildableBuildings()[selectedBuildingIndex];
|
|
|
|
// 드래그 위치가 없으면 현재 프리뷰 위치로 단일 건설 시도
|
|
if (dragBuildingPositions.Count == 0)
|
|
{
|
|
if (previewObject != null && previewObject.activeSelf)
|
|
{
|
|
// 현재 프리뷰 위치에서 건설 시도
|
|
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
|
if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
|
|
{
|
|
if (BuildingManager.Instance.IsValidPlacement(selectedData, hit.point, currentRotation, out Vector3 groundPosition))
|
|
{
|
|
var coreResourceManager = CoreResourceManager.Instance;
|
|
if (coreResourceManager == null || coreResourceManager.CanAfford(selectedData.mana))
|
|
{
|
|
BuildingManager.Instance.RequestPlaceFoundation(selectedData.name, groundPosition, currentRotation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Check affordability
|
|
var coreResourceManager2 = CoreResourceManager.Instance;
|
|
if (coreResourceManager2 == null || !coreResourceManager2.CanAfford(selectedData.mana * dragBuildingPositions.Count))
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var position in dragBuildingPositions)
|
|
{
|
|
BuildingManager.Instance.RequestPlaceFoundation(selectedData.name, position, currentRotation);
|
|
}
|
|
}
|
|
|
|
private void ClearDragPreviews()
|
|
{
|
|
foreach (var preview in dragPreviewObjects)
|
|
{
|
|
if (preview != null)
|
|
{
|
|
Destroy(preview);
|
|
}
|
|
}
|
|
dragPreviewObjects.Clear();
|
|
dragBuildingPositions.Clear();
|
|
}
|
|
|
|
private void OnBuild()
|
|
{
|
|
if (!isBuildModeActive || previewObject == null) return;
|
|
|
|
// UI 위에서 클릭한 경우 무시
|
|
if (_isPointerOverUI) return;
|
|
|
|
// Get placement position
|
|
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
|
if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
|
|
{
|
|
TowerData selectedData = BuildingManager.Instance.GetBuildableBuildings()[selectedBuildingIndex];
|
|
|
|
// Check affordability
|
|
var coreResourceManager = CoreResourceManager.Instance;
|
|
if (coreResourceManager != null && !coreResourceManager.CanAfford(selectedData.mana))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (BuildingManager.Instance.IsValidPlacement(selectedData, hit.point, currentRotation, out Vector3 groundPosition))
|
|
{
|
|
// 토대 배치 요청 (건물 이름 사용)
|
|
BuildingManager.Instance.RequestPlaceFoundation(selectedData.name, groundPosition, currentRotation);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 마우스 포인터가 건설 UI 위에 있는지 확인 (Update에서만 호출)
|
|
/// </summary>
|
|
private bool CheckPointerOverUI()
|
|
{
|
|
// EventSystem이 없으면 UI 체크 불가능
|
|
if (EventSystem.current == null)
|
|
return false;
|
|
|
|
if (Mouse.current == null)
|
|
return false;
|
|
|
|
Vector2 mousePosition = Mouse.current.position.ReadValue();
|
|
|
|
// BuildingQuickslotUI의 패널이 마우스 아래에 있는지 확인
|
|
if (BuildingQuickslotUI.Instance != null)
|
|
{
|
|
var quickslotUI = BuildingQuickslotUI.Instance;
|
|
|
|
// quickslotPanel이 활성화되어 있고, 마우스가 그 위에 있으면 true
|
|
if (quickslotUI.IsMouseOverQuickslot(mousePosition))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 건설 모드 활성화 상태 확인
|
|
/// </summary>
|
|
public bool IsBuildModeActive()
|
|
{
|
|
return isBuildModeActive;
|
|
}
|
|
}
|
|
}
|