Skip to content

Commit

Permalink
GMAS v1.0 (#28)
Browse files Browse the repository at this point in the history
* Initial Attribute Mod

* Refactoring of the Ability Map

* Add capability to reset attribute modifiers during a SetValue call

* Make the modifiers UPROPs

* Add new EndTaskGMAS. Expose AbilityData to Ability BPs

* Dev branch code cleanup (macOS/Linux) (#31)

* Clean-up warnings, get Shipping build config working.

* Further changes, to get everything compiling under macOS and Linux

* Fix compile errors under clang's more stringent mode

* Tag Change Delegates (#32)

* Add delegates for gameplay tag change events, and matching task.

* Gameplay tag binding support and animation blueprint

Adds a GMAS equivalent to GAS's FGameplayTagBlueprintPropertyBindingMap,
along with an animation blueprint supporting this, and an editor module
to actually allow editing the binding map in question.

* Swap iterator direction when removing filtered tags.

* Add SetTargetDataFloat, change HasTag to HasTagExact for Grant/Active checks

* Add the ability to map attributes to properties as well as tags. (#34)

If a property binding has a single tag (rather than multiple), it will
also be considered valid for any attribute changes which match that tag.

* Boolean properties will be set as (value > 0)
* Int properties will be set as the rounded attribute value.
* Float and Double properties will be set as the attribute value.

* Add some safety railings if you attempt to get an attribute when no attributes have been initialized.

* Cleanup to property mapping.

* Changes to static_cast rather than Cast in the element mapper.
* Changes to allow animation blueprint to cope with reality when the
  ability system component is added at runtime *after* the animation
  blueprint is initialized. (Why would you do this.)

* Clean-up active tag access. (#41)

* Minor cleanup/safety checks. (#40)

* Minor safety railings to debugger category.

* We don't need to call our own BindInstancedStruct implementation any more.

* Further improvements on anim instance, to let you set the preview class.

* Additional animation utility functionality (#42)

* Minor safety railings to debugger category.

* We don't need to call our own BindInstancedStruct implementation any more.

* Further improvements on anim instance, to let you set the preview class.

* Further animation cleanup, addition of convenience GMAS_Pawn.

Also cleaned up categories a bit for properties/functions.

* Minor grammar change to fix clang pickiness under Linux.

* Remove pass by reference on blueprint callable tag functions

* Add helper functions to retrieve input action value in BP easily, add bp function library, etc. (#43)

* Add helper functions to retrieve input action value in BP easily, add bp function library, etc.

* update ability helpers to return gmc version of pawn and pc

* Move Tag change events to check in both Aux and Sim ticks

* Add check for ActionTimer before generating Effect ID. Add GetCooldownsForInputTag

---------

Co-authored-by: Packetdancer <seattlesparks@mac.com>
Co-authored-by: Packetdancer <rachel.c.blackman@gmail.com>
Co-authored-by: Packetdancer <packetdancer@gmail.com>
Co-authored-by: Peter Gilbert <33180578+petegilb@users.noreply.github.com>
  • Loading branch information
5 people authored Apr 1, 2024
1 parent f446cd5 commit 689c7c1
Show file tree
Hide file tree
Showing 37 changed files with 1,794 additions and 157 deletions.
7 changes: 6 additions & 1 deletion GMCAbilitySystem.uplugin
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"VersionName": "1.0",
"FriendlyName": "GMCAbilitySystem",
"Description": "",
"Category": "Other",
"Category": "GMC",
"CreatedBy": "Reznok",
"CreatedByURL": "",
"DocsURL": "",
Expand All @@ -19,6 +19,11 @@
"Name": "GMCAbilitySystem",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "GMCAbilitySystemEditor",
"Type": "Editor",
"LoadingPhase": "Default"
}
],
"Plugins": [
Expand Down
22 changes: 19 additions & 3 deletions Source/GMCAbilitySystem/Private/Ability/GMCAbility.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "Ability/GMCAbility.h"
#include "GMCAbilitySystem.h"
#include "GMCPawn.h"
#include "Ability/Tasks/GMCAbilityTaskBase.h"
#include "Components/GMCAbilityComponent.h"

Expand Down Expand Up @@ -39,7 +40,7 @@ void UGMCAbility::TickTasks(float DeltaTime)
}
}

void UGMCAbility::Execute(UGMC_AbilitySystemComponent* InAbilityComponent, int InAbilityID, UInputAction* InputAction)
void UGMCAbility::Execute(UGMC_AbilitySystemComponent* InAbilityComponent, int InAbilityID, const UInputAction* InputAction)
{
this->AbilityInputAction = InputAction;
this->AbilityID = InAbilityID;
Expand Down Expand Up @@ -220,11 +221,10 @@ void UGMCAbility::BeginAbility()

void UGMCAbility::EndAbility()
{
// RunningTasks.Empty();
for (const TPair<int, UGMCAbilityTaskBase* >& Task : RunningTasks)
{
if (Task.Value == nullptr) continue;
Task.Value->EndTask();
Task.Value->EndTaskGMAS();
}

AbilityState = EAbilityState::Ended;
Expand All @@ -236,6 +236,22 @@ AActor* UGMCAbility::GetOwnerActor() const
return OwnerAbilityComponent->GetOwner();
}

AGMC_Pawn* UGMCAbility::GetOwnerPawn() const{
if (AGMC_Pawn* OwningPawn = Cast<AGMC_Pawn>(GetOwnerActor())){
return OwningPawn;
}
return nullptr;
}

AGMC_PlayerController* UGMCAbility::GetOwningPlayerController() const{
if (const AGMC_Pawn* OwningPawn = GetOwnerPawn()){
if(AGMC_PlayerController* OwningPC = Cast<AGMC_PlayerController>(OwningPawn->GetController())){
return OwningPC;
}
}
return nullptr;
}

float UGMCAbility::GetOwnerAttributeValueByTag(FGameplayTag AttributeTag) const
{
return OwnerAbilityComponent->GetAttributeValueByTag(AttributeTag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ void UGMCAbilityTaskBase::Activate()
LastHeartbeatReceivedTime = AbilitySystemComponent->ActionTimer;
}

void UGMCAbilityTaskBase::EndTaskGMAS()
{
EndTask();
}

void UGMCAbilityTaskBase::SetAbilitySystemComponent(UGMC_AbilitySystemComponent* InAbilitySystemComponent)
{
this->AbilitySystemComponent = InAbilitySystemComponent;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "Ability/Tasks/SetTargetDataFloat.h"

#include "GMCAbilityComponent.h"


UGMCAbilityTask_SetTargetDataFloat* UGMCAbilityTask_SetTargetDataFloat::SetTargetDataFloat(UGMCAbility* OwningAbility,
float Float)
{
UGMCAbilityTask_SetTargetDataFloat* Task = NewAbilityTask<UGMCAbilityTask_SetTargetDataFloat>(OwningAbility);
Task->Ability = OwningAbility;
Task->Target = Float;
return Task;
}

void UGMCAbilityTask_SetTargetDataFloat::Activate()
{
Super::Activate();

if (AbilitySystemComponent->GetNetMode() != NM_DedicatedServer)
{
ClientProgressTask();
}
}

void UGMCAbilityTask_SetTargetDataFloat::ProgressTask(FInstancedStruct& TaskData)
{
Super::ProgressTask(TaskData);
const FGMCAbilityTaskTargetDataFloat Data = TaskData.Get<FGMCAbilityTaskTargetDataFloat>();

Completed.Broadcast(Data.Target);
EndTask();
}

void UGMCAbilityTask_SetTargetDataFloat::ClientProgressTask()
{
FGMCAbilityTaskTargetDataFloat TaskData;
TaskData.TaskType = EGMCAbilityTaskDataType::Progress;
TaskData.AbilityID = Ability->GetAbilityID();
TaskData.TaskID = TaskID;
TaskData.Target = Target;
const FInstancedStruct TaskDataInstance = FInstancedStruct::Make(TaskData);

Ability->OwnerAbilityComponent->QueueTaskData(TaskDataInstance);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "..\..\..\Public\Ability\Tasks\SetTargetDataGameplayTag.h"
#include "Ability/Tasks/SetTargetDataGameplayTag.h"
#include "GMCAbilityComponent.h"

UGMCAbilityTask_SetTargetDataGameplayTag* UGMCAbilityTask_SetTargetDataGameplayTag::SetTargetDataGameplayTag(UGMCAbility* OwningAbility, FGameplayTag InTag){
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Fill out your copyright notice in the Description page of Project Settings.


#include "Ability/Tasks/WaitForGameplayTagChange.h"

UGMCAbilityTask_WaitForGameplayTagChange* UGMCAbilityTask_WaitForGameplayTagChange::WaitForGameplayTagChange(
UGMCAbility* OwningAbility, const FGameplayTagContainer& WatchedTags, EGMCWaitForGameplayTagChangeType ChangeType)
{
UGMCAbilityTask_WaitForGameplayTagChange* Task = NewAbilityTask<UGMCAbilityTask_WaitForGameplayTagChange>(OwningAbility);
Task->Tags = WatchedTags;
Task->ChangeType = ChangeType;
return Task;
}

void UGMCAbilityTask_WaitForGameplayTagChange::Activate()
{
Super::Activate();

Ability->OwnerAbilityComponent->AddFilteredTagChangeDelegate(Tags, FGameplayTagFilteredMulticastDelegate::FDelegate::CreateUObject(this, &UGMCAbilityTask_WaitForGameplayTagChange::OnGameplayTagChanged));
}

void UGMCAbilityTask_WaitForGameplayTagChange::OnGameplayTagChanged(const FGameplayTagContainer& AddedTags,
const FGameplayTagContainer& RemovedTags)
{
FGameplayTagContainer MatchedTags;

switch (ChangeType)
{
case Set:
MatchedTags = AddedTags.Filter(Tags);
break;

case Unset:
MatchedTags = AddedTags.Filter(Tags);
break;

case Changed:
MatchedTags = AddedTags.Filter(Tags);
MatchedTags.AppendTags(RemovedTags.Filter(Tags));
break;
}

if (!MatchedTags.IsEmpty())
{
Completed.Broadcast(MatchedTags);
EndTask();
}
}
75 changes: 75 additions & 0 deletions Source/GMCAbilitySystem/Private/Actors/GMAS_Pawn.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include "Actors/GMAS_Pawn.h"

#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Utility/GMASUtilities.h"


// Sets default values
AGMAS_Pawn::AGMAS_Pawn(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;

CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule"));
SetRootComponent(CapsuleComponent);
CapsuleComponent->SetCapsuleRadius(30.f);
CapsuleComponent->SetCapsuleHalfHeight(90.f);
CapsuleComponent->bEditableWhenInherited = true;
CapsuleComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
CapsuleComponent->SetCollisionObjectType(ECC_Pawn);
CapsuleComponent->SetCollisionResponseToAllChannels(ECR_Block);
CapsuleComponent->SetCollisionResponseToChannel(ECC_Camera, ECR_Ignore);
CapsuleComponent->SetCollisionResponseToChannel(ECC_Visibility, ECR_Ignore);

MeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Skeletal Mesh"));
MeshComponent->bEditableWhenInherited = true;
MeshComponent->SetupAttachment(CapsuleComponent);
MeshComponent->SetRelativeLocation(FVector(0.f, 0.f, -90.f));
MeshComponent->SetRelativeRotation(FRotator(0.f, -90.f, 0.f));
MeshComponent->SetCollisionProfileName(TEXT("NoCollision"));

SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("Spring Arm"));
SpringArmComponent->bEditableWhenInherited = true;
SpringArmComponent->SetupAttachment(CapsuleComponent);
SpringArmComponent->TargetArmLength = 400.f;
SpringArmComponent->bUsePawnControlRotation = true;

CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("Follow Camera"));
CameraComponent->bEditableWhenInherited = true;
CameraComponent->SetupAttachment(SpringArmComponent);
CameraComponent->bUsePawnControlRotation = false;

// Add our basic goodies.
AbilitySystemComponent = CreateDefaultSubobject<UGMC_AbilitySystemComponent>(TEXT("Ability Component"));

#if WITH_EDITOR
UGMASUtilities::SetPropertyFlagsSafe(StaticClass(), TEXT("CapsuleComponent"), CPF_DisableEditOnInstance);
UGMASUtilities::SetPropertyFlagsSafe(StaticClass(), TEXT("MeshComponent"), CPF_DisableEditOnInstance);
UGMASUtilities::SetPropertyFlagsSafe(StaticClass(), TEXT("SpringArmComponent"), CPF_DisableEditOnInstance);
UGMASUtilities::SetPropertyFlagsSafe(StaticClass(), TEXT("CameraComponent"), CPF_DisableEditOnInstance);
UGMASUtilities::SetPropertyFlagsSafe(StaticClass(), TEXT("AbilitySystemComponent"), CPF_DisableEditOnInstance);
#endif
}

// Called to bind functionality to input
void AGMAS_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
if (const UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent); InputMappingContext && EnhancedInput)
{
if (const ULocalPlayer* Player = Cast<ULocalPlayer>(GetController()))
{
if (UEnhancedInputLocalPlayerSubsystem* InputSystem = Player->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
// Add our default system as the lowest mapping context.
InputSystem->AddMappingContext(InputMappingContext, 0);
}
}
}

Super::SetupPlayerInputComponent(PlayerInputComponent);
}

107 changes: 107 additions & 0 deletions Source/GMCAbilitySystem/Private/Animation/GMCAbilityAnimInstance.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include "Animation/GMCAbilityAnimInstance.h"
#include "GMCAbilityComponent.h"
#include "Utility/GMASUtilities.h"

UGMCAbilityAnimInstance::UGMCAbilityAnimInstance(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
#if WITH_EDITOR
// Hide instance-only variables which we do not need cluttering everything up.
UGMASUtilities::ClearPropertyFlagsSafe(StaticClass(), TEXT("GMCPawn"), CPF_SimpleDisplay | CPF_Edit);
UGMASUtilities::ClearPropertyFlagsSafe(StaticClass(), TEXT("AbilitySystemComponent"), CPF_SimpleDisplay | CPF_Edit);
#endif
}

void UGMCAbilityAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();

bool bShouldInitializeProperties = true;

// A subclass may have already set these before calling us, so check if we need to do the work.
if (!AbilitySystemComponent || !GMCPawn)
{
AActor* OwnerActor = GetOwningActor();
if (OwnerActor)
{
GMCPawn = Cast<AGMC_Pawn>(OwnerActor);
AbilitySystemComponent = Cast<UGMC_AbilitySystemComponent>(OwnerActor->GetComponentByClass(UGMC_AbilitySystemComponent::StaticClass()));
}

#if WITH_EDITOR
if (!GetWorld()->IsGameWorld() && (!IsValid(AbilitySystemComponent) || !IsValid(GMCPawn)))
{
// Create a default for in-editor preview.
if (!IsValid(GMCPawn))
{
GMCPawn = EditorPreviewClass->GetDefaultObject<AGMC_Pawn>();

// Since we might have overridden the editor preview class with something that already has an ability component,
// try getting the ability component again.
AbilitySystemComponent = Cast<UGMC_AbilitySystemComponent>(GMCPawn->GetComponentByClass(UGMC_AbilitySystemComponent::StaticClass()));
}

if (!IsValid(AbilitySystemComponent))
{
// There's not going to be any attributes or tags really to work with when using the
// default parent class.
bShouldInitializeProperties = false;

AbilitySystemComponent = NewObject<UGMC_AbilitySystemComponent>();
}
}
#endif
}

if (AbilitySystemComponent)
{
#if WITH_EDITOR
if (!GetWorld()->IsGameWorld())
{
// We're in the editor preview; manually instantiate our attributes for purposes of property mapping
// the default values. This is a convenience for debugging in-editor, so that the editor preview's
// mapped properties should match the class-level defaults for the chosen editor preview class.
AbilitySystemComponent->InstantiateAttributes();
AbilitySystemComponent->SetStartingTags();
}
#endif

if (bShouldInitializeProperties)
{
TagPropertyMap.Initialize(this, AbilitySystemComponent);
}
else
{
UE_LOG(LogGMCAbilitySystem, Log, TEXT("%s: skipping property map initialization since we're an in-editor preview; we're using the default ability system component, and won't have valid data."),
*GetClass()->GetName());
}
}
else
{
// Don't bother with this log in editor previews.
if (GetWorld()->IsGameWorld())
{
UE_LOG(LogGMCAbilitySystem, Warning, TEXT("%s: unable to find a GMC Ability System Component on %s after initializing animation blueprint. Will make a last-ditch effort in BeginPlay."),
*GetClass()->GetName(), *GetOwningActor()->GetName())
}
}
}

void UGMCAbilityAnimInstance::NativeBeginPlay()
{
Super::NativeBeginPlay();

// Components added in blueprint won't be available during InitializeAnimation but will by the time we hit BeginPlay;
// make a second attempt to set up our property bindings in that scenario.
if (GMCPawn && !AbilitySystemComponent)
{
AbilitySystemComponent = Cast<UGMC_AbilitySystemComponent>(GMCPawn->GetComponentByClass(UGMC_AbilitySystemComponent::StaticClass()));
if (!AbilitySystemComponent)
{
UE_LOG(LogGMCAbilitySystem, Error, TEXT("%s: last-ditch effort to find a GMC Ability System Component on %s failed!"), *GetClass()->GetPathName(), *GMCPawn->GetName());
return;
}

TagPropertyMap.Initialize(this, AbilitySystemComponent);
}
}

Loading

0 comments on commit 689c7c1

Please sign in to comment.