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; 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 (!Input.GetMouseButton(0)) { 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 (!Input.GetMouseButtonDown(1)) 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) { Debug.Log($"[BlockPlacer] {selectedItem.itemName} не является блоком."); 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; // MaterialPropertyBlock — не создаём новый экземпляр материала var 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); if (Physics.Raycast(ray, out hit, reach, blockLayer)) { // 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; } }