HOW WE BUILT DESTRUCTIBLE BUILDINGS
I want to do a proper tech writeup on how buildings actually break in Kaiju Protocol. Not the marketing version with vague talk about "next-gen destruction technology." The real version, with the ugly bits, the code I am embarrassed of, and the three or four cases where the system still falls over and cries. If you are building anything that involves city destruction, this is the post I wish I could have read a year ago.
The TL;DR is that we picked prefab chunking over voxels, we built a fracture pipeline on top of Unity's rigid bodies, we spend more time hiding problems with dust and camera shake than solving them, and we have a frame budget so tight that one bad eviction from the chunk pool can ruin the whole party.
Let me walk through it in the order I had to figure it out.
Voxel or prefab, pick one and live with it
The first real decision was the data model. Every building in the game is either a grid of little cubes that know how to break, or a stack of hand authored prefabs that swap to damaged variants. There is no middle ground that is actually nice to work in. I tried.
Voxels give you infinite fidelity. Every chunk of rubble is a real thing you can push around. Teardown built a whole game on this and it is spectacular. The problem is cost at city scale. Back of the envelope, a single twelve story office block at 20cm voxel resolution is around two and a half million voxels. Multiply by a few hundred buildings in a playable district and you are staring at numbers that do not fit in memory on a midrange GPU, let alone fit inside a 16ms frame. You can compress with sparse octrees and stream from disk, but the engineering lift is a full time job for a graphics programmer. I do not have a graphics programmer. I have me and Unity.
Prefabs are the other end. You author a building by hand, you decide in advance how it breaks, and the engine never thinks about geometry at runtime, only about which prefab to show. The cost is that breaks only happen at the granularity you authored. Your hole is as small as the smallest chunk you made. Your destruction is really a state machine with nice animations bolted on.
I went prefab. The reasoning, in order:
- The art style is low poly with clean silhouettes. Voxels would fight that. The whole look of the game depends on flat shaded polygons catching light in predictable ways, and a voxel skin on a polygon monster would look wrong in every frame.
- Kaiju combat is about big gestures, not surgical precision. Nobody playing this game wants to chisel through a wall. They want to shoulder charge through the whole block.
- Solo dev. I cannot eat a six month detour into custom voxel rendering and still ship this decade.
Once that call was made, everything downstream fell into place. And a few things got worse.
The chunk pipeline
A building in Kaiju Protocol is a hierarchy. At the top is the Building component, which owns metadata like type, height, footprint, mass, and a list of Floor children. Each Floor owns a list of Chunk children. Each Chunk is a mesh, a collider, a handful of destruction states, and a pointer to a pooled debris set.
Pseudocode, because the real code has too many Unity specific bits to be useful:
class Building {
BuildingArchetype archetype
List<Floor> floors
float integrity
void OnChunkDestroyed(Chunk c) {
c.floor.RemoveSupport(c.supportValue)
SpawnDebris(c)
if (c.floor.SupportBelow(c.floor.collapseThreshold))
QueueFloorCollapse(c.floor)
}
}
class Floor {
int supportCount
float supportMax
void QueueCollapse() {
// stagger by a small random delay per chunk
foreach (chunk in chunks)
chunk.ScheduleCollapse(Random.Range(0.05f, 0.25f))
PropagateToFloorsAbove()
}
}
The structural check is not structural. It is a counter. Each chunk contributes a number to its floor's supportCount. Destroy chunks, number goes down, when it crosses a per-floor threshold the floor flags itself as unstable and collapses. Floors above an unstable floor also lose support from below, which is how you get the pancake effect when you knock out a ground floor.
It is a glorified state machine. It does not know what stress is. It does not care about load paths. And yet, because the thresholds are tuned to roughly match how real buildings fail in the reference footage I pinned to my wall, the output feels structural. The player is not running statics in their head. They are reading cause and effect.
The fracture system, or what happens when a chunk actually breaks
When a chunk takes enough damage to break, the sequence is:
- Chunk mesh is swapped to its damaged variant, which is a pre-authored lower poly version with visible breakage.
- A debris set is pulled from the pool. Each debris piece is a small rigid body with its own collider.
- Debris pieces are positioned at the chunk's world space, oriented to match the impact direction, and given an initial impulse along the hit normal plus a small random tangent component so they fan out instead of flying in a perfect cone.
- A dust particle system fires at the impact point, sized to match the chunk volume.
- A sound event is triggered with a material tag, so concrete sounds like concrete and glass sounds like glass.
- A camera shake pulse scales with the chunk mass and distance to camera.
- The floor's supportCount is decremented. If the floor now wants to collapse, a collapse event is queued.
The pre-authored damage variants are the whole game. I tried runtime mesh fracturing early on with a plugin that does Voronoi based cuts. Results looked great and it cost me 8 to 14ms per fracture. In a 16ms frame budget, that is a full stutter every time anything breaks. So runtime fracturing is out. Every break in the game is a swap between hand built meshes plus a pool pull for the flying debris. No geometry is created at runtime, ever.
The pool, and why it is the heart of the system
Here is the actual bottleneck. Not physics, not rendering, not mesh swaps. Garbage collection. Unity's GC will murder your framerate the first time you allocate a thousand rigid bodies during a building collapse, and worse, it will do it at unpredictable moments because nothing about GC is on your schedule.
So every debris piece, every dust particle, every smoke puff, every audio source, every visual effect the destruction system uses comes from a pool. Pools are pre-allocated at level load and live for the lifetime of the scene. Nothing is ever instantiated during gameplay. Nothing is ever destroyed. Objects are fetched, used, and returned.
The pool looks roughly like this:
class DebrisPool {
Queue<Debris> available
LinkedList<Debris> active
int capacity
Debris Acquire() {
if (available.Count == 0)
EvictOldest()
var d = available.Dequeue()
active.AddLast(d)
return d
}
void Release(Debris d) {
active.Remove(d)
available.Enqueue(d)
}
void EvictOldest() {
var oldest = active.First.Value
oldest.DespawnWithDust()
Release(oldest)
}
}
The eviction is important. When the pool is empty and a new debris piece is requested, the oldest active piece is despawned, hidden by a small dust puff so the player does not catch the pop. This only looks right if the dust is convincing, which is why I spent three weeks tuning particle curves instead of writing "real" systems code.
Capacity is tuned per debris class. Small concrete chunks: 400. Large structural pieces: 80. Glass shards: 600 because they break in huge numbers but are small enough to evict quickly without anyone noticing.
LOD, the unsexy savior
Level of detail is the only reason this works at city scale. Every building has three LOD states that the camera distance chooses between:
- LOD0, near camera, full chunking, real collisions, real debris, real dust. Budget: about 2ms during active destruction.
- LOD1, mid distance, reduced chunk count, simplified colliders, debris is particle-only not physics. Budget: about 0.5ms.
- LOD2, far distance, no chunking at all, just intact and rubble states with a transition animation. Budget: basically free.
The switching is distance based with a small hysteresis so a building on the edge does not flicker between LODs. That hysteresis cost me an afternoon to find, because without it you get weird popping where half the block changes detail every frame as you walk a corner.
There is also a special case for buildings the player has damaged but walked away from. Those freeze in their current damage state, physics disabled. If the player comes back, physics reactivates with a two frame warmup that sometimes causes a small visual pop. I have a fix on a branch. It has been on a branch for two months.
Culling the dead
Originally I left destroyed chunks as rubble piles, each one still a rigid body considered by the broadphase. Looked great in screenshots. Ran at 40fps.
The fix was staged culling:
- Rubble that stopped moving for more than 2 seconds gets merged into a single static rubble mesh per building.
- That merged mesh uses a convex hull collider, not per piece.
- The individual rigid bodies go back to the pool.
- If the building's neighbor collapses and throws force at the pile, the pile does not react. This is a compromise. You cannot have everything.
The merge happens on a background thread using Unity's Jobs system. The math is straightforward, just a combined bounding volume and a hull generation pass. The tricky part was that Unity's mesh APIs are not thread safe, so the hull generation produces raw vertex and index buffers on the worker thread, and the main thread uploads them during a later frame when it has spare budget.
Memory-wise, the merge is a huge win. A collapsed skyscraper goes from around 600 active rigid bodies down to one. Physics frame time for that building drops from around 3ms to under 0.1ms.
Falling debris physics, the part that still breaks
Here is where I am honest. The falling debris looks great 90% of the time and broken the other 10%.
The common failure is tunneling. Large debris pieces moving fast through thin colliders can clip right through them. Unity's discrete collision detection is not reliable above a certain velocity to size ratio, and large chunks falling off a skyscraper hit those numbers easily. I switched those pieces to continuous collision detection, which fixed tunneling but cost about 0.3ms per piece per frame. That means I can only afford continuous collision on the few biggest pieces per event, so medium pieces still tunnel sometimes. When it happens you see a slab vanish through a car and reappear rolling on the ground.
The second failure is the interpenetration wake up I mentioned earlier. If a debris pile lands on another debris pile before the first one has merged, the colliders can overlap. When the physics engine wakes them up it resolves the overlap by shoving bodies outward, which sometimes launches a pile skyward. This is hilarious on Discord clips and terrible in an actual playthrough.
The third is the cascade problem. If a piece of building A lands on building B, building B is supposed to take damage proportional to impact force. I approximate this with a trigger volume that sums kinetic energy of bodies entering it. The approximation is rough. Sometimes B collapses because a small piece tapped it fast. Sometimes it ignores a huge chunk because the chunk's velocity did not align with the trigger's entry detection. I have a debug overlay showing the running energy sum per building, and watching it is how I find bad cases.
The frame budget, in numbers
The target is 60fps on a 3060 class GPU. 16ms total. Here is the approximate split during an active destruction event, based on profiler captures:
- Rendering: 6ms
- Animation and game logic: 2ms
- AI and crowd: 1.5ms
- Audio: 0.5ms
- Physics broadphase and narrowphase: 2ms
- Destruction system, chunk evaluation and collapse scheduling: 0.5ms
- Debris spawn and pool management: 0.8ms
- Particle systems for dust and secondary effects: 1.2ms
- Margin: 1.5ms
Margin gets eaten first when things get busy. Multi-building collapses during heavy combat can push total frame time past 20ms, which means dropped frames. The current mitigation is a collapse rate limiter that stretches simultaneous collapses across 100 to 300ms. It is not perfect but it keeps the average frame time inside budget.
What I would do differently
If I started over today, a few things change.
I would commit to pooling from day one, not retrofit it in month three. Every system that allocates at runtime had to be rewritten, and some rewrites took weeks.
I would build the debug tooling before the first prototype. My overlay visualizes support counts per floor, energy sums per building, pool occupancy, and LOD state. Every one of those was added after a bug that would have been trivial with the tool.
I would resist the temptation to build custom physics. I spent a week writing a little verlet based debris simulator. It was fun. It was pointless. Unity's physics is fine for 95% of what destruction needs. The remaining 5% is handled better by animation and fakery than any simulation I could hand roll in reasonable time.
The other frustrations and half built systems in the Kaiju Protocol destructible cities post are still accurate, and if you want the higher level take on why I made the design decisions I did, that is the place to start. This post is the engineering layer underneath it.
What ships in the demo
The demo drops with four building archetypes, three LODs each, about 120 unique debris assets across five material classes, and collapse logic tuned through roughly 200 hours of playtesting. It runs 58 to 62fps on my 3060 rig during heavy combat, dropping to around 50 in the worst cascading multi-building scenarios. Crashed twice during testing, both times due to a race between the merge job and the LOD switch that I think I have now fixed.
The destruction model is, in the strictest technical sense, almost entirely fake. Counters instead of stress. State machine instead of simulation. Pre-authored fractures instead of runtime cuts. Particle curtains hiding the swaps. Every shortcut is in service of staying inside 16ms while still making the player feel like they knocked a skyscraper over.
Which, as far as the last round of playtests tells me, is working. The next problem is interior destruction, and I have some ideas, most of which are also cheats.
LIKED THIS? STAY IN THE LOOP
New posts, game updates, and things you won't find anywhere else.