283 lines
10 KiB
C#
283 lines
10 KiB
C#
using Unity.Netcode;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// 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($"<color=green>[BuildingPlacement] 토대 배치 요청: {selectedData.buildingName}</color>");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("<color=yellow>[BuildingPlacement] 배치할 수 없는 위치입니다.</color>");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmos()
|
|
{
|
|
if (!IsOwner || !isBuildModeActive || !showGridBounds) return;
|
|
if (BuildingManager.Instance == null || previewObject == null || !previewObject.activeSelf) return;
|
|
|
|
BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
|
|
if (data == null) return;
|
|
|
|
// Draw grid bounds being used for collision detection
|
|
Bounds bounds = BuildingManager.Instance.GetPlacementBounds(data, previewObject.transform.position, currentRotation);
|
|
|
|
// Check if valid
|
|
bool isValid = BuildingManager.Instance.IsValidPlacement(data, previewObject.transform.position, currentRotation, out _);
|
|
|
|
Gizmos.color = isValid ? new Color(0, 1, 0, 0.5f) : new Color(1, 0, 0, 0.5f);
|
|
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
|
}
|
|
}
|
|
}
|