Files
my-project/Assets/Blocks/Scripts/BlockPlacer.cs
2026-04-17 02:14:56 +05:00

190 lines
7.2 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
/// <summary>
/// Компонент на игроке.
/// ЛКМ — удержание → разрушение блока с прогрессом.
/// ПКМ — мгновенная постановка блока из хотбара.
/// </summary>
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<Collider>());
_overlayRenderer = _breakOverlay.GetComponent<MeshRenderer>();
_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;
}
}