Ragdoll Physics on Death

Hey Guys,

Fairly new to Unreal C++, though ive learnt a lot recently
im trying to implement ragdoll physics on death but im a little confused as to how i should go about doing that.

Ive got my code here:


    if (Health <= 0.0f && !IsDead)
    {
        // Dead
        IsDead = true;

        GetMovementComponent()->StopMovementImmediately();
        GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
        DetachFromControllerPendingDestroy();

        SetLifeSpan(10.0f);
    }

not exactly sure how to implement “ragdolling”.
I tried SetSimulatePhysics(true) but that didnt really seem to work.

Any ideas?
Thanks!

This is the code im using to change the character into a ragdoll, which can also be found in some tutorials/examples



bool ASwatCharacter::Die(float Damage, FDamageEvent const& DamageEvent, class AController* EventInstigator, class AActor* DamageCauser)
{
    if (!bIsDying)
    {
        bReplicateMovement = false;
        bTearOff = true;

        DetachFromControllerPendingDestroy();

        /* Disable all collision on capsule */
        UCapsuleComponent* CapsuleComp = GetCapsuleComponent();
        CapsuleComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
        CapsuleComp->SetCollisionResponseToAllChannels(ECR_Ignore);

        GetMesh()->SetCollisionProfileName(TEXT("Ragdoll"));
        SetActorEnableCollision(true);

        if (!bIsRagdoll)
        {
            // Ragdoll
            GetMesh()->SetAllBodiesSimulatePhysics(true);
            GetMesh()->SetSimulatePhysics(true);
            GetMesh()->WakeAllRigidBodies();
            GetMesh()->bBlendPhysics = true;

            UCharacterMovementComponent* CharacterComp = Cast<UCharacterMovementComponent>(GetMovementComponent());
            if (CharacterComp)
            {
                CharacterComp->StopMovementImmediately();
                CharacterComp->DisableMovement();
                CharacterComp->SetComponentTickEnabled(false);
            }

            SetLifeSpan(10.0f);
            bIsRagdoll = true;
        }
    }

    /* Apply physics impulse on the bone of the enemy skeleton mesh we hit (ray-trace damage only) */
    if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
    {
        FPointDamageEvent PointDmg = *((FPointDamageEvent*)(&DamageEvent));
        {
            GetMesh()->AddImpulseAtLocation(PointDmg.ShotDirection * 5000, PointDmg.HitInfo.ImpactPoint, PointDmg.HitInfo.BoneName);
        }
    }
    if (DamageEvent.IsOfType(FRadialDamageEvent::ClassID))
    {
        FRadialDamageEvent RadialDmg = *((FRadialDamageEvent const*)(&DamageEvent));
        {
            GetMesh()->AddRadialImpulse(RadialDmg.Origin, RadialDmg.Params.GetMaxRadius(), 100000, ERadialImpulseFalloff::RIF_Linear);
        }
    }

    return true;
}


1 Like

Thanks for the reply!
Would if be possible for you to link those tutorials/examples so i can get a better understanding?

1 Like

A part of the code comes from the Epic Survival tutorial series by Tom Looman.

U can also browse the code of this tutorial on GitHub: EpicSurvivalGameSeries/SBaseCharacter.cpp at master · tomlooman/EpicSurvivalGameSeries · GitHub

1 Like

Ah yeah ive seen that, i followed the Udemy Course but im now trying to expand on it with my own features.
In the course we created a Delegate and used that to create a custom even when taking damage, so im trying to implement the above method with that delegate but its proving to be quite difficult

Depending on how your delegate works, whenever u take damage and the character run out of healt, u can call the Die method.
For a more complex ragdoll example, u can checkout github unreal tournament source code which is sometime complex but a very good learning source (see UTCharacter::StartRagdoll())

Can u show example of your delegate and how u use it?
I tend to write pretty much my entire game in C++ but use native/blueprint events to be able to implement and/or override events in blueprint, which might be usefull for prototyping/quick testing and allowing modding later on. There are only few places where im using delegates, but its been almost 6 month now i worked on the game, so it’s all a bit rusty :slight_smile:

Yeah of course!



//This is my HealthComponent.h file

DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FOnHealthChangedSignature, UTPSHealthComponent*, OwningHealthComp, float, Health,
float, HealthDelta, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser);

UCLASS( ClassGroup=(TPS), meta=(BlueprintSpawnableComponent) )
class TP_SHOOTER_API UTPSHealthComponent : public UActorComponent
{
    GENERATED_BODY()

public:    
    // Sets default values for this component's properties
    UTPSHealthComponent();

protected:
    // Called when the game starts
    virtual void BeginPlay() override;

    UPROPERTY(BlueprintReadOnly, Category = "HealthComponent")
    float Health;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HealthComponent")
    float DefaultHealth;

    UFUNCTION()
    void HandleTakeAnyDamage(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);

public:

    UPROPERTY(BlueprintAssignable, Category = "Events")
    FOnHealthChangedSignature OnHealthChanged;

};


// The HealthComponent.cpp method

void UTPSHealthComponent::HandleTakeAnyDamage(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
{
    if (Damage <= 0.0f)    {return;}

    Health = FMath::Clamp(Health - Damage, 0.0f, DefaultHealth);
    OnHealthChanged.Broadcast(this, Health, Damage, DamageType, InstigatedBy, DamageCauser);
}





// This is the method in my Character.cpp file that handles damage, enemies use the same health component.

void ATPSCharacter::OnHealthChanged(UTPSHealthComponent* OwningHealthComp, float Health, float HealthDelta, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
{

    if (Health <= 0.0f && !IsDead)
    {
        // Dead
        IsDead = true;
        GetMovementComponent()->StopMovementImmediately();
        GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);

        DetachFromControllerPendingDestroy();

        SetLifeSpan(10.0f);
    }    
}


Ah, u are using delegates because u are using a health component, that explains :slight_smile:

So there are 2 things u can do.

  1. in your ATPSCharacter::OnHealthChanged, implement or call a method for the ragdoll.
  2. In your health component, add an extra delegate to broadcast the death.

I would go for the second, so u can also hookup the event in other places without having to check if your pawn died every time in the event handler.

I tried the second method, adding an extra delegate, but cant seem to get it to work.
Ive also tries the first method but for that i would still need access to FDamageEvent const& DamageEvent, wouldnt i?
I just need to be able to access FDamageEvent const& DamageEvent so i can use the ImpulseAtLocation and use the bone name.

U can change your existing delegate by adding the DamageEvent parameter.
But creating a new delegate should also work. Can u post the code u tried and does not work?

it was something along the lines of


DECLARE_DYNAMIC_MULTICAST_DELEGATE_SevenParams(FOnHealthChangedSignature, UTPSHealthComponent*, OwningHealthComp, float, Health, float, HealthDelta, FDamageEvent const&, DamageEvent, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser); 

but that just messsed everything else up.

I also have this:


   
// in my TPSCharacter.cpp
HealthComponent->OnHealthChanged.AddDynamic(this, &ATPSCharacter::OnHealthChanged);

this would fail compilation too when i had the SevenParams in my delegate

here is my HealthComponent cpp file: #include "TPSHealthComponent.h"#include "GameFramework/Actor.h"// Sets - Pastebin.com

and this is my Character cpp file: Character.cpp - Pastebin.com

Both files seem to link to character.cpp

Oops sorry! links are now working.

If u changed the delegate, did u also change the function bound to the delegate to add the extra parameter?

I think the main problem is the OnTakeAnyDamage delegate which comes from AActor, but does not contain the DamageEvent parameter, so even when u extend your OnHealthChanged delegate, u have no access to the DamageEvent at all.

What u can do is remove the OnTakeAnyDamage delegate from the HealthComponent BeginPlay, override the TakeDamage on your character and any other actor that have a health component and inside this method, call your TakeDamage method on the health component.



float ATPSCharacter::TakeDamage(float Damage, FDamageEvent const& DamageEvent, class AController* EventInstigator, class AActor* DamageCauser)
{
    const auto DamageTypeCDO = DamageEvent.DamageTypeClass ? DamageEvent.DamageTypeClass->GetDefaultObject<UDamageType>() : GetDefault<UDamageType>();
    HealthComponent->HandleTakeAnyDamage(this, Damage, DamageEvent, DamageTypeCDO, EventInstigator, DamageCauser);
    return Damage;
}

void ATPSCharacter::OnHealthChanged(UTPSHealthComponent* OwningHealthComp, float Health, float HealthDelta, **FDamageEvent const& DamageEvent**, const class UDamageType*DamageType, class AController* InstigatedBy, AActor* DamageCauser)
{
}

void UTPSHealthComponent::HandleTakeAnyDamage(AActor* DamagedActor, float Damage, **FDamageEvent const& DamageEvent**, const class UDamageType* DamageType, class AController*InstigatedBy, AActor* DamageCauser)
{
    OnHealthChanged.Broadcast(this, Health, Damage, **DamageEvent**, DamageType, InstigatedBy, DamageCauser);
}


I guess for that to work first i would need to modify the delegate to take SevenParams, so something like this?


DECLARE_DYNAMIC_MULTICAST_DELEGATE_SevenParams(FOnHealthChangedSignature, UTPSHealthComponent*, OwningHealthComp, float, Health, float, HealthDelta, struct FDamageEvent const&, DamageEvent, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser);

yes, that seems correct, just make sure if u change the delegate that all functions bound to the delegate also get the extra parameter and your health component include the parameter when calling the callback