건물 체력 시스템 추가
건물 시야 시스템은 기본 건물데이터에 통합
This commit is contained in:
@@ -118,6 +118,139 @@ namespace Northbound
|
||||
}
|
||||
}
|
||||
|
||||
private void OnToggleBuildMode(InputAction.CallbackContext context)
|
||||
{
|
||||
isBuildModeActive = !isBuildModeActive;
|
||||
|
||||
if (isBuildModeActive)
|
||||
{
|
||||
CreatePreview();
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyPreview();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreatePreview()
|
||||
{
|
||||
if (BuildingManager.Instance == null)
|
||||
{
|
||||
Debug.LogWarning("[BuildingPlacement] BuildingManager가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedBuildingIndex < 0 || selectedBuildingIndex >= BuildingManager.Instance.availableBuildings.Count)
|
||||
{
|
||||
Debug.LogWarning($"[BuildingPlacement] 유효하지 않은 건물 인덱스: {selectedBuildingIndex}");
|
||||
return;
|
||||
}
|
||||
|
||||
BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
|
||||
if (data == null || data.prefab == null)
|
||||
{
|
||||
Debug.LogWarning("[BuildingPlacement] BuildingData 또는 Prefab이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
previewObject = Instantiate(data.prefab);
|
||||
|
||||
// Remove NetworkObject component from preview
|
||||
NetworkObject netObj = previewObject.GetComponent<NetworkObject>();
|
||||
if (netObj != null)
|
||||
{
|
||||
Destroy(netObj);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyPreview()
|
||||
{
|
||||
if (previewObject != null)
|
||||
{
|
||||
Destroy(previewObject);
|
||||
previewObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePreviewPosition()
|
||||
{
|
||||
if (previewObject == null || BuildingManager.Instance == null)
|
||||
return;
|
||||
|
||||
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
|
||||
{
|
||||
BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
|
||||
|
||||
// Check if placement is valid
|
||||
bool isValid = BuildingManager.Instance.IsValidPlacement(data, hit.point, currentRotation, out Vector3 snappedPosition);
|
||||
|
||||
// Update preview position
|
||||
previewObject.transform.position = snappedPosition + data.placementOffset;
|
||||
previewObject.transform.rotation = Quaternion.Euler(0, currentRotation * 90f, 0);
|
||||
|
||||
// Update material based on validity
|
||||
Material targetMat = isValid ? 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRotate(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!isBuildModeActive) return;
|
||||
|
||||
currentRotation = (currentRotation + 1) % 4;
|
||||
}
|
||||
|
||||
private void OnBuild(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!isBuildModeActive || previewObject == null || BuildingManager.Instance == null)
|
||||
return;
|
||||
|
||||
BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
|
||||
Vector3 placementPosition = previewObject.transform.position - data.placementOffset;
|
||||
|
||||
// Validate placement one more time
|
||||
if (BuildingManager.Instance.IsValidPlacement(data, placementPosition, currentRotation, out Vector3 snappedPosition))
|
||||
{
|
||||
// 🔥 변경: PlaceBuildingServerRpc 대신 RequestPlaceBuilding 호출
|
||||
BuildingManager.Instance.RequestPlaceBuilding(selectedBuildingIndex, snappedPosition, currentRotation);
|
||||
|
||||
Debug.Log($"<color=cyan>[BuildingPlacement] 건물 배치 요청: {data.buildingName}</color>");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("<color=yellow>[BuildingPlacement] 건물을 배치할 수 없는 위치입니다.</color>");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (!IsOwner || !isBuildModeActive || !showGridBounds) return;
|
||||
@@ -134,321 +267,6 @@ namespace Northbound
|
||||
|
||||
Gizmos.color = isValid ? new Color(0, 1, 0, 0.5f) : new Color(1, 0, 0, 0.5f);
|
||||
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
||||
|
||||
// Draw grid cells
|
||||
Gizmos.color = Color.yellow;
|
||||
float gridSize = BuildingManager.Instance.gridSize;
|
||||
Vector3 snappedPos = BuildingManager.Instance.SnapToGrid(previewObject.transform.position);
|
||||
|
||||
// Draw grid origin point
|
||||
Gizmos.DrawSphere(snappedPos, 0.1f);
|
||||
|
||||
// Draw grid outline for each cell
|
||||
int width = Mathf.RoundToInt(bounds.size.x / gridSize);
|
||||
int length = Mathf.RoundToInt(bounds.size.z / gridSize);
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int z = 0; z < length; z++)
|
||||
{
|
||||
Vector3 cellPos = snappedPos + new Vector3(
|
||||
(x - width / 2f + 0.5f) * gridSize,
|
||||
0.01f,
|
||||
(z - length / 2f + 0.5f) * gridSize
|
||||
);
|
||||
Gizmos.DrawWireCube(cellPos, new Vector3(gridSize, 0.01f, gridSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnToggleBuildMode(InputAction.CallbackContext context)
|
||||
{
|
||||
ToggleBuildMode();
|
||||
}
|
||||
|
||||
private void OnRotate(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!isBuildModeActive) return;
|
||||
|
||||
float rotateValue = context.ReadValue<float>();
|
||||
if (rotateValue > 0)
|
||||
{
|
||||
currentRotation = (currentRotation + 1) % 4;
|
||||
}
|
||||
else if (rotateValue < 0)
|
||||
{
|
||||
currentRotation = (currentRotation - 1 + 4) % 4;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBuild(InputAction.CallbackContext context)
|
||||
{
|
||||
if (!isBuildModeActive) return;
|
||||
TryPlaceBuilding();
|
||||
}
|
||||
|
||||
private void ToggleBuildMode()
|
||||
{
|
||||
isBuildModeActive = !isBuildModeActive;
|
||||
|
||||
if (isBuildModeActive)
|
||||
{
|
||||
EnterBuildMode();
|
||||
}
|
||||
else
|
||||
{
|
||||
ExitBuildMode();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnterBuildMode()
|
||||
{
|
||||
currentRotation = 0;
|
||||
CreatePreview();
|
||||
}
|
||||
|
||||
private void ExitBuildMode()
|
||||
{
|
||||
DestroyPreview();
|
||||
}
|
||||
|
||||
private void CreatePreview()
|
||||
{
|
||||
if (BuildingManager.Instance == null)
|
||||
return;
|
||||
|
||||
if (BuildingManager.Instance.availableBuildings.Count == 0)
|
||||
return;
|
||||
|
||||
selectedBuildingIndex = Mathf.Clamp(selectedBuildingIndex, 0,
|
||||
BuildingManager.Instance.availableBuildings.Count - 1);
|
||||
|
||||
BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
|
||||
if (data == null || data.prefab == null)
|
||||
return;
|
||||
|
||||
previewObject = Instantiate(data.prefab);
|
||||
previewObject.name = "BuildingPreview";
|
||||
|
||||
// Disable colliders on preview
|
||||
foreach (var collider in previewObject.GetComponentsInChildren<Collider>())
|
||||
{
|
||||
collider.enabled = false;
|
||||
}
|
||||
|
||||
// Remove NetworkObject from preview if exists
|
||||
NetworkObject netObj = previewObject.GetComponent<NetworkObject>();
|
||||
if (netObj != null)
|
||||
{
|
||||
Destroy(netObj);
|
||||
}
|
||||
|
||||
// Store original materials and setup preview renderers
|
||||
previewRenderers = previewObject.GetComponentsInChildren<Renderer>();
|
||||
|
||||
if (previewRenderers.Length == 0)
|
||||
{
|
||||
// Add a debug cube so you can at least see something
|
||||
GameObject debugCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
debugCube.transform.SetParent(previewObject.transform);
|
||||
debugCube.transform.localPosition = Vector3.zero;
|
||||
Destroy(debugCube.GetComponent<Collider>());
|
||||
previewRenderers = previewObject.GetComponentsInChildren<Renderer>();
|
||||
}
|
||||
|
||||
originalMaterials = new Material[previewRenderers.Length];
|
||||
|
||||
for (int i = 0; i < previewRenderers.Length; i++)
|
||||
{
|
||||
originalMaterials[i] = previewRenderers[i].material;
|
||||
// Replace all materials with ghost material
|
||||
Material[] mats = new Material[previewRenderers[i].materials.Length];
|
||||
for (int j = 0; j < mats.Length; j++)
|
||||
{
|
||||
mats[j] = validMaterial;
|
||||
}
|
||||
previewRenderers[i].materials = mats;
|
||||
|
||||
// Disable shadow casting
|
||||
previewRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||
previewRenderers[i].receiveShadows = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyPreview()
|
||||
{
|
||||
if (previewObject != null)
|
||||
{
|
||||
Destroy(previewObject);
|
||||
previewObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePreviewPosition()
|
||||
{
|
||||
if (BuildingManager.Instance == null) return;
|
||||
|
||||
if (previewObject == null)
|
||||
{
|
||||
CreatePreview();
|
||||
if (previewObject == null) return;
|
||||
}
|
||||
|
||||
if (Mouse.current == null)
|
||||
return;
|
||||
|
||||
Vector2 mousePos = Mouse.current.position.ReadValue();
|
||||
Ray ray = Camera.main.ScreenPointToRay(mousePos);
|
||||
|
||||
Vector3 targetPosition = Vector3.zero;
|
||||
bool foundPosition = false;
|
||||
|
||||
// Robust approach: Find where ray intersects ground plane, then raycast down from there
|
||||
// This works even when pointing at empty space between buildings
|
||||
|
||||
// Step 1: Calculate where ray intersects a horizontal plane at Y=0
|
||||
// Using plane intersection math
|
||||
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
|
||||
float enter;
|
||||
|
||||
if (groundPlane.Raycast(ray, out enter))
|
||||
{
|
||||
// Get the XZ position where ray hits the ground plane
|
||||
Vector3 planeHitPoint = ray.GetPoint(enter);
|
||||
|
||||
// Step 2: Cast down from above this XZ position to find actual ground
|
||||
Vector3 highPoint = new Vector3(planeHitPoint.x, 100f, planeHitPoint.z);
|
||||
|
||||
if (Physics.Raycast(highPoint, Vector3.down, out RaycastHit downHit, 200f, groundLayer))
|
||||
{
|
||||
// Always use the hit point
|
||||
targetPosition = downHit.point;
|
||||
foundPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No ground found via raycast, use the plane intersection point directly
|
||||
targetPosition = planeHitPoint;
|
||||
foundPosition = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: Try direct raycast to anything
|
||||
if (Physics.Raycast(ray, out RaycastHit anyHit, maxPlacementDistance))
|
||||
{
|
||||
targetPosition = anyHit.point;
|
||||
foundPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundPosition)
|
||||
{
|
||||
BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
|
||||
|
||||
// IsValidPlacement now returns the snapped position in groundPosition
|
||||
bool isValid = BuildingManager.Instance.IsValidPlacement(data, targetPosition, currentRotation, out Vector3 snappedPosition);
|
||||
|
||||
previewObject.transform.position = snappedPosition + data.placementOffset;
|
||||
previewObject.transform.rotation = Quaternion.Euler(0, currentRotation * 90f, 0);
|
||||
|
||||
if (!previewObject.activeSelf)
|
||||
{
|
||||
previewObject.SetActive(true);
|
||||
}
|
||||
|
||||
// Update material
|
||||
Material previewMat = isValid ? validMaterial : invalidMaterial;
|
||||
foreach (var renderer in previewRenderers)
|
||||
{
|
||||
Material[] mats = new Material[renderer.materials.Length];
|
||||
for (int i = 0; i < mats.Length; i++)
|
||||
{
|
||||
mats[i] = previewMat;
|
||||
}
|
||||
renderer.materials = mats;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (previewObject.activeSelf)
|
||||
{
|
||||
previewObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryPlaceBuilding()
|
||||
{
|
||||
if (previewObject == null || !previewObject.activeSelf) return;
|
||||
|
||||
Vector2 mousePos = Mouse.current.position.ReadValue();
|
||||
Ray ray = Camera.main.ScreenPointToRay(mousePos);
|
||||
|
||||
Vector3 targetPosition = Vector3.zero;
|
||||
bool foundPosition = false;
|
||||
|
||||
// Use plane intersection to get XZ position, then raycast down to find ground
|
||||
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
|
||||
float enter;
|
||||
|
||||
if (groundPlane.Raycast(ray, out enter))
|
||||
{
|
||||
Vector3 planeHitPoint = ray.GetPoint(enter);
|
||||
Vector3 highPoint = new Vector3(planeHitPoint.x, 100f, planeHitPoint.z);
|
||||
|
||||
if (Physics.Raycast(highPoint, Vector3.down, out RaycastHit downHit, 200f, groundLayer))
|
||||
{
|
||||
if (downHit.collider.GetComponentInParent<Building>() == null)
|
||||
{
|
||||
targetPosition = downHit.point;
|
||||
foundPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPosition = downHit.point;
|
||||
foundPosition = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPosition = planeHitPoint;
|
||||
foundPosition = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: direct raycast
|
||||
if (Physics.Raycast(ray, out RaycastHit anyHit, maxPlacementDistance))
|
||||
{
|
||||
targetPosition = anyHit.point;
|
||||
foundPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundPosition)
|
||||
{
|
||||
BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
|
||||
|
||||
// IsValidPlacement now returns the snapped position
|
||||
if (BuildingManager.Instance.IsValidPlacement(data, targetPosition, currentRotation, out Vector3 snappedPosition))
|
||||
{
|
||||
BuildingManager.Instance.PlaceBuildingServerRpc(selectedBuildingIndex, snappedPosition, currentRotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override public void OnDestroy()
|
||||
{
|
||||
DestroyPreview();
|
||||
|
||||
if (_inputActions != null)
|
||||
{
|
||||
_inputActions.Dispose();
|
||||
}
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user