드래그로 대량 건설 기능 추가

Kaykit Medival 누락사항 추가
기본 성벽 및 성문 변경
This commit is contained in:
2026-01-28 15:19:55 +09:00
parent 68a2e4e340
commit 56b9af850f
56 changed files with 3695 additions and 1473 deletions

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
@@ -21,6 +22,12 @@ namespace Northbound
[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
@@ -28,6 +35,12 @@ namespace Northbound
private Renderer[] previewRenderers;
private PlayerInputActions _inputActions;
// 드래그 건설 관련
private bool isDragging = false;
private Vector3 dragStartPosition;
private List<GameObject> dragPreviewObjects = new List<GameObject>();
private List<Vector3> dragBuildingPositions = new List<Vector3>();
public override void OnNetworkSpawn()
{
if (!IsOwner) return;
@@ -35,7 +48,9 @@ namespace Northbound
_inputActions = new PlayerInputActions();
_inputActions.Player.ToggleBuildMode.performed += OnToggleBuildMode;
_inputActions.Player.Rotate.performed += OnRotate;
_inputActions.Player.Build.performed += OnBuild;
_inputActions.Player.Build.performed += OnBuildPressed;
_inputActions.Player.Build.canceled += OnBuildReleased;
_inputActions.Player.Cancel.performed += OnCancel;
_inputActions.Enable();
// Create default materials if not assigned
@@ -103,10 +118,14 @@ namespace Northbound
{
_inputActions.Player.ToggleBuildMode.performed -= OnToggleBuildMode;
_inputActions.Player.Rotate.performed -= OnRotate;
_inputActions.Player.Build.performed -= OnBuild;
_inputActions.Player.Build.performed -= OnBuildPressed;
_inputActions.Player.Build.canceled -= OnBuildReleased;
_inputActions.Player.Cancel.performed -= OnCancel;
_inputActions.Disable();
_inputActions.Dispose();
}
ClearDragPreviews();
}
private void Update()
@@ -115,7 +134,24 @@ namespace Northbound
if (isBuildModeActive)
{
UpdatePreviewPosition();
if (isDragging && enableDragBuilding)
{
UpdateDragPreview();
}
else
{
UpdatePreviewPosition();
}
}
}
private void OnCancel(InputAction.CallbackContext context)
{
// 드래그 중일 때만 취소
if (isDragging && isBuildModeActive)
{
CancelDrag();
Debug.Log("<color=yellow>[BuildingPlacement] 드래그 건설 취소됨</color>");
}
}
@@ -141,7 +177,15 @@ namespace Northbound
BuildingQuickslotUI.Instance.HideQuickslot();
}
// 드래그 중이었다면 취소
if (isDragging)
{
CancelDrag();
Debug.Log("<color=yellow>[BuildingPlacement] 건설 모드 종료로 드래그 취소됨</color>");
}
DestroyPreview();
ClearDragPreviews();
}
Debug.Log($"[BuildingPlacement] 건설 모드 {(isBuildModeActive ? "" : "")}");
@@ -162,6 +206,13 @@ namespace Northbound
selectedBuildingIndex = index;
currentRotation = 0; // 회전 초기화
// 드래그 중이었다면 취소
if (isDragging)
{
CancelDrag();
Debug.Log("<color=yellow>[BuildingPlacement] 건물 변경으로 드래그 취소됨</color>");
}
// 프리뷰 다시 생성
if (isBuildModeActive)
{
@@ -276,10 +327,234 @@ namespace Northbound
{
if (!isBuildModeActive) return;
// 드래그 중에는 회전 불가
if (isDragging)
{
Debug.Log("<color=yellow>[BuildingPlacement] 드래그 중에는 회전할 수 없습니다</color>");
return;
}
currentRotation = (currentRotation + 1) % 4;
}
private void OnBuild(InputAction.CallbackContext context)
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);
}
Debug.Log("<color=cyan>[BuildingPlacement] 드래그 건설 시작 (ESC로 취소 가능)</color>");
}
}
}
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;
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
if (!Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
{
return;
}
BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
// 드래그 영역 계산
Vector3 dragEndPosition = hit.point;
List<Vector3> positions = CalculateDragBuildingPositions(dragStartPosition, dragEndPosition, data);
// 기존 프리뷰 정리
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);
// 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 ? 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;
}
dragPreviewObjects.Add(preview);
if (isValid)
{
dragBuildingPositions.Add(snappedPosition);
}
}
}
private List<Vector3> CalculateDragBuildingPositions(Vector3 start, Vector3 end, BuildingData 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()
{
if (dragBuildingPositions.Count == 0)
{
Debug.Log("<color=yellow>[BuildingPlacement] 배치 가능한 건물이 없습니다.</color>");
return;
}
BuildingData selectedData = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
int successCount = 0;
foreach (var position in dragBuildingPositions)
{
BuildingManager.Instance.RequestPlaceFoundation(selectedBuildingIndex, position, currentRotation);
successCount++;
}
Debug.Log($"<color=cyan>[BuildingPlacement] 드래그 건설 완료: {successCount}개의 {selectedData.buildingName} 배치 시도</color>");
}
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;