using UnityEngine; /// /// Компонент на игроке. /// ЛКМ — удержание → разрушение блока с прогрессом. /// ПКМ — мгновенная постановка блока из хотбара. /// public class BlockPlacer : MonoBehaviour { [Header("Settings")] public float reach = 5f; // дальность в Unity-единицах public float breakTime = 1f; // секунд до разрушения public LayerMask blockLayer; // слой чанков (назначить в Inspector) [Header("Break Overlay")] public Material breakMaterial; // Blocks/BlockBreak шейдер public Texture2D crackTexture; // текстура трещин (grayscale) // ── Приватное ───────────────────────────────────────────────────────────── Camera _cam; float _breakProgress; Vector3Int _breakingBlock = Vector3Int.one * int.MinValue; GameObject _breakOverlay; MeshRenderer _overlayRenderer; MaterialPropertyBlock _mpb; static readonly int PropProgress = Shader.PropertyToID("_Progress"); void Start() { _cam = Camera.main; if (breakMaterial == null) { var shader = Shader.Find("Blocks/BlockBreak"); if (shader != null) breakMaterial = new Material(shader); } if (breakMaterial != null && crackTexture != null) breakMaterial.SetTexture("_CrackTex", crackTexture); } void Update() { HandleBreak(); HandlePlace(); } // ── Разрушение (ЛКМ удержание) ─────────────────────────────────────────── void HandleBreak() { if (PlayerInput.Instance == null || !PlayerInput.Instance.BreakHeld) { CancelBreak(); return; } if (!Raycast(out RaycastHit hit, out Vector3Int blockPos)) { CancelBreak(); return; } // Сменился ли целевой блок? if (blockPos != _breakingBlock) { CancelBreak(); _breakingBlock = blockPos; SpawnBreakOverlay(blockPos); } _breakProgress += Time.deltaTime / breakTime; UpdateOverlay(_breakProgress); if (_breakProgress >= 1f) { FinishBreak(blockPos); } } void FinishBreak(Vector3Int blockPos) { string blockId = WorldManager.Instance.BreakBlock(blockPos); CancelBreak(); if (blockId == null) return; // Дроп предмета var def = BlockRegistry.Instance?.GetById(blockId); if (def?.linkedItem != null && InventoryItemFactory.Instance != null) { Vector3 dropPos = new Vector3(blockPos.x + 0.5f, blockPos.y + 0.5f, blockPos.z + 0.5f); InventoryItemFactory.Instance.CreateWorldItem(def.linkedItem, 1, dropPos); } } void CancelBreak() { _breakProgress = 0f; _breakingBlock = Vector3Int.one * int.MinValue; if (_breakOverlay != null) Destroy(_breakOverlay); _breakOverlay = null; } // ── Постановка (ПКМ) ───────────────────────────────────────────────────── void HandlePlace() { if (PlayerInput.Instance == null || !PlayerInput.Instance.PlacePressed) return; if (!Raycast(out RaycastHit hit, out Vector3Int blockPos)) return; // Ячейка, куда ставим = сосед грани которую мы смотрим Vector3Int placePos = blockPos + Vector3Int.RoundToInt(hit.normal); // Не ставим блок внутри игрока if (IsInsidePlayer(placePos)) return; // Получаем блок из хотбара var selectedItem = InventoryManager.Instance?.GetSelectedHotbarItem(); if (selectedItem == null) return; var def = BlockRegistry.Instance?.GetByItemId(selectedItem.id); if (def == null) return; WorldManager.Instance.PlaceBlock(placePos, def.blockId); // Потребляем 1 из хотбара InventoryManager.Instance.RemoveItem(selectedItem.id, 1); } // ── Overlay разрушения ──────────────────────────────────────────────────── void SpawnBreakOverlay(Vector3Int blockPos) { if (breakMaterial == null) return; _breakOverlay = GameObject.CreatePrimitive(PrimitiveType.Cube); _breakOverlay.name = "BreakOverlay"; _breakOverlay.transform.position = blockPos + new Vector3(0.5f, 0.5f, 0.5f); _breakOverlay.transform.localScale = Vector3.one * 1.001f; // чуть больше, без z-fight // Убираем коллайдер — он не нужен Destroy(_breakOverlay.GetComponent()); _overlayRenderer = _breakOverlay.GetComponent(); _overlayRenderer.sharedMaterial = breakMaterial; _overlayRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } void UpdateOverlay(float progress) { if (_overlayRenderer == null) return; _mpb ??= new MaterialPropertyBlock(); _overlayRenderer.GetPropertyBlock(_mpb); _mpb.SetFloat(PropProgress, progress); _overlayRenderer.SetPropertyBlock(_mpb); } // ── Утилиты ─────────────────────────────────────────────────────────────── bool Raycast(out RaycastHit hit, out Vector3Int blockPos) { blockPos = default; Ray ray = new Ray(_cam.transform.position, _cam.transform.forward); // blockLayer == 0 означает "Nothing" в Unity LayerMask — рейкаст ни по чему не попадёт. // Если слой не назначен в Inspector, бьём по всем слоям (-1 = Everything). int mask = blockLayer.value == 0 ? ~0 : blockLayer.value; if (Physics.Raycast(ray, out hit, reach, mask)) { // hit.point лежит на грани — смещаемся чуть внутрь блока Vector3 inner = hit.point - hit.normal * 0.01f; blockPos = WorldManager.WorldToBlock(inner); return true; } return false; } bool IsInsidePlayer(Vector3Int blockPos) { // Проверяем две ячейки (ноги и голова) Vector3Int feet = WorldManager.WorldToBlock(transform.position); Vector3Int head = WorldManager.WorldToBlock(transform.position + Vector3.up * 1.8f); return blockPos == feet || blockPos == head; } }