A Note on Unreal’s Collision System

I am sure there must be a mnemonic-al way to Unreal’s collision configuration, independent from C++ or blueprint. Maybe Stephen’s explanation can demystify parts of the collision. In this blog-post I shall attempt to create an appropriate mnemonic device using abstraction(s).

In Unreal, UPrimitiveComponent is the scene component class with collision specific data and render-able geometry. Any specific object, that you see placed or spawned in the level, having certain shape, probably has a scene component derived from UPrimitiveComponent. The collision settings are configured in the details panel of the appropriate blueprint

The picture above shows collision configurable for capsule component. Similarly there is one set of configurables for Mesh component. Rest of the components have no such collision configurable.

To get an overview of all the collision configurables, one can click on the parent of Capsule Component (BP_FortniteCrystal here) to get the following in Details panel

We note that collision configurables for both the mesh and capsule component are shown which pretty sums up the collision configurable for the blueprint.

Next, we have the following configurables

  1. Simulation Generates Hit Events
  2. Generate Overlap Events
  3. Can Character Step Up On
  4. Collision Presets

Not sure what point 1 does. Point 2 makes sure that the component is involved in generating overlap events. If you want the ability for a character (a pawn with ability to walk) to step on, check point 3. Finally we come to Collision Presets

The picture above shows some default collision presets (except ZombieTouch and Ragdoll presets which are custom presets, which I may explain later). You may select the preset that best fits the component. For instance in C++ code we have the following equivalent

void ASunovatechZombieKillZoCharacter::SetRagdollPhysics()
{
	USkeletalMeshComponent* Mesh3P = GetMesh();
	
        if(Mesh3P)
        {
	    Mesh3P->SetAllBodiesSimulatePhysics(true);
	    Mesh3P->SetSimulatePhysics(true);
	    Mesh3P->WakeAllRigidBodies();
	    Mesh3P->bBlendPhysics = true;
	    Mesh3P->SetCollisionProfileName(TEXT("Ragdoll"));// Go to Project Settings -> Collision -> Preset -> Ragdoll
        }
        ...
}

The SetCollisionProfileName selects the Collision Preset, when using C++, during runtime, when zombie character is killed and becomes a mere ragdoll.

The idea is that each Collision Preset (or profile) makes the component sensitive to raycast or spherecasts or some sort of cast (by a channel), sent from external source, appropriately (corresponding to trace).

Channels decide the nature of sensitivity, in a sense, to the trace being done. For instance, in the picture of collision presets below, there are three categories, one custom (WeaponNoCharacter) and two default (Visibility and Camera). BTW, here is Epic’s blog-post on the subject. Also objects can decide the nature of sensitivity to the trace being done.

Traces offer a method for reaching out to your levels and getting feedback on what is present along a line segment, LineTraceMultiByChannel, or sphere tube and all that (SweepTestByChannel).

Consider the following custom collision presets

for, say, skeletal mesh component. This implies that the component registers blocking hit only for WeaponNoCharacter channel trace and ignores Visibility and Camera channel traces.

Similarly the component is sensitive only to the objects of WorldStatic, WorldDynamic, PhysicsBocy, and Destructibles category.

A custom trace channel is created in Project Settings

and to use in C++, open Config/DefaultEngine.ini and look for the channel name (here WeaponNoCharacter). You may find something like

+DefaultChannelResponses=(Channel=ECC_GameTraceChannel6,DefaultResponse=ECR_Block,bTraceType=True,bStaticObject=False,Name="WeaponNoCharacter")

Finally in C++ you can use the channel by proper name ECC_GameTraceChannel6.

SOLID WORKS

SOLID principles for object oriented programming are demonstrated in action here. Whilst coding my FPS game, I observed couple of violations (SO) the second time I modified a section of code. This is the classic example of how context helps in catching such violations. Consider the following hierarchical class arrangement

class SUNOVATECHZOMBIEKILL_API ASTPickupInventory : public ASTPickup{
public:
    virtual void GiveTo(Pawn* Target) override;
...
}
class SUNOVATECHZOMBIEKILL_API ASTPickupWeapon : public ASTPickupInventory
{
...
}

and finally implementation of GiveTo() function like so

void ASTPickupInventory::GiveTo(Pawn* Target)
{
	if(Target == nullptr)
	{
		return;
	}

	Super::GiveTo(Target);

	ASTInventory* Existing = Target->FindInventoryType(InventoryType, true);

	if (Existing == nullptr)
	{
		ASTInventory* Inv = nullptr;

		// Spawn the inventory type class object and attach to pawn
		UWorld* const World = GetWorld();
		if (World != nullptr)
		{
			...
		}
		else
		{
			UE_LOG(LogSunovatechZombieKill, Log, TEXT("No world found, can't spawn actor of class %s"), *InventoryType->GetName());
		}

		// Add inventory to target and equip
		if(Target->SwitchToRecentPickup())
		{
			Target->AddInventory(Inv, true);
		}
		else // or not
		{
			Target->AddInventory(Inv, false);
		}

                // This looks out of place
		ASTWeapon* MyWeapon = Cast<ASTWeapon>(Inv);

		// Generate circular doubly linked list for switching weapons by rotation
		if(MyWeapon)
		{
			Target->GenerateWeaponCDL();
		}
	}
	else // ok this pickup already exists in inventory
	{
                // This looks out of place as well
		// if weapon, increase the ammo
		ASTWeapon* MyWeapon = Cast<ASTWeapon>(Existing);

		if(MyWeapon)
		{
			MyWeapon->AddAmmo(10); // to do: move this code to STPickupWeapon maybe
		}
	}
}

The “out of place” code may require data member (or member function) of ASTPickupWeapon class, for instance AddAmmo() may require amount belonging to the class.

The SRP (Single Responsibility Principle) says that class should have only one function, here, involving general inventory only. Specializing to ASTWeapon seems responsibility of ASTPickupWeapon. Open/Close principle says that class should be open for extension and closed for modification which also seems like a violation here. This implies violation when weapon specific modification are applied. Hence if SRP is violated, so is Open/Close principle.

A simple solution is to write overridable methods like so

class SUNOVATECHZOMBIEKILL_API ASTPickupInventory : public ASTPickup{
protected:
	/**
	 * @brief Function for dealing with non-existant and post inventory spawn procedures
	 */
	virtual void DealWithNonExistentInventory(ASTInventory* NonExistingInventory, Pawn* Target);

	/**
	 * @brief Function for dealing with existing inventory procedure
	 */
	virtual void DealWithExistingInventory(ASTInventory* ExistingInventory, Pawn* Target);
...
}
by doing this, we have opened passage way for child class ASTPickupWeapon to do ASTWeapon specific computing.
void ASTPickupInventory::GiveTo(Pawn* Target)
{
	if(Target == nullptr)
	{
		return;
	}

	Super::GiveTo(Target);

	ASTInventory* Existing = Target->FindInventoryType(InventoryType, true);

	if (Existing == nullptr)
	{
		ASTInventory* Inv = nullptr;

		// Spawn the inventory type class object and attach to pawn
		UWorld* const World = GetWorld();
		if (World != nullptr)
		{
			...
		}
		else
		{
			UE_LOG(LogSunovatechZombieKill, Log, TEXT("No world found, can't spawn actor of class %s"), *InventoryType->GetName());
		}

		// Add inventory to target and equip
		if(Target->SwitchToRecentPickup())
		{
			Target->AddInventory(Inv, true);
		}
		else // or not
		{
			Target->AddInventory(Inv, false);
		}

                DealWithNonExistentInventory(Inv, Target);
	}
	else // ok this pickup already exists in inventory
	{
               DealWithExistingInventory(Existing, nullptr);
	}
}

Blood Splat

Recently, whilst making an FPS game, I happen to implement a feature involving blood. The idea is, when you butcher zombies, their post-coagulated(?) blood spills on the floor they are standing or rather creeping on. Clearly we need to use something “other than” UParticleSystem for the purpose because we are not looking those particle effects which look like spray in air

The gif shows two kinds of blood effects. One is the blood-sprayed-in-air effect (particle effect) and other is splat on ground (decal).

For particle system we have the following Unreal C++ declaration

UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Effects")
TArray<UParticleSystem*> BloodEffects;

and following code implementation

UParticleSystemComponent* PSC = NewObject<UParticleSystemComponent>(this, UParticleSystemComponent::StaticClass());
...			
PSC->SetWorldLocationAndRotation(LastTakeHitInfo.RelHitLocation + GetActorLocation(), LastTakeHitInfo.RelHitLocation.Rotation());
...

Here first we create new object of type UParticleSystemComponent and set to raycast’s hit location on the pawn. Hence spray effect can be seen.

What about the blot spats on the floor?

For that, we take help of decals.

UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Effects")
TArray<FBloodDecalInfo> BloodDecals;

Basically, we send line trace from hit location (on the pawn) towards vertically downward direction and obtain the hit location on the floor

GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceStart + FVector(0.f, 0.f, -1.f) * 200, ECC_Visibility, FCollisionQueryParams(NAME_BloodDecal, false, this)))

and use Engine’s SpawnDecalAtLocation

UDecalComponent* DecalComp = UGameplayStatics::SpawnDecalAtLocation(GetWorld(), DecalInfo.Material, FVector(1.0f, DecalScale.X, DecalScale.Y), Hit.Location, (-Hit.Normal).Rotation(), 0);

to spawn blood decal at Hit.Location.