Chains that move when objects get displaced can really improve the believability of a setting. But most tutorials on the web will only teach you how to create static chains using Unreal Engine's spline system. In this guide, we will see how to make dynamic chains instead.
Prerequisites
I am using Unreal Engine 5.3. This will probably also work with different versions of the engine, but I did not test it.
You will also need a chain link 3D asset. The easiest solution will be to have a model with 2 links, so that you can just copy it all along the chain. Otherwise, if you have an asset with only one link, you will need to rotate it by 90° at every step.
In my case, I am using a link asset from Polyart Studio's Dreamscape: Stylized Environment Tower:
Now let's get into it!
Here is the plan: instead of placing links along a spline, we will use Unreal Engine's cable component to compute physics for us. Then we can hide the cable's mesh, and draw links in place of the cable. This way, we will have a fully functioning dynamic chain.
The code
First we need to add the CableComponent
dependency to the project, in your [Your Project].build.cs
file. Like this:
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine" });
PublicDependencyModuleNames.AddRange(new string[] { "CableComponent" });
Now we can start coding! Here is the class I use as base for my chain actor:
The actor's code is small. What it does is simple in principle, but some API specificities may make parts of the code hard to understand. In details:
AChainActor::AChainActor()
{
CableComponent = CreateDefaultSubobject<UCableComponent>(TEXT("CableComponent"));
InstancedStaticMeshComponent = CreateDefaultSubobject<UInstancedStaticMeshComponent>(TEXT("InstancedStaticMeshComponent"));
RootComponent = CableComponent;
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bTickEvenWhenPaused = true;
}
The constructor simply adds the necessary CableComponent
, along with an InstancedStaticMeshComponent
. We will use the latter to display the same mesh multiple times, and we will use it to display the chain links.
// AChainActor::OnConstruction is the same
void AChainActor::BeginPlay()
{
Super::BeginPlay();
InstancedStaticMeshComponent->ClearInstances();
InstanceIndices.clear();
CreateChainMeshInstances();
}
void AChainActor::CreateChainMeshInstances()
{
for (size_t i = 0; i < CableComponent->NumSegments; i++)
{
FTransform InstanceTransform{};
int InstanceIndex = InstancedStaticMeshComponent->AddInstance(InstanceTransform, true);
InstanceIndices.push_back(InstanceIndex);
}
}
When the game starts, the component creates the different static mesh instances it will use to draw the chain.
We cannot access them using zero-based numbering. Instead, the component will give us the ID of the instance when we create it, and we will reference meshes later using those IDs instead. So we store them in the InstanceIndices
vector.
void AChainActor::TickActor(float DeltaTime, ELevelTick TickType, FActorTickFunction& ThisTickFunction)
{
Super::TickActor(DeltaTime, TickType, ThisTickFunction);
UpdateChainMeshInstances();
}
void AChainActor::UpdateChainMeshInstances()
{
static TArray<FVector> CableParticleLocations{};
CableComponent->GetCableParticleLocations(CableParticleLocations);
for (size_t i = 0; i < CableComponent->NumSegments; i++)
{
FTransform InstanceTransform = GetTransformForChainAt(i, CableParticleLocations);
InstancedStaticMeshComponent->UpdateInstanceTransform(InstanceIndices[i], InstanceTransform, true, true);
}
}
FTransform AChainActor::GetTransformForChainAt(const int NodeIndex, TArray<FVector> CableParticleLocations) const
{
const FVector Position = (CableParticleLocations[NodeIndex] + CableParticleLocations[NodeIndex+1]) / 2;
const FRotator Rotation = (CableParticleLocations[NodeIndex+1] - CableParticleLocations[NodeIndex]).Rotation();
const FVector Scale{ChainScale};
return FTransform{Rotation, Position, Scale};
}
Here, on every tick, we will update the static mesh instances' transform based on the segments of the cable component. We access the static mesh instances using the indices we saved during game start.
The Blueprint Setup
Now we can switch to the editor. You can create an actor blueprint using the ChainActor
as a base class. From there, you will need to set up the blueprint:
- Add the mesh to the instanced static mesh component's
Static Mesh/Static Mesh
field. - Make the cable invisible, by setting its
Rendering/Visible
field to false. We only need it for physics, but you can set it visible if you need to debug the actor. - [This step can also be done in the actual level] Adjust the length of the chain/size of the links using those 3 different field:
- The actor's
Chain Actor/Chain Scale
field, - The cable component's
Cable/Cable Length
field, - And the cable component's
Cable/Num Segments
.
- The actor's
To make the cable more “chain like”, you can also change:
- The
Cable/Solver Iterations
to make the cable feel less elastic. I personally have it set to 16. - And the
Cable Forces/Cable Force
's negative Z value, or theCable Forces/Cable Gravitiy Scale
, to make the chain heavier. I had good results with Y = -9000,0.
Cable/Advanced/Enable Collision
field. Here is what you should end up with:
As you can see, the fact that the 3D mesh only features 2 links is not really visible.
And this actor should react realistically to its attach points moving. To learn more on how to attach cables, see the cable component documentation. You can also attach them to sockets.
Limitations
This chain actor, while simple, is far from perfect. Here are some aspects of it that could be improved:
Inconvenience
The actor setup involves a lot of steps. They could probably be automated to some extent. But for my use (creating a single actor), automating all this was not worth the effort.
Plus, the actor moves every frame, even when the game is not started. This plus the use of the static mesh component causes the levels the chain is in to always appear as “edited”. But I did not find any option to exclude the meshes from the level save process.
The performance cost may be high
I don't know if a static mesh component is made for drawing meshes that move every frame. I don't know if the “static” in static mesh component means “static” as “does not move”, or “does not change”.
Weird attachment points
Because of the way links are rendered, if the cable moves too much around its attach point, the chain can clip through the ring of the attachment point. This could probably be solved by offsetting the meshes so that the cable particles are located inside the chain links.
Thank you for reading 🤍
If you ever end up using this actor class in your game, don't hesitate to contact me by email at website@ganyuss.com!