using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; namespace Code.Infrastructure.Utils { public static class HitBoxUtil { private struct ActiveHitbox { public Matrix4x4 Matrix; public Material Material; public float ExpireTime; } private static readonly List _activeBoxes = new(); private static Mesh _unitCubeMesh; private static bool _isInitialized; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void Initialize() { if (_isInitialized) return; _unitCubeMesh = CreateUnitCubeMesh(); // Подписываемся на этап рендера для отрисовки в каждом кадре RenderPipelineManager.beginContextRendering += OnBeginContextRendering; UnityEngine.Camera.onPreCull += OnPreCullLegacy; _isInitialized = true; } /// /// Добавляет хитбокс на заданную длительность (в секундах) /// public static void DrawBox(Vector3 center, Vector3 size, Quaternion rotation, Material material, float duration = 1.0f) { if (material == null || _unitCubeMesh == null) return; Matrix4x4 trs = Matrix4x4.TRS(center, rotation, size); _activeBoxes.Add(new ActiveHitbox { Matrix = trs, Material = material, ExpireTime = Time.unscaledTime + duration }); } /// /// Перегрузка напрямую для BoxCollider /// public static void DrawBoxCollider(BoxCollider box, Material material, float duration = 1.0f) { if (box == null) return; Transform t = box.transform; Vector3 center = t.TransformPoint(box.center); Vector3 size = Vector3.Scale(box.size, t.lossyScale); DrawBox(center, size, t.rotation, material, duration); } /// /// Мгновенная очистка всех активных хитбоксов (например, при выходе из боя) /// public static void Clear() { _activeBoxes.Clear(); } private static void OnBeginContextRendering(ScriptableRenderContext context, List cameras) { RenderHitboxes(); } private static void OnPreCullLegacy(UnityEngine.Camera cam) { // Для совместимости с Built-in RP if (GraphicsSettings.currentRenderPipeline == null) { RenderHitboxes(); } } private static void RenderHitboxes() { if (_activeBoxes.Count == 0) return; float currentTime = Time.unscaledTime; for (int i = _activeBoxes.Count - 1; i >= 0; i--) { ActiveHitbox box = _activeBoxes[i]; if (currentTime >= box.ExpireTime) { _activeBoxes.RemoveAt(i); continue; } // Отправляем GPU команду на отрисовку процедурного меша куба Graphics.DrawMesh( _unitCubeMesh, box.Matrix, box.Material, 0, // Layer null, // Camera (null = все камеры) 0, // Submesh index null, // MaterialPropertyBlock castShadows: false, receiveShadows: false, useLightProbes: false ); } } /// /// Генерирует единичный куб 1x1x1 от -0.5 до 0.5 без создания лишних GameObject /// private static Mesh CreateUnitCubeMesh() { Mesh mesh = new Mesh { name = "Procedural_Unit_Cube" }; Vector3[] vertices = { new(-0.5f, -0.5f, -0.5f), new(0.5f, -0.5f, -0.5f), new(0.5f, 0.5f, -0.5f), new(-0.5f, 0.5f, -0.5f), new(-0.5f, -0.5f, 0.5f), new(0.5f, -0.5f, 0.5f), new(0.5f, 0.5f, 0.5f), new(-0.5f, 0.5f, 0.5f) }; int[] triangles = { // Задняя 0, 2, 1, 0, 3, 2, // Передняя 4, 5, 6, 4, 6, 7, // Левая 0, 7, 3, 0, 4, 7, // Правая 1, 2, 6, 1, 6, 5, // Нижняя 0, 1, 5, 0, 5, 4, // Верхняя 3, 6, 2, 3, 7, 6 }; mesh.vertices = vertices; mesh.triangles = triangles; mesh.RecalculateNormals(); mesh.RecalculateBounds(); return mesh; } } }