Feedback Needed: Comprehensive Hit Bone System Utilizing GAS Attributes and Gameplay Effects

Information

I want to create a system where, whenever a bullet hits a target, it calls a Hit function on the StatComponent of the affected Character. This function will apply a GameplayEffect associated with the weapon to modify the health attribute of the specific bone that was hit, provided that the hit is valid.

I need this system to work for multiple enemy types, each with different bone structures. For example, a smaller enemy might have three attributes (main, leg1, and leg2), while a larger enemy could have eight (main, leg1, leg2, torso, head, hand1, hand2, etc.).

I’m explaining this to ensure that my approach aligns with the built-in Gameplay Ability System (GAS) in Unreal Engine. This is the most advanced system I’ve attempted, and I want to make sure I implement it correctly this time.

Thank you for taking the time to read this long message! :wink:


Attribute Set

  • Setup the maximum number of Attributes but no need to use all of them (customizable system)
UCLASS()
class UEnemyAttributeSet : public UAttributeSet
{
    GENERATED_BODY()

public:
    UPROPERTY(BlueprintReadOnly, Category = "BoneHealth", ReplicatedUsing = OnRep_BoneHealth0)
    FGameplayAttributeData BoneHealth0;
    ATTRIBUTE_ACCESSORS(UEnemyAttributeSet, BoneHealth0)

    UPROPERTY(BlueprintReadOnly, Category = "BoneHealth", ReplicatedUsing = OnRep_BoneHealth1)
    FGameplayAttributeData BoneHealth1;
    ATTRIBUTE_ACCESSORS(UEnemyAttributeSet, BoneHealth1)

    // Define up to the maximum number of bones
    UPROPERTY(BlueprintReadOnly, Category = "BoneHealth", ReplicatedUsing = OnRep_BoneHealth2)
    FGameplayAttributeData BoneHealth2;
    ATTRIBUTE_ACCESSORS(UEnemyAttributeSet, BoneHealth2)

    // ...more attributes as needed... 

    // Replication functions for each attribute
    UFUNCTION()
    void OnRep_BoneHealth0(const FGameplayAttributeData& OldBoneHealth0);

    UFUNCTION()
    void OnRep_BoneHealth1(const FGameplayAttributeData& OldBoneHealth1);

    // More OnRep functions for other bone health attributes...
};

Stat Component

UCLASS()
class UGlobalStatComponent : public UActorComponent
{
    GENERATED_BODY()

public:
    UGlobalStatComponent();

    // Player
    FGameplayAttribute HealthAttribute;

    // Map bone names to GAS attributes for enemies
    TMap<FName, FGameplayAttribute> BoneHealthSlotMap;

    void InitializeBoneHealthMap(UEnemyAttributeSet* EnemyAttributes, const TArray<FName>& BoneNames);

    void ApplyDamageToBone(FName BoneName, float DamageAmount);
};

Initializing on Beginplay all attributes

// Initialize the map for enemy bone health attributes, this function is customizable and works for different amount of enemy bones
void UGlobalStatComponent::InitializeBoneHealthMap(UEnemyAttributeSet* EnemyAttributes, const TArray<FName>& BoneNames)
{
    // Example: Map bones to health slots (attributes) dynamically
    for (int32 i = 0; i < BoneNames.Num(); ++i)
    {
        FName BoneName = BoneNames[i];

        switch (i)
        {
        case 0:
            BoneHealthSlotMap.Add(BoneName, UEnemyAttributeSet::GetBoneHealth0Attribute());
            break;
        case 1:
            BoneHealthSlotMap.Add(BoneName, UEnemyAttributeSet::GetBoneHealth1Attribute());
            break;
        case 2:
            BoneHealthSlotMap.Add(BoneName, UEnemyAttributeSet::GetBoneHealth2Attribute());
            break;
        // Map up to the number of predefined attributes...
        default:
            break;
        }
    }
}

Example of customizable initialization for different enemies:

// Should be matching with the Skeletal Mesh
BoneNames = { "Head", "LeftArm", "RightArm" }; 
GlobalStatComponent->InitializeBoneHealthMap(EnemyAttributes, BoneNames);

Apply Damage with the GE

void UGlobalStatComponent::ApplyDamageToBone(FName BoneName, float DamageAmount)
{
    if (BoneHealthSlotMap.Contains(BoneName))
    {
        FGameplayAttribute BoneHealthAttribute = BoneHealthSlotMap[BoneName];

        // Get the Ability System Component (ASC) of the owner
        UAbilitySystemComponent* ASC = GetOwner()->FindComponentByClass<UAbilitySystemComponent>();
        if (ASC)
        {
           // ASC->ApplyGameplayEffect(...); from a given DataAsset
        }
    }
}

This system would enable me to utilize GAS to apply effects easily and manipulate bones depending on their health! I need your opinions and insights!

// about your approach
First, the scheme as is on the first glance: it should work, and even work properly with multiplayer.

But it isn’t very clear to me how you going to ApplyDamageToBone to proper attribute via GE, as iirc GE can only affect predefined attributes. Is the from a given DataAsset comment means that you’ll have GE_DamageBone1, GE_DamageBone2, etc? If so, It will work, though looks weird.

// the suggestion\idea:
… I was going to suggest to use the several of the same AttributeSets despite the doc says otherwise, but after a second thought i understand that it indeed will be a problem while trying to apply GE to particular “bone”.

Nevertheless, the third thought is: as an option you may use individual ASC per bone, which should works fine in all means, including multiplayer.

Considering you already going to store mapping bone->n'th health attribute - instead you may make mapping bone->ASC.

The pros of this are:

  • it’ll looks more natural;
  • it’ll simplify the damage logic: you’ll only need the only GE_Damage GE, just apply it to proper ASC.
  • you’ll still have full power of attribute sets on each bone, not limited to just health and without any needs to copypaste code to add more attributes\bones.

The cons are:

  • You’ll have more components, so a bit more of abstractions to keep in mind, but it still shouldn’t affect nor perfomance nor readability.

So probably there are no real cons.

And if you still need the ASC on character with AttributeSet specific to character - you still can add all this above bones’s ASCs.

Anyway, i didn’t try it, so it’s just an idea. If anyone can add something on this - i’d like to hear about it.

minor upd:
definitely the cons of idea:

  • it will be harder to debug several AttributeSets\ASCs than a single one like in your case;
  • probably overengineering.
1 Like

I forgot to update the code, but I will be passing a GE to the ApplyDamageToBone function to then be able to use it and apply it.

Now that I think of it, the GE_Fire should decrease the health attribute of all the bones a bit, but I would still need to keep this bone structure for when a single bullet hit a bone, then the GE_Explosion will be applied to only that bone… (Depending on what type of weapon it is) This is easily done in the GE though!

Thanks for your ideas, but I think I’ll go with the first one because it seems like the easiest one to go with…