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(); 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; } } 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($"[BuildingPlacement] 토대 배치 요청: {selectedData.buildingName}"); } else { Debug.LogWarning("[BuildingPlacement] 배치할 수 없는 위치입니다."); } } } 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); } } }