Built using C++, Blueprints and Unreal Engine 5
Click the image to watch the demo.
A third person multiplayer project built on Unreal's Gameplay Ability System. The GAS foundation (ability system component, attribute set, granting, base ability class) is in C++. Individual abilities are Blueprint graphs on top of it, with C++ helpers where the maths is easier to write in code.
Made with game FX artist Kavya Swaminathan.
| Platform | Engine | Duration | Team Size | Role |
|---|---|---|---|---|
| PC | Unreal Engine 5 / C++ | Present | Duo | AI / Gameplay Programmer |
The code is in Source/CombatRPG/GameplayAbilitySystem.
The component lives on the character and replicates in Mixed mode by default.
AbilitySystemComponent = CreateDefaultSubobject<UCombatRPGAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(AscReplicationMode);Actor info is initialised twice: in PossessedBy on the server and in OnRep_PlayerState on the client. Abilities are only ever granted or removed on the server, and GAS replicates the specs to the owner.
void ACombatRPGCharacterBase::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
if (AbilitySystemComponent)
{
AbilitySystemComponent->InitAbilityActorInfo(this, this);
GrantAbilities(StartingAbilities);
}
}
TArray<FGameplayAbilitySpecHandle> ACombatRPGCharacterBase::GrantAbilities(
TArray<TSubclassOf<UGameplayAbility>> AbilitiesToGrant)
{
if (!AbilitySystemComponent || !HasAuthority())
{
return TArray<FGameplayAbilitySpecHandle>();
}
TArray<FGameplayAbilitySpecHandle> AbilityHandles;
for (TSubclassOf<UGameplayAbility> Ability : AbilitiesToGrant)
{
FGameplayAbilitySpecHandle SpecHandle = AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(
Ability, 1, -1, this
));
AbilityHandles.Add(SpecHandle);
}
SendAbilitiesChangedEvent();
return AbilityHandles;
}SendAbilitiesChangedEvent fires an Event.Abilities.Changed gameplay event, which the UI listens for so it can update.
UBasicAttributeSet holds Health, MaxHealth, Stamina and MaxStamina. Each one replicates with a RepNotify, and values are clamped before they change so an effect can't push health below zero or above the max.
void UBasicAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
if (Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
} else if (Attribute == GetStaminaAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxStamina());
}
}Every ability inherits from UCombatRPGGameplayAbility, which adds a GameplayAbility.Active tag while it runs. Other abilities use that tag to block themselves.
ActivationOwnedTags.AddTag(FGameplayTag::RequestGameplayTag(FName("GameplayAbility.Active")));A four-direction dash. The ability graph is Blueprint. UDashAbility in C++ works out which way the dash is going relative to the character and picks the matching montage.
- Activation event arrives with the input direction
- Direction is classified as forward, back, left or right
- Commit checks stamina cost and cooldown
- Root motion force moves the character
- The montage for that direction plays
- A Gameplay Cue plays VFX and audio
- The ability ends when the montage does
Direction detection
EDashDirection UDashAbility::GetDashDirectionFromWorld(const FVector& WorldDashDirection, float ForwardThreshold) const
{
const FGameplayAbilityActorInfo* Info = CurrentActorInfo;
if (!Info || !Info->AvatarActor.IsValid())
{
return EDashDirection::Forward;
}
FVector Dir = WorldDashDirection;
Dir.Z = 0.0f;
Dir = Dir.GetSafeNormal();
if (Dir.IsNearlyZero())
{
return EDashDirection::Forward;
}
const AActor* Avatar = Info->AvatarActor.Get();
const FVector Forward = Avatar->GetActorForwardVector().GetSafeNormal2D();
const FVector Right = Avatar->GetActorRightVector().GetSafeNormal2D();
const float F = FVector::DotProduct(Dir, Forward);
const float R = FVector::DotProduct(Dir, Right);
if (F >= ForwardThreshold && F >= FMath::Abs(R))
{
return EDashDirection::Forward;
}
if (F <= -ForwardThreshold && FMath::Abs(F) >= FMath::Abs(R))
{
return EDashDirection::Back;
}
return (R >= 0.0f) ? EDashDirection::Right : EDashDirection::Left;
}
Stamina cost and cooldown are Gameplay Effects. VFX and sound go through a Gameplay Cue, so they stay out of the gameplay logic.
A radial melee attack, built in Blueprint.
- Activation event arrives
- Commit checks cost and cooldown
- The attack montage plays
- A sphere overlap on the server finds everything in range
- Targets are filtered with Gameplay Tags, so no self hits or friendly fire
- A damage Gameplay Effect is applied to each valid target
- A Gameplay Cue plays impact VFX and audio
- The ability ends when the montage does