using System.Collections.Generic; using UnityEngine; public class RuntimeVoxelExplosion3DTest : MonoBehaviour { [Header("Trigger")] [SerializeField] private bool enableDebugKey = true; [SerializeField] private KeyCode triggerKey = KeyCode.Alpha0; [SerializeField] private bool supportKeypad0 = true; [SerializeField] private bool debugLogs = true; [Header("Target")] [SerializeField] private GameObject targetObject; [SerializeField] private Transform chunkParent; [SerializeField] private bool disableTargetOnExplode = true; [SerializeField] private bool destroyPreviousChunks = true; [Header("Voxel Cut")] [SerializeField] private int chunksX = 4; [SerializeField] private int chunksY = 4; [SerializeField] private int chunksZ = 4; [SerializeField] private float chunkScaleMultiplier = 0.92f; [SerializeField] private float occupancyPadding = 0.02f; [SerializeField] private bool requireColliderOverlap = true; [SerializeField] private int maxChunkCount = 128; [Header("Explosion")] [SerializeField] private float explosionForce = 1.2f; [SerializeField] private float upwardForce = 0.08f; [SerializeField] private float randomForceJitter = 0.12f; [SerializeField] private float randomTorque = 5f; [SerializeField] private float maxChunkSpeed = 0.9f; [SerializeField] private float linearDamping = 5f; [SerializeField] private float angularDamping = 7f; [SerializeField] private bool disableGravity = false; [Header("Cleanup")] [SerializeField] private bool autoDestroyChunks = false; [SerializeField] private float destroyDelay = 5f; private readonly List spawnedChunks = new List(); private bool exploded; private void Update() { if (!enableDebugKey) return; bool pressed = Input.GetKeyDown(triggerKey); if (!pressed && supportKeypad0) { pressed = Input.GetKeyDown(KeyCode.Keypad0); } if (pressed) { if (debugLogs) { Debug.Log("[RuntimeVoxelExplosion3DTest] Explosion key pressed."); } TriggerExplosion(); } } [ContextMenu("Trigger Runtime Voxel Explosion")] public void TriggerExplosion() { if (exploded) { if (debugLogs) { Debug.Log("[RuntimeVoxelExplosion3DTest] Already exploded, ignoring trigger."); } return; } GameObject target = targetObject != null ? targetObject : gameObject; if (targetObject == null && debugLogs) { Debug.Log("[RuntimeVoxelExplosion3DTest] targetObject not assigned, using current GameObject."); } if (target == null) { Debug.LogWarning("[RuntimeVoxelExplosion3DTest] Missing targetObject."); return; } Renderer[] renderers = target.GetComponentsInChildren(true); if (renderers == null || renderers.Length == 0) { Debug.LogWarning("[RuntimeVoxelExplosion3DTest] Target has no renderer."); return; } if (destroyPreviousChunks) { ClearSpawnedChunks(); } Bounds bounds = CalculateCombinedBounds(renderers); Collider[] colliders = target.GetComponentsInChildren(true); Material chunkMaterial = ResolveChunkMaterial(renderers); int safeX = Mathf.Max(1, chunksX); int safeY = Mathf.Max(1, chunksY); int safeZ = Mathf.Max(1, chunksZ); Vector3 cellSize = new Vector3( bounds.size.x / safeX, bounds.size.y / safeY, bounds.size.z / safeZ); int spawnedCount = 0; for (int x = 0; x < safeX; x++) { for (int y = 0; y < safeY; y++) { for (int z = 0; z < safeZ; z++) { if (spawnedCount >= Mathf.Max(1, maxChunkCount)) break; Vector3 center = new Vector3( bounds.min.x + cellSize.x * (x + 0.5f), bounds.min.y + cellSize.y * (y + 0.5f), bounds.min.z + cellSize.z * (z + 0.5f)); if (requireColliderOverlap && colliders.Length > 0 && !CellOverlapsTarget(center, cellSize, colliders)) continue; GameObject chunk = GameObject.CreatePrimitive(PrimitiveType.Cube); chunk.name = $"RuntimeChunk_{x}_{y}_{z}"; chunk.transform.SetParent(chunkParent != null ? chunkParent : null, true); chunk.transform.position = center; chunk.transform.rotation = target.transform.rotation; chunk.transform.localScale = Vector3.Scale(cellSize, Vector3.one * Mathf.Clamp(chunkScaleMultiplier, 0.01f, 1f)); Renderer chunkRenderer = chunk.GetComponent(); if (chunkRenderer != null && chunkMaterial != null) { chunkRenderer.sharedMaterial = chunkMaterial; } Rigidbody rb = chunk.AddComponent(); rb.mass = 0.08f; rb.linearDamping = linearDamping; rb.angularDamping = angularDamping; rb.useGravity = !disableGravity; ApplyChunkImpulse(rb, bounds.center); spawnedChunks.Add(chunk); spawnedCount++; } } } if (disableTargetOnExplode) { target.SetActive(false); } if (debugLogs) { Debug.Log($"[RuntimeVoxelExplosion3DTest] Spawned {spawnedCount} runtime chunks."); } exploded = true; if (autoDestroyChunks && destroyDelay > 0f) { Invoke(nameof(ClearSpawnedChunks), destroyDelay); } } [ContextMenu("Reset Runtime Voxel Explosion")] public void ResetExplosion() { CancelInvoke(nameof(ClearSpawnedChunks)); ClearSpawnedChunks(); exploded = false; if (targetObject != null) { targetObject.SetActive(true); } } private Bounds CalculateCombinedBounds(Renderer[] renderers) { Bounds bounds = renderers[0].bounds; for (int i = 1; i < renderers.Length; i++) { bounds.Encapsulate(renderers[i].bounds); } return bounds; } private Material ResolveChunkMaterial(Renderer[] renderers) { for (int i = 0; i < renderers.Length; i++) { if (renderers[i] != null && renderers[i].sharedMaterial != null) return renderers[i].sharedMaterial; } return null; } private bool CellOverlapsTarget(Vector3 center, Vector3 cellSize, Collider[] colliders) { Bounds cellBounds = new Bounds(center, cellSize + Vector3.one * occupancyPadding); for (int i = 0; i < colliders.Length; i++) { Collider col = colliders[i]; if (col == null || !col.enabled) continue; if (col.bounds.Intersects(cellBounds)) return true; } return false; } private void ApplyChunkImpulse(Rigidbody rb, Vector3 explosionCenter) { if (rb == null) return; Vector3 direction = rb.worldCenterOfMass - explosionCenter; if (direction.sqrMagnitude < 0.0001f) { direction = Random.onUnitSphere; } float jitter = 1f + Random.Range(-randomForceJitter, randomForceJitter); Vector3 impulse = direction.normalized * Mathf.Max(0f, explosionForce * jitter); impulse += Vector3.up * upwardForce; rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; rb.AddForce(impulse, ForceMode.Impulse); if (randomTorque > 0f) { rb.AddTorque(Random.onUnitSphere * randomTorque, ForceMode.Impulse); } if (rb.linearVelocity.sqrMagnitude > maxChunkSpeed * maxChunkSpeed) { rb.linearVelocity = rb.linearVelocity.normalized * maxChunkSpeed; } } private void ClearSpawnedChunks() { for (int i = 0; i < spawnedChunks.Count; i++) { if (spawnedChunks[i] != null) { Destroy(spawnedChunks[i]); } } spawnedChunks.Clear(); } }