The Kids With Sticks Team has recently published a thorough tutorial on how to create destructible objects in UE4 and Blender. With their permission, we are sharing it on our website. Follow this link to see the original post.
While Chaos physics engine is on the way, we were testing approaches to make destroyables in Unreal Engine 4. Here you can find how we did it.
REQUIREMENTS
As always we start by pointing out what we would like to reach:
- Artistic Control. We want our graphics artist to be able to create destructible as they wish.
- Don’t break gameplay. They should be only visual representation not breaking any gameplay-related stuff.
- Optimized. We want to have full control of performance and don’t break CPU performance.
- Easy to setup. They will be configured by graphics artists so we want them to be easy to configure without too many steps.
We were searching for best references that fit our needs and we found that destructible from Dark Souls 3 and Bloodborne fit perfectly for us.
BASIC IDEA
The basic idea is easy:
- Have base mesh which is visible
- Add mesh parts which are hidden
- On break: hide base -> show parts -> enable physics
PREPARING ASSETS
We are using Blender for object preparation, we recommend using it too. To create fracture mesh we use Blender Addon called Cell Fracture.
- Enable Addon
First, you would need to enable the addon as it’s not enabled by default.
- Search for Addon (F3)
Then enable addon on selected mesh.
- Configure Settings
- Run addon
Check out our settings here:
- Select by Material to unwrap the cut parts
Then just create UVs for parts.
- Add Edge Split
Edge Split will correct shading.
- Link Modifiers
This will apply Edge Split modifier to all selected parts.
- Final
This is how it looks in Blender. Basically, we don’t need to model all parts.
IMPLEMENTATION
Base class
Our destroyable is an Actor who has a couple of components:
- Root scene
- Static Mesh which is our base mesh
- Box for collision
- Another Box for overlaps
- Radial Force
We are configuring some things in the constructor:
- Disabling tick (basically, always remember to disable tick on actors that don’t need it)
- Set mobility to static for all components
- Disabling affecting navigation
- Set up collision profiles
Setting up Actor in Constructor.
ADestroyable::ADestroyable()
{
PrimaryActorTick.bCanEverTick = false; //always disable tick if you don't need it
bDestroyed = false;
RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootComp")); // root scene which will hold everything else
RootScene->SetMobility(EComponentMobility::Static);
RootComponent = RootScene;
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BaseMeshComp")); // base mesh which will store whole mesh
Mesh->SetMobility(EComponentMobility::Static);
Mesh->SetupAttachment(RootScene);
Collision = CreateDefaultSubobject<UBoxComponent>(TEXT("CollisionComp")); // collision which will look for overlaps
Collision->SetMobility(EComponentMobility::Static);
Collision->SetupAttachment(Mesh);
OverlapWithNearDestroyable = CreateDefaultSubobject<UBoxComponent>(TEXT("OverlapWithNearDestroyableComp")); // another collision for checking other destroyables near
OverlapWithNearDestroyable->SetMobility(EComponentMobility::Static);
OverlapWithNearDestroyable->SetupAttachment(Mesh);
Force = CreateDefaultSubobject<URadialForceComponent>(TEXT("RadialForceComp")); // minor force component to add some impulse on break
Force->SetMobility(EComponentMobility::Static);
Force->SetupAttachment(RootScene);
Force->Radius = 100.f;
Force->bImpulseVelChange = true;
Force->AddCollisionChannelToAffect(ECC_WorldDynamic);
/* set collisions */
Mesh->SetCollisionObjectType(ECC_WorldDynamic);
Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
Mesh->SetCollisionResponseToAllChannels(ECR_Block);
Mesh->SetCollisionResponseToChannel(ECC_Visibility, ECR_Ignore);
Mesh->SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore);
Mesh->SetCollisionResponseToChannel(ECC_CameraFadeOverlap, ECR_Overlap);
Mesh->SetCollisionResponseToChannel(ECC_Interaction, ECR_Ignore);
Mesh->SetCanEverAffectNavigation(false); // we don't want to change our navigation
Collision->SetBoxExtent(FVector(50.f, 50.f, 50.f));
Collision->SetCollisionObjectType(ECC_WorldDynamic);
Collision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
Collision->SetCollisionResponseToAllChannels(ECR_Ignore);
Collision->SetCollisionResponseToChannel(ECC_Melee, ECR_Overlap);
Collision->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
Collision->SetCollisionResponseToChannel(ECC_Projectile, ECR_Overlap);
Collision->SetCanEverAffectNavigation(false); // we don't want to change our navigation
Collision->OnComponentBeginOverlap.AddDynamic(this, &ADestroyable::OnBeginOverlap); // bind to overlap to be able to react
Collision->OnComponentEndOverlap.AddDynamic(this, &ADestroyable::OnEndOverlap);
OverlapWithNearDestroyable->SetBoxExtent(FVector(40.f, 40.f, 40.f));
OverlapWithNearDestroyable->SetCollisionObjectType(ECC_WorldDynamic);
OverlapWithNearDestroyable->SetCollisionEnabled(ECollisionEnabled::NoCollision); // disable collision as it will be enabled for one frame when breaking
OverlapWithNearDestroyable->SetCollisionResponseToAllChannels(ECR_Ignore);
OverlapWithNearDestroyable->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Overlap);
OverlapWithNearDestroyable->CanCharacterStepUp(false);
OverlapWithNearDestroyable->SetCanEverAffectNavigation(false); // we don't want to change our navigation
}
In Begin Play we are gathering some data and configure them:
- Search for all parts using “dest” tag
- Setup collision for all of them so artist doesn’t need to think about this step
- Set mobility to static
- Hide all parts
Configure parts on Begin Play.
void ADestroyable::ConfigureBreakablesOnStart()
{
Mesh->SetCullDistance(BaseMeshMaxDrawDistance); // custom draw distance for our base mesh
for (UStaticMeshComponent* Comp : GetBreakableComponents()) // get all parts
{
Comp->SetCollisionEnabled(ECollisionEnabled::NoCollision); // disable collision
Comp->SetCollisionResponseToAllChannels(ECR_Ignore); // ignore everything
Comp->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block); // parts should block only static (eg. world)
Comp->SetMobility(EComponentMobility::Static); // always remember to set static if something isn't moving
Comp->SetHiddenInGame(true); // hide parts on start, we have base mesh to show whole mesh
}
}
Simple function to get parts components.
TArray<UStaticMeshComponent*> ADestroyable::GetBreakableComponents()
{
if (BreakableComponents.Num() == 0) // do we have cached data?
{
TInlineComponentArray<UStaticMeshComponent*> ComponentsByClass; //store all static mesh components
GetComponents(ComponentsByClass);
TArray<UStaticMeshComponent*> ComponentsByTag; // store all static mesh components with "dest" tag which are our parts
ComponentsByTag.Reserve(ComponentsByClass.Num()); // reserve size
for (UStaticMeshComponent* Component : ComponentsByClass)
{
if (Component->ComponentHasTag(TEXT("dest")))
{
ComponentsByTag.Push(Component);
}
}
BreakableComponents = ComponentsByTag; // cache data for later use
}
return BreakableComponents;
}
Destroying part
There are three places which will trigger break:
- OnOverlap
For example: when you want to break if someone is dashing or using the specific thing like rolling ball.
- OnTakeDamage
When destroyable is receiving damage.
- OnOverlapWithNearDestroyable
This one is important. When you put one destroyable on another one. In our case, they both break as we don’t want to have too many specific physics issues to handle.
Breaking Part Flow
Showing breakables.
void ADestroyable::ShowBreakables(FVector DealerLocation, bool ByOtherDestroyable /*= false*/)
{
float ImpulseStrength = ByOtherDestroyable ? -500.f : -1000.f; // setup impuse power. #todo move to variable
FVector Impulse = (DealerLocation - GetActorLocation()).GetSafeNormal() * ImpulseStrength; // setup impulse vector based on dealer location
for (UStaticMeshComponent* Comp : GetBreakableComponents()) // get all parts
{
Comp->SetMobility(EComponentMobility::Movable); //we will enable physics so they need to be movable from now
FBodyInstance* RootBI = Comp->GetBodyInstance(NAME_None, false); // check if we have body instance
if (RootBI)
{
RootBI->bGenerateWakeEvents = true; // we do want to have physics events on parts
if (PartsGenerateHitEvent)
{
RootBI->bNotifyRigidBodyCollision = true; // make sure we get OnComponentHit event
Comp->OnComponentHit.AddDynamic(this, &ADestroyable::OnPartHitCallback); // bind on component hit as we want to spawn some effects there
}
}
Comp->SetHiddenInGame(false); // show part
Comp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); // enable collision
Comp->SetSimulatePhysics(true); // enable physics
Comp->AddImpulse(Impulse, NAME_None, true); // fire impulse
if (ByOtherDestroyable)
Comp->AddAngularImpulseInRadians(Impulse * 5.f); // if breaked by other near destroyable make some changes to impulse #todo: move this to variable
//set parts draw distance
Comp->SetCullDistance(PartsMaxDrawDistance);
Comp->OnComponentSleep.AddDynamic(this, &ADestroyable::OnPartPutToSleep); // bind on physics sleep to disable physics and set mobility to static
}
}
Main break function.
void ADestroyable::Break(AActor* InBreakingActor, bool ByOtherDestroyable /*= false*/)
{
if (bDestroyed) // if already destroyed don't do anything
return;
bDestroyed = true;
Mesh->SetHiddenInGame(true); // hide base mesh as it isn't needed from now
Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); // disable collision on base mesh
Collision->SetCollisionEnabled(ECollisionEnabled::NoCollision); // disable overlap collision checks
OverlapWithNearDestroyable->SetCollisionEnabled(ECollisionEnabled::NoCollision);
ShowBreakables(InBreakingActor->GetActorLocation(), ByOtherDestroyable); // show parts
Force->bImpulseVelChange = !ByOtherDestroyable; // configure force component if breaked by other destroyable
Force->FireImpulse(); // fire out radial force
/* check other overlaping destroyables now */
OverlapWithNearDestroyable->SetCollisionEnabled(ECollisionEnabled::QueryOnly); // enable collision to check other near destoryables
TArray<AActor*> OtherOverlapingDestroyables;
OverlapWithNearDestroyable->GetOverlappingActors(OtherOverlapingDestroyables, ADestroyable::StaticClass()); // get other destroyables inside box
for (AActor* OtherActor : OtherOverlapingDestroyables)
{
if (OtherActor == this)
continue;
if (ADestroyable* OtherDest = Cast<ADestroyable>(OtherActor))
{
if (OtherDest->IsDestroyed()) // check if already destroyed
continue;
OtherDest->Break(this, true); // break other destroyable which is near
}
}
OverlapWithNearDestroyable->SetCollisionEnabled(ECollisionEnabled::NoCollision); // disable collision
GetWorld()->GetTimerManager().SetTimer(ForceSleepTimerHandle, this, &ADestroyable::ForceSleep, FORCESLEEPDELAY, false); // force sleep if we don't get event from physics
if(bDestroyAfterDelay)
GetWorld()->GetTimerManager().SetTimer(DestroyAfterBreakTimerHandle, this, &ADestroyable::DestroyAfterBreaking, DESTROYACTORDELAY, false); // setup timer to check if we can destroy whole actor
OnBreakBP(InBreakingActor, ByOtherDestroyable); // let blueprint handle audio-visuals
}
Handle sleep
When a part is going to sleep (from sleep physics event or forced sleep by delay) we are disabling physics/collision and set mobility to static. Thanks to that performance will increase.
Each primitive component with physics can go to sleep. We are binding to this function when breaking.
void ADestroyable::OnPartPutToSleep(UPrimitiveComponent* InComp, FName InBoneName)
{
InComp->SetSimulatePhysics(false); // disable physics
InComp->SetCollisionEnabled(ECollisionEnabled::NoCollision); // disable collision
InComp->SetMobility(EComponentMobility::Static); // mark part as static from now
/* at this point part is static and won't interact with world */
}
Sometimes your physics asset won’t go to sleep and will still update even if you don’t see any movement. We are forcing all parts to go to sleep after 15 seconds if they still simulate physics:
Force sleep function which is called by timer.
void ADestroyable::ForceSleep()
{
for (UStaticMeshComponent* Comp : GetBreakableComponents()) // get all parts
{
if (Comp->RigidBodyIsAwake()) // check if physics is running
OnPartPutToSleep(Comp, NAME_None); // put part to sleep
}
GetWorld()->GetTimerManager().ClearTimer(ForceSleepTimerHandle); // clear forcing to sleep timer
}
Handle destroy
After breaking we are trying to check if we can destroy the actor (eg. player is far) if not – check again in some time.
Try to destroy if player is away.
void ADestroyable::DestroyAfterBreaking()
{
if (IsPlayerNear()) // check if player is near
{
//try again next time
GetWorld()->GetTimerManager().SetTimer(DestroyAfterBreakTimerHandle, this, &ADestroyable::DestroyAfterBreaking, DESTROYACTORDELAY, false);
}
else
{
GetWorld()->GetTimerManager().ClearTimer(DestroyAfterBreakTimerHandle); // clear timer
Destroy(); // finally destroy actor #todo add pooling support for destroyables
}
}
On Part hit callback
We decided that Blueprints are responsible for the audio-visual part of the game so we are adding Blueprints events where we can.
void ADestroyable::OnPartHitCallback(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
OnPartHitBP(Hit, NormalImpulse, HitComp, OtherComp); // let blueprint handle audio-visual
}
End Play and clearing out
Our game can be played in editor and custom editors created by us. That’s why we need to clear out everything we can on EndPlay.
void ADestroyable::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
/* clear out our timers */
GetWorld()->GetTimerManager().ClearTimer(DestroyAfterBreakTimerHandle);
GetWorld()->GetTimerManager().ClearTimer(ForceSleepTimerHandle);
Super::EndPlay(EndPlayReason);
}
Configuration in Blueprints
The configuration is simple. You just put parts attached to the base mesh and tag them as “dest”. That is it.
Graphics artists don’t need to do anything else in the engine.
Our base blueprint class is only doing audio-visual things from events that we provided in C++.
Begin play – load necessary assets. Basically, in our case, each asset is soft object pointer and you should use it as well even when creating prototypes. Hardcoded assets reference will increase editor/game load times and memory usage.
On Break Event – spawn effects and sounds. Here you can find some Niagara parameters which are described later.
On Part Hit Event – spawn hit effects and sounds.
Basically we are always trying to implement logic in C ++ and use the Blueprint to configure parameters and do audio-visual things.
Utility to Quickly Add Collisions
You can create Utility Blueprint for Asset Type Actions to generate collisions for all the parts. Much faster than creating it by yourself.
PARTICLE EFFECTS IN NIAGARA
Niagara is a life changer now. Here you can find how we created this simple effect.
Material
Erosion, color, and alpha come from Niagara.
As you can see most of the effect is coming from the texture. We could use B channel here to add more details but currently, it’s not needed in our case.
Niagara System Params
We use two Niagara Systems: one for break effect (which is using base mesh to spawn particles) and the other one when a part is colliding (without static mesh location).
Niagara Spawn Burst
Niagara Particle Spawn
- We sample static mesh which comes from the destroyable
- Pick random Lifetime, Mass and Size
- Chose color from user color parameter (which is set by the destroyable actor)
- Spawn particles at mesh verticles location
- Add random velocity and rotation rate
Sampling Static Mesh
To be able to sample static mesh in Niagara your mesh needs to have AllowCPU checked.
TIP: In the current (4.24) version of the engine if you reimport your mesh this value will reset to default. And in shipping build when you try to run such Niagara System with a mesh that doesn’t have CPU Access enabled you will have a crash.
That’s why we have added simple code to check if mesh has this value checked.
bool UFunctionLibrary::MeshHaveCPUAccess(UStaticMesh* InMesh)
{
return InMesh->bAllowCPUAccess;
}
Which is used in Blueprints before spawning Niagara.
What is cool you can create an editor widget to find Destroyables, and set their Base Mesh AllowCPUAccess variable.
Here’s the python code which is searching for all destroyables and set CPU access on the base mesh.
Python code to set static mesh allow_cpu_access variable.
import unreal as ue
asset_registry = ue.AssetRegistryHelpers.get_asset_registry()
all_assets = asset_registry.get_assets_by_path('/Game/Blueprints/Actors/Destroyables', recursive=True) #this is the place where we have all destroyable blueprints
for asset in all_assets:
path = asset.object_path
bp_gc = ue.EditorAssetLibrary.load_blueprint_class(path) #get blueprint class
bp_cdo = ue.get_default_object(bp_gc) # get the Class Default Object (CDO) of the generated class
if bp_cdo.mesh.static_mesh != None:
ue.EditorStaticMeshLibrary.set_allow_cpu_access(bp_cdo.mesh.static_mesh, True) # sets allow cpu on static mesh
You can run it directly using py command, or create button to run this code in Utility Widget.
Niagara Particle Update
On update we are doing a couple of things:
- Scaling alpha over life
- Adding some curl noise
- Change rotation rate using a custom expression
(Particles.RotRate * (0.8 – Particles.NormalizedAge)
Sometimes it’s easier to do custom expression than a curve. - Scaling particle size over life
- Update material erode parameter
- Add some vector noise
Niagara Learning References
You should definitely watch some presentations about Niagara. We will be doing more Niagara focused tutorials over time.
Why we are using such an old-school approach
We could use the current destroyable system from UE4 but we want to have better control over performance and visuals. You should ask yourself if you need a big system for your needs. In our case, we don’t. As for Chaos, we are waiting when it will be production-ready.
Kids With Sticks, Indie Game Developers
Keep reading
You may find this article interesting