Files
bansonic_beta_main/Assets/scripts/Utilities/RuntimeVoxelExplosion3DTest.cs

270 lines
8.5 KiB
C#

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<GameObject> spawnedChunks = new List<GameObject>();
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<Renderer>(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<Collider>(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<Renderer>();
if (chunkRenderer != null && chunkMaterial != null)
{
chunkRenderer.sharedMaterial = chunkMaterial;
}
Rigidbody rb = chunk.AddComponent<Rigidbody>();
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();
}
}