270 lines
8.5 KiB
C#
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();
|
|
}
|
|
}
|