using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.EventSystems;
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;
private bool isBuildModeActive = false;
private GameObject previewObject;
private int currentRotation = 0; // 0-3
private Material[] originalMaterials;
private Renderer[] previewRenderers;
private PlayerInputActions _inputActions;
public override void OnNetworkSpawn()
{
if (!IsOwner) return;
_inputActions = new PlayerInputActions();
_inputActions.Player.ToggleBuildMode.performed += OnToggleBuildMode;
_inputActions.Player.Rotate.performed += OnRotate;
_inputActions.Player.Build.performed += OnBuild;
_inputActions.Enable();
// 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 (IsOwner && _inputActions != null)
{
_inputActions.Player.ToggleBuildMode.performed -= OnToggleBuildMode;
_inputActions.Player.Rotate.performed -= OnRotate;
_inputActions.Player.Build.performed -= OnBuild;
_inputActions.Disable();
_inputActions.Dispose();
}
}
private void Update()
{
if (!IsOwner) return;
if (isBuildModeActive)
{
UpdatePreviewPosition();
}
}
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();
}
DestroyPreview();
}
Debug.Log($"[BuildingPlacement] 건설 모드 {(isBuildModeActive ? "활성화" : "비활성화")}");
}
///
/// UI에서 건물 선택 시 호출
///
public void SetSelectedBuilding(int index)
{
if (index < 0 || BuildingManager.Instance == null ||
index >= BuildingManager.Instance.availableBuildings.Count)
{
Debug.LogWarning($"[BuildingPlacement] 유효하지 않은 건물 인덱스: {index}");
return;
}
selectedBuildingIndex = index;
currentRotation = 0; // 회전 초기화
// 프리뷰 다시 생성
if (isBuildModeActive)
{
DestroyPreview();
CreatePreview();
}
Debug.Log($"[BuildingPlacement] 건물 선택됨: {BuildingManager.Instance.availableBuildings[index].buildingName}");
}
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();
if (netObj != null)
{
Destroy(netObj);
}
// Remove Building component from preview
Building building = previewObject.GetComponent();
if (building != null)
{
Destroy(building);
}
// Apply ghost materials
previewRenderers = previewObject.GetComponentsInChildren();
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.enabled = false;
}
Debug.Log($"[BuildingPlacement] 프리뷰 생성됨: {data.buildingName}");
}
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 (placementOffset 적용)
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) return;
// UI 위에서 클릭한 경우 무시
if (IsPointerOverUI())
{
Debug.Log("[BuildingPlacement] UI 위에서 클릭함 - 건설 취소");
return;
}
// Get placement position
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
{
BuildingData selectedData = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
if (BuildingManager.Instance.IsValidPlacement(selectedData, hit.point, currentRotation, out Vector3 groundPosition))
{
// 토대 배치 요청
BuildingManager.Instance.RequestPlaceFoundation(selectedBuildingIndex, groundPosition, currentRotation);
Debug.Log($"[BuildingPlacement] 건설 시작: {selectedData.buildingName}");
}
else
{
Debug.Log("[BuildingPlacement] 배치할 수 없는 위치입니다.");
}
}
}
///
/// 마우스 포인터가 UI 위에 있는지 확인
///
private bool IsPointerOverUI()
{
// EventSystem이 없으면 UI 체크 불가능
if (EventSystem.current == null)
return false;
// New Input System을 사용하는 경우
if (Mouse.current != null)
{
Vector2 mousePosition = Mouse.current.position.ReadValue();
return EventSystem.current.IsPointerOverGameObject();
}
// Legacy Input System (폴백)
return EventSystem.current.IsPointerOverGameObject();
}
///
/// 건설 모드 활성화 상태 확인
///
public bool IsBuildModeActive()
{
return isBuildModeActive;
}
}
}