Voxel Arena Destruction

Optimization Destruction

I was inspired by the ‘Spleef’ minigame from Minecraft. The idea was to create an arena where players could destroy the floor beneath their opponents, causing them to fall. I wanted the arena to be composed of a large number of small cubes. This required a performant system for handling the arena floor itself, with each cube being individually destructible. On top of this, I wanted individual cubes to be able to be damaged and visually reflect that damaged state, which added another layer of complexity.

For this experiment, I aimed to have around 5000 cubes in the arena.

All tests were run in PIE on high-end hardware. This was a prototype-level comparison meant to identify the most promising approach, not a rigorous benchmark.

Explored Approaches

Naive Approach: Separate Actors

I started with the most straightforward implementation: one actor per cube, each with its own mesh and collision. Each cube could react to damage, track health, and update its visuals through a material parameter. I also implemented actor pooling to reuse cube actors instead of constantly spawning and destroying them, which helped slightly.

This worked, of course, but was very expensive and definitely unsustainable. The implementation was clearly CPU-bound, with detectable frame-time spikes when the arena was created or when several cubes were damaged / destroyed in the same frame.

Instanced Static Meshes

The next iteration used instanced static meshes. I used a single actor for the whole arena with a single UInstancedStaticMeshComponent to render all cubes. This removed the frequent CPU spikes from the actor-per-cube approach, but destroying cubes still produced noticeable frame-time spikes.

My assumption was that this was due to costly updates to the ISM whenever a cube was destroyed. The performance was not good enough, especially when multiple cubes were destroyed in quick succession.

Per-Instance Custom Data

To update the visuals of the instanced cubes, I used the ‘Per-Instance Custom Data’ feature of the UInstancedStaticMeshComponent. This way, I could update the damage state of each cube by simply updating a Custom Data value. This proved to be very efficient. Updating damage visuals had little to no noticeable cost, even when many cubes were updated in the same frame.

Chunking

I switched to a chunked approach. Instead of one ISM for the whole arena, the arena was divided into multiple chunks, each represented by its own actor and UInstancedStaticMeshComponent.

This way, when a cube was destroyed, only the chunk it belonged to needed to be updated. I didn’t do a systematic analysis of the best chunk size, but I experimented with a few numbers and ended up using chunks of 14x14 cubes, which seemed like a good balance and performed well.

This reduced the cost of destruction significantly, because each update affected only a fraction of the full arena.

Batching Updates

I implemented a batching system for updates. The arena was only allowed to process a certain number of updates per frame, and any additional updates are queued for the next frame. This way, even if a large number of cubes are damaged or destroyed in a single frame, the performance impact is spread across multiple frames, preventing significant frame-time spikes. This proved to be overkill, since I never managed to have any frame-time spikes after implementing the chunked approach, but it felt like a sensible safeguard to have in place.

Conclusion

The chunked-ISM approach gave the best results.

The visual feedback and responsiveness felt good, and the performance was solid. I profiled the frame-time during a simulated ‘worst case scenario’ where most cubes were being damaged / destroyed in a single frame, and the game remained responsive and performed well in PIE.

A few notes

Testing Conditions: I ran all tests with a similar number of cubes, around 5000. I ran all tests in PIE on Unreal Engine 5.6, on a machine with a 7950X3D CPU and a 4080 SUPER GPU. The bottleneck was always CPU-side, which I profiled with in-editor profiling tools. PIE is not ideal for benchmarking, but it was more than enough to get a good sense of the performance characteristics of each approach.

Hierarchical Instanced Static Meshes (HISMs): An alternative component to the UInstancedStaticMeshComponent is the UHierarchicalInstancedStaticMeshComponent. HISMs are optimized for large number of meshes and include an out-of-view culling feature, which we did not need. In addition, HISMs take non-negligible time to update their internal hierarchy, which would happen whenever a cube is destroyed. They did not look like a good fit for this use case.

Chaos Destruction: Unreal Engine’s Chaos Destruction system has built-in support for destructible meshes, but it would be a poor fit for this experiment. It is designed for cinematic-quality destruction, not for real-time gameplay with thousands of individually destructible elements.

Other Potential Optimizations: I stopped optimizing once the performance was good enough for the experiment, but there are still several directions worth exploring if I ever wanted to scale the system further:

  • Multi-threading: I don’t think the ISM could be updated outside the game thread, but damage calculation and other gameplay logic could potentially be moved to a separate thread. Possibly relevant, especially if the arena size is increased significantly.
  • Move instead of Destroy: Instead of destroying cubes, I could move them to a ‘graveyard’ location to hide them. This may avoid some of the cost of updating the ISM, since its internal array of instances would not have to be reordered after a destruction.
  • Optimized Collisions: This is a performance optimization for the actual gameplay of such a game, and not for the destruction mechanic itself. Since all cubes are axis-aligned, uniformly sized, and arranged on a plane, some gameplay interactions could potentially be resolved with simple math instead of full collision checks.

More projects

Continue browsing the archive

Plugins / 2025

UI Framework

A reusable UI framework for Unreal Engine, built as a standalone plugin around CommonUI and Lyra-style patterns for screen registration, layer management, modal flows, and UI lifecycle.

Plugins / 2026

Settings System

A flexible and reusable settings plugin for Unreal Engine projects, providing a solid foundation for settings menus, authoring workflows, runtime behavior, and UX considerations.

Tools / 2025

ImGui Debug Tools

A shared ImGui-based debug layer in Unreal Engine 5 that makes new developer-facing tools cheap to add and easy to maintain.