# AriesGame
**Repository Path**: naughtycat/AriesGame
## Basic Information
- **Project Name**: AriesGame
- **Description**: 自定义 RPG 游戏框架
- **Primary Language**: 其他
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-11-11
- **Last Updated**: 2025-11-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# AuraGame
## 一、准备阶段
#### Description
{**NaughtyCat 的游戏项目 是一个集成了GAS、AI、EQS的游戏框架**}
#### 开发环境
虚幻引擎5.5版本
#### 项目创建 2024年12月21日
1. 创建C++项目
2. 修改版权所有 ProjectSetting -》 Copyright Notice
3. 关闭LiveCoding
4. 添加第三人称游戏模板。并重构结构。


#### 软硬引用的对比
1. 右键查看资产的Size Map...



2. 引用关系图

3. 解决
- 使用更为廉价的父类、C++类、接口等。并不是持有蓝图才厚重,而是硬引用的对象厚重。
- 避免直接传递BP引用。
- 只把逻辑部分留在父类。
- 非必要将一切资产加载。而是动态生成。
4. 软引用
- C++里的硬引用。

- 软引用


缺点是需要自己加载资源和掌握正确的时机。
5. 总结
- 应该在关键资源使用硬引用,其他资源使用软引用。

#### 角色
基本架构:

#### 行为
不在使用传统按键 而是和 GAS结合





## 二、基础框架
### 2.1、全局Log工具
创建一个全局的Log,用于方便输出屏幕和log信息的内容。
*AriesDebugHelper.h*
```cpp
#pragma once
namespace Debug
{
static void Print(const FString& Msg, const FColor& Color = FColor::MakeRandomColor(), int32 Inkey = -1)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(Inkey, 7.0f, Color, Msg);
UE_LOG(LogTemp, Warning, TEXT("%s"), *Msg);
}
}
static void Print(const FString& FloatTitle, float FloatValueToPrint, int32 Inkey = -1, const FColor& Color = FColor::MakeRandomColor())
{
if (GEngine)
{
const FString FinalMsg = FloatTitle + TEXT(": ") + FString::SanitizeFloat(FloatValueToPrint);
GEngine->AddOnScreenDebugMessage(Inkey, 7.0f, Color, FinalMsg);
UE_LOG(LogTemp, Warning, TEXT("%s"), *FinalMsg);
}
}
}
```
### 2.2、Gameplay系统组件
#### GameModeBase
创建名为*AriesBaseGameMode*的GameMode。
```cpp
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "AriesBaseGameMode.generated.h"
UCLASS()
class ARIESGAME_API AAriesBaseGameMode : public AGameModeBase
{
GENERATED_BODY()
};
```
#### PlayerController
暂时先为玩家提供PlayerController,敌人之后使用AIPlayerController。创建名为*AriesHeroPlayerController*的PlayerController。
```cpp
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "AriesHeroPlayerController.generated.h"
UCLASS()
class ARIESGAME_API AAriesHeroPlayerController : public APlayerController
{
GENERATED_BODY()
};
```
### 2.3、角色
创建名为*AriesBaseCharacter*的Character。
```cpp
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AriesBaseCharacter.generated.h"
class UAriesAttributeSet;
class UAriesAbilitySystemComponent;
class UDataAsset_StartUpDataBase;
UCLASS()
class ARIESGAME_API AAriesBaseCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AAriesBaseCharacter();
protected:
//~ Begin APawn Interface
virtual void PossessedBy(AController* NewController) override;
//~ End APawn Interface
}
```
解释:
- 重写`PossessedBy()`的原因是GAS会通过其在服务器之间传递控制。
实现:
```cpp
AAriesBaseCharacter::AAriesBaseCharacter()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = false;
GetMesh()->bReceivesDecals = false;
}
void AAriesBaseCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
}
```
继承类 Hero的Character,添加相机和相关设置。
```cpp
#pragma once
#include "CoreMinimal.h"
#include "Character/AriesBaseCharacter.h"
#include "Camera/CameraComponent.h"
#include "AriesHeroCharacter.generated.h"
class USpringArmComponent;
struct FInputActionValue;
UCLASS()
class ARIESGAME_API AAriesHeroCharacter : public AAriesBaseCharacter
{
GENERATED_BODY()
public:
AAriesHeroCharacter();
virtual void BeginPlay() override;
//~ APawn Interface
virtual void PossessedBy(AController* NewController) override;
private:
# pragma region Components
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera", meta = (AllowPrivateAccess = "true"))
USpringArmComponent* CameraBoom = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera", meta = (AllowPrivateAccess = "true"))
UCameraComponent* FollowCamera = nullptr;
# pragma endregion
};
```
实现:
```cpp
AAriesHeroCharacter::AAriesHeroCharacter()
{
GetCapsuleComponent()->InitCapsuleSize(42.0f, 96.0f);
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
CameraBoom = CreateDefaultSubobject(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 200.0f;
CameraBoom->SocketOffset = FVector(0.0f, 55.0f, 65.0f);
CameraBoom->bUsePawnControlRotation = true;
FollowCamera = CreateDefaultSubobject(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
GetCharacterMovement()->MaxWalkSpeed = 400.0f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.0f;
}
```

### 2.4、全局设置
创建Character、Controller,GameMode的蓝图对象。
#### GameMode
#### Project Settings

> **总结**:项目设置可以设置默认启动的关卡和GameMode、而GameMode中可以装填Character、PlayerController等。
### 2.5、Gameplay Tag
因为GAS围绕Tag展开的,所以推荐使用Tag。这里使用GameplayTag。

```cpp
#pragma once
#include "NativeGameplayTags.h"
// .h
namespace AriesGameplayTags
{
/** Input Tags */
ARIESGAME_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_Move);
ARIESGAME_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_Look);
/** Player Tags */
ARIESGAME_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Player_Weapon_Axe);
}
// .cpp
#include "AriesGameplayTag.h"
namespace AriesGameplayTags
{
/** Input Tags */
UE_DEFINE_GAMEPLAY_TAG(InputTag_Move, "InputTag.Move");
UE_DEFINE_GAMEPLAY_TAG(InputTag_Look, "InputTag.Look");
/** Player Tags */
UE_DEFINE_GAMEPLAY_TAG(Player_Weapon_Axe, "Player.Weapon.Axe");
}
```
## 三、用户输入

使用DataAsset将Tag和Input关联起来。
### 3.1、定义用户输入和标签的关联资产

```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "InputAction.h"
#include "InputMappingContext.h"
#include "Engine/DataAsset.h"
#include "DataAsset_InputConfig.generated.h"
USTRUCT(BlueprintType)
struct FAriesInputActionConfig
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (Category = "InputTag")) // 最后的说明符号,指的是Tag只能选择InputTag开头的。
FGameplayTag InputTag;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr InputAction; // UInputAction*
};
UCLASS()
class ARIESGAME_API UDataAsset_InputConfig : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UInputMappingContext* DefaultMappingContext;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (TitleProperty = "InputTag")) // 指定结构数组中结构成员属性的内容,作为数组元素的显示标题。
TArray NativeInputActions;
UFUNCTION(BlueprintCallable, Category = "InputConfig")
UInputAction* FindNativeInputActionByTag(const FGameplayTag& InputTag) const;
};
```
解释:
这里首先定义了一个结构体,用于存储**GameplayTag** 和 **UInputAction**。这里注意下meta里的Category,做了一次开头过滤。只选择InputTag的因为这里我们处理的是输入动作和Tag的关系。(定义了一个键值对)
然后是输入映射UInputMappingContext和结构体数组一起定义为我们的DataAsset。这样一来**UInputMappingContext输入映射**(里面会关联UInputAction输入动作),**UInputAction输入动作**和GameplayTag就关联在了一起。
通过`FindNativeInputActionByTag()`方法根据Tag提供InputAction。(简单的键值对查询方法)
```cpp
UInputAction* UDataAsset_InputConfig::FindNativeInputActionByTag(const FGameplayTag& InputTag) const
{
for (const FAriesInputActionConfig& InputActionConfig : NativeInputActions)
{
if (InputActionConfig.InputTag == InputTag && InputActionConfig.InputAction)
{
return InputActionConfig.InputAction;
}
}
return nullptr;
}
```
### 3.2、自定义增强输入组件

```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#pragma once
#include "CoreMinimal.h"
#include "EnhancedInputComponent.h"
#include "Data/DataAsset_InputConfig.h"
#include "AriesEnhancedInputComponent.generated.h"
/**
* 输入组件 在 项目设置 default input component class 选择本类
*/
UCLASS()
class ARIESGAME_API UAriesEnhancedInputComponent : public UEnhancedInputComponent
{
GENERATED_BODY()
public:
template
void BindNativeInputAction(const UDataAsset_InputConfig* InInputConfig, const FGameplayTag& InInputTag, ETriggerEvent TriggerEvent, UserObject* ContextObject, CallbackFunc Func);
};
template
inline void UAriesEnhancedInputComponent::BindNativeInputAction(const UDataAsset_InputConfig* InInputConfig, const FGameplayTag& InInputTag, ETriggerEvent TriggerEvent, UserObject* ContextObject, CallbackFunc Func)
{
checkf(InInputConfig, TEXT("Content of InInputConfig is invalid!"));
if (UInputAction* FoundInputAction = InInputConfig->FindNativeInputActionByTag(InInputTag))
{
BindAction(FoundInputAction, TriggerEvent, ContextObject, Func);
}
}
```
这里主要定义了一个绑定方式,并内联它的实现,首先验证上一节定义的**UDataAsset_InputConfig**存在不,然后通过它的`FindNativeInputActionByTag()`方法,根据Tag找到Action再绑定回调。
#### 绑定动作资产
#### 角色指认DA
AriesHeroCharacter中添加UDataAsset_InputConfig和支持动作的回调函数。
```cpp
//~ Begin APawn Interface
virtual void SetupPlayerInputComponent(class UInputComponent* EnhancedInputComponent) override;
//~ End APawn Interface
# pragma region Inputs
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "CharacterData", meta = (AllowPrivateAccess = "true"))
UDataAsset_InputConfig* InputConfigDataAseet;
void Input_Move(const FInputActionValue& InputActionValue);
void Input_Look(const FInputActionValue& InputActionValue);
# pragma endregion
```
实现:
```cpp
void AAriesHeroCharacter::SetupPlayerInputComponent(class UInputComponent* EnhancedInputComponent)
{
checkf(InputConfigDataAseet, TEXT("InputConfigDataAseet is nullptr"));
ULocalPlayer* LocalPlayer = GetController()->GetLocalPlayer();
UEnhancedInputLocalPlayerSubsystem* EnhancedInputSubsystem = ULocalPlayer::GetSubsystem(LocalPlayer);
check(EnhancedInputSubsystem);
EnhancedInputSubsystem->AddMappingContext(InputConfigDataAseet->DefaultMappingContext, 0); // 关联映射
// DefaultMappingContext 绑定了按键键 和值
UAriesEnhancedInputComponent* AriesEnhancedInputComponent = CastChecked(InputComponent);
// 基于Tag和DataAseet 绑定动作 和 对应回调
AriesEnhancedInputComponent->BindNativeInputAction(InputConfigDataAseet, AriesGameplayTags::InputTag_Move, ETriggerEvent::Triggered, this, &AAriesHeroCharacter::Input_Move);
AriesEnhancedInputComponent->BindNativeInputAction(InputConfigDataAseet, AriesGameplayTags::InputTag_Look, ETriggerEvent::Triggered, this, &AAriesHeroCharacter::Input_Look);
}
void AAriesHeroCharacter::Input_Move(const FInputActionValue& InputActionValue)
{
const FVector2d MoventmentVector = InputActionValue.Get();
const FRotator MoventmentRotator(0.f, GetControlRotation().Yaw, 0.f);
if (MoventmentVector.Y != 0)
{
const FVector ForwardDirection = MoventmentRotator.RotateVector(FVector::ForwardVector);
AddMovementInput(ForwardDirection, MoventmentVector.Y);
}
if (MoventmentVector.X != 0)
{
const FVector RightDirection = MoventmentRotator.RotateVector(FVector::RightVector);
AddMovementInput(RightDirection, MoventmentVector.X);
}
}
void AAriesHeroCharacter::Input_Look(const FInputActionValue& InputActionValue)
{
const FVector2d MLookAxisVector = InputActionValue.Get();
if (MLookAxisVector.X != 0)
{
AddControllerYawInput(MLookAxisVector.X);
}
if (MLookAxisVector.Y != 0)
{
AddControllerPitchInput(MLookAxisVector.Y);
}
}
```
解释:
SetupPlayerInputComponent()来初始化输入组件,获取当前Player,增强输入系统里添加上下文映射,应用绑定的动作映射。转化我们之前自定义的增强。绑定回调。
## 四、动画
### 4.1、BaseAnimInstance
```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "AriesBaseAnimInstance.generated.h"
class UCharacterMovementComponent;
class AAriesBaseCharacter;
UCLASS()
class ARIESGAME_API UAriesBaseAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
virtual void NativeInitializeAnimation() override;
virtual void NativeThreadSafeUpdateAnimation(float DeltaSeconds) override;
protected:
UPROPERTY()
AAriesBaseCharacter* OwningCharacter;
UPROPERTY()
UCharacterMovementComponent* OwningMovementComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AnimData|LocomotionData")
float GroundSpeed;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AnimData|LocomotionData")
bool bHasAcceleration;
};
```
解释:
首先重新UAnimInstance接口里的方法用于驱动更新,添加引用用户角色`AAriesBaseCharacter*`和角色运动组件`UCharacterMovementComponent*`的引用。 控制动画播放速率的速度和判断启停的加速度标识。
实现:
```cpp
void UAriesBaseAnimInstance::NativeInitializeAnimation()
{
OwningCharacter = Cast(TryGetPawnOwner());
if (OwningCharacter)
{
OwningMovementComponent = OwningCharacter->GetCharacterMovement();
}
}
void UAriesBaseAnimInstance::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
if (!OwningCharacter || !OwningMovementComponent)
{
return;
}
GroundSpeed = OwningCharacter->GetVelocity().Size2D();
bHasAcceleration = OwningMovementComponent->GetCurrentAcceleration().SizeSquared2D() > 0.f;
}
```
`NativeInitializeAnimation()` 用于初始化属性。`NativeThreadSafeUpdateAnimation()`用于修正速度和加速度。
### 4.2、HeroAnimation
即创建BaseAnim的子类。

### 4.3、创建主角动画
#### HeroAnimInstance
```cpp
UCLASS()
class ARIESGAME_API UAriesHeroAnimInstance : public UAriesBaseAnimInstance
{
GENERATED_BODY()
public:
virtual void NativeInitializeAnimation() override;
virtual void NativeThreadSafeUpdateAnimation(float DeltaSeconds) override;
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AnimData|Refrences")
AAriesHeroCharacter* OwningHeroCharacter;//
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AnimData|LocomotionData")
bool bShouldEnableRelaxState; // 新增放松状态
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "AnimData|LocomotionData")
float EnableRelaxStateThreshold = 0.5f;// 新增阈值
float IdleElpasedTime;// 累计时间
};
```
实现:
```cpp
void UAriesHeroAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
if (OwningCharacter)
{
OwningHeroCharacter = Cast(OwningCharacter);
}
}
void UAriesHeroAnimInstance::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
Super::NativeThreadSafeUpdateAnimation(DeltaSeconds);
if (bHasAcceleration)
{
IdleElpasedTime = 0.0f;
bShouldEnableRelaxState = false;
}
else
{
IdleElpasedTime += DeltaSeconds;
bShouldEnableRelaxState = (IdleElpasedTime >= EnableRelaxStateThreshold);
}
}
```
`NativeInitializeAnimation()`初始化主角角色,`NativeThreadSafeUpdateAnimation()` 静止状态累计过渡时间随时切换到自由态。
#### BlendSpace
创建命名BS_UnarmedLocmotion的BlendSpace。
#### 动画蓝图实现
判断Idle -- Jog 使用*bHasAcceleration*,Idle -- Relax 使用*bShouldEnableRelaxState*。
## 五、GAS
开启插件

### 5.1、创建ASC和Set

```cpp
UCLASS()
class ARIESGAME_API UAriesAbilitySystemComponent : public UAbilitySystemComponent
{
GENERATED_BODY()
};
```
继承UAbilitySystemComponent即可。
```cpp
UCLASS()
class ARIESGAME_API UAriesAttributeSet : public UAttributeSet
{
GENERATED_BODY()
};
```
继承UAttributeSet即可。
#### 角色追加ASC和Set
```cpp
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemInterface.h"
#include "GameFramework/Character.h"
#include "AriesBaseCharacter.generated.h"
class UAriesAttributeSet;
class UAriesAbilitySystemComponent;
class UDataAsset_StartUpDataBase;
UCLASS()
class ARIESGAME_API AAriesBaseCharacter : public ACharacter, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AAriesBaseCharacter();
protected:
//~ Begin APawn Interface
virtual void PossessedBy(AController* NewController) override;
//~ End APawn Interface
//~ Begin IAbilitySystemInterface Interface
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const;
//~ End IAbilitySystemInterface Interface
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AbilitySystem")
UAriesAbilitySystemComponent* AriesAbilitySystemComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AbilitySystem")
UAriesAttributeSet* AriesAttributeSet;
public:
FORCEINLINE UAriesAbilitySystemComponent* GetAriesAbilitySystemComponent() const { return AriesAbilitySystemComponent; }
FORCEINLINE UAriesAttributeSet* GetAttributeSet() const { return AriesAttributeSet; }
};
```
这里先给我们角色追加了**IAbilitySystemInterface接口**,重写`GetAbilitySystemComponent()`用于对外提供ASC。其它就是属性和它的Get方法,也直接使用了内联宏。
```cpp
AAriesBaseCharacter::AAriesBaseCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = false;
GetMesh()->bReceivesDecals = false;
AriesAbilitySystemComponent = CreateDefaultSubobject(TEXT("AriesAbilitySystemComponent"));
AriesAttributeSet = CreateDefaultSubobject(TEXT("AriesAttributeSet"));
}
void AAriesBaseCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
if (AriesAbilitySystemComponent)
{
AriesAbilitySystemComponent->InitAbilityActorInfo(this, this);
}
}
UAbilitySystemComponent* AAriesBaseCharacter::GetAbilitySystemComponent() const
{
return GetAriesAbilitySystemComponent();
}
```
初始化ASC和AttributeSet的地方。PossessedBy里初始化ActorInfo用于后续各种访问信息。
### 5.2、创建GA
```cpp
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "AriesGameplayAbility.generated.h"
UENUM(BlueprintType)
enum class EAriesAbilityActivationPolicy : uint8
{
OnTriggered,
OnGiven
};
UCLASS()
class ARIESGAME_API UAriesGameplayAbility : public UGameplayAbility
{
GENERATED_BODY()
protected:
//~ Begin UGameplayAbility interface
virtual void OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
//~ End UGameplayAbility interface
UPROPERTY(EditDefaultsOnly, Category = "AriesAbility")
EAriesAbilityActivationPolicy AbilityActivationPolicy = EAriesAbilityActivationPolicy::OnTriggered;
};
```
解释:
创建枚举类归类Ability激活的类型。创建AriesGameplayAbility重写UGameplayAbility接口的OnGiveAbility、EndAbility方法。再添加一个标识激活方式的属性类型为刚定义的枚举。
实现:
```cpp
void UAriesGameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
Super::OnGiveAbility(ActorInfo, Spec);
if (AbilityActivationPolicy == EAriesAbilityActivationPolicy::OnGiven)
{
if (ActorInfo && !Spec.IsActive())
{
ActorInfo->AbilitySystemComponent->TryActivateAbility(Spec.Handle);
}
}
}
void UAriesGameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
if (AbilityActivationPolicy == EAriesAbilityActivationPolicy::OnGiven)
{
if (ActorInfo)
{
ActorInfo->AbilitySystemComponent->ClearAbility(Handle);
}
}
}
```
实现激活Ability的基本流程,不过加了类型判断`EAriesAbilityActivationPolicy::OnGiven`才可以激活和结束走这里的逻辑,不可重复激活。
### 5.3、武器GA
创建UAriesGameplayAbility的实现类,并在蓝图里实现想要实现的逻辑。

#### 武器Actor制作

```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "AriesWeaponBase.generated.h"
class UBoxComponent;
UCLASS()
class ARIESGAME_API AAriesWeaponBase : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AAriesWeaponBase();
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapons")
UStaticMeshComponent* WaeaponMesh;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapons")
UBoxComponent* WeaponCollisionBox;
public:
FORCEINLINE UBoxComponent* GetWeaponCollisionBox() const { return WeaponCollisionBox; }
};
```
制作一个有碰撞和模型的模板。
实现:
```cpp
AAriesWeaponBase::AAriesWeaponBase()
{
//不用tick
PrimaryActorTick.bCanEverTick = false;
WaeaponMesh =CreateDefaultSubobject(TEXT("WeaponMesh"));
SetRootComponent(WaeaponMesh);
WeaponCollisionBox = CreateDefaultSubobject(TEXT("WeaponCollisionBox"));
WeaponCollisionBox->SetupAttachment(GetRootComponent());
WeaponCollisionBox->SetBoxExtent(FVector(20.0f));
WeaponCollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
```
子类实现类



给角色追加武器Socket
#### 完善武器GA蓝图逻辑
生成武器Actor
追加到指定插槽(中间的Reginster之后的逻辑)。
#### 管理武器GA的DataAsset
创建DataAsset来管理能力
```cpp
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "DataAsset_StartUpDataBase.generated.h"
class UAriesAbilitySystemComponent;
class UAriesGameplayAbility;
UCLASS(BlueprintType)
class ARIESGAME_API UDataAsset_StartUpDataBase : public UDataAsset
{
GENERATED_BODY()
public:
virtual void GiveToAbilitySystemComponent(UAriesAbilitySystemComponent* InASCToGive, int32 ApplyLevel = 1);
protected:
UPROPERTY(EditDefaultsOnly, Category = "StartUpData")
TArray> ActiveOnGivenAbilities;
UPROPERTY(EditDefaultsOnly, Category = "StartUpData")
TArray> ReactiveAbilities;
void GrantAbilities(const TArray>& InAbilitiesToGive, UAriesAbilitySystemComponent* InASCToGive, int32 ApplyLevel = 1);
};
```
DataAsset中管理两组Ability和追加Ability的方式。
实现:
```cpp
void UDataAsset_StartUpDataBase::GiveToAbilitySystemComponent(UAriesAbilitySystemComponent* InASCToGive, int32 ApplyLevel)
{
checkf(InASCToGive, TEXT("InAriesASCToGive is nullptr!"));
GrantAbilities(ActiveOnGivenAbilities, InASCToGive, ApplyLevel);
GrantAbilities(ReactiveAbilities, InASCToGive, ApplyLevel);
}
void UDataAsset_StartUpDataBase::GrantAbilities(const TArray>& InAbilitiesToGive, UAriesAbilitySystemComponent* InASCToGive, int32 ApplyLevel)
{
if (InAbilitiesToGive.IsEmpty())
{
return;
}
for (const TSubclassOf& Ability : InAbilitiesToGive)
{
if (!Ability) continue;
FGameplayAbilitySpec AbilitySpec(Ability);
AbilitySpec.SourceObject = InASCToGive->GetAvatarActor();
AbilitySpec.Level = ApplyLevel;
InASCToGive->GiveAbility(AbilitySpec);
}
}
```
两种类型Ability分别激活。
再创建它的派生类:

分配之前的GA到蓝图的DataAsset上。

#### 给角色添加DataAsset
通过软引用的方式来加载DA,DA数据会很大里面关联大量资产。


只做验证不在这里使用。蓝图使用如下

```cpp
void AAriesHeroCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
if (!CharacterStartUpData.IsNull())
{
if (UDataAsset_StartUpDataBase* LoadedData = CharacterStartUpData.LoadSynchronous())
{
LoadedData->GiveToAbilitySystemComponent(AriesAbilitySystemComponent);
}
}
}
```
这里会注册GA给角色。被注册的GA会执行它的逻辑

## 六、能力系统
### 6.1、角色拓展组件
```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "PawnExtensionComponentBase.generated.h"
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class ARIESGAME_API UPawnExtensionComponentBase : public UActorComponent
{
GENERATED_BODY()
protected:
// 任意类型转化
template
T* GetOwningPawn() const
{
// 断言:T必须是APawn的子类
static_assert(TPointerIsConvertibleFromTo::Value, "'T' must be derived from APawn");
return CastChecked(GetOwner());
}
// 对内
APawn* GetOwningPawn() const
{
return GetOwningPawn();
}
template
T* GetOwningController() const
{
// 断言:T必须是AController的子类
static_assert(TPointerIsConvertibleFromTo::Value, "'T' must be derived from AController");
return GetOwningPawn()->GetController();
}
};
```
这是一个对外获取Controller和Pawn的工具组件,具有承上启下作用。为HeroCharacter追加该组件:
#### 战斗拓展组件
UPawnCombatComponent是UPawnExtensionComponentBase的继承子类。
```cpp
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Component/PawnExtensionComponentBase.h"
#include "PawnCombatComponent.generated.h"
class AAriesWeaponBase;
UCLASS()
class ARIESGAME_API UPawnCombatComponent : public UPawnExtensionComponentBase
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Aries|Combat")
void ReginsterSpawnedWeapon(FGameplayTag InWeaponTagToRegister, AAriesWeaponBase* InWeaponToRegister, bool bReginsterAsEquippedWeapon = false);
UFUNCTION(BlueprintCallable, Category = "Aries|Combat")
AAriesWeaponBase* GetCharacterCarriedWeaponByTag(FGameplayTag InWeaponTagToGet) const;
UFUNCTION(BlueprintCallable, Category = "Aries|Combat")
AAriesWeaponBase* GetCharacterCurrentEquippedWeapon(FGameplayTag InWeaponTagToGet) const;
UPROPERTY(BlueprintReadWrite, Category = "Aries|Combat")
FGameplayTag CurrentEquippedWeaponTag;
private:
TMap CharacterCarriedWeaponMap;
};
```
通过该组件我们讲服务角色各种武器,使用GA来拓展的武器可以用GameplayTag关联全局。用CharacterCarriedWeaponMap来关联FGameplayTag 和 AAriesWeaponBase,这是一个Map键值对。然后通过CurrentEquippedWeaponTag代表当前武器。之后通过三个方法来注册、切换。
```cpp
#include "Component/Combat/PawnCombatComponent.h"
#include "Items/Weapon/AriesWeaponBase.h"
#include "AriesDeBugHelper.h"
void UPawnCombatComponent::ReginsterSpawnedWeapon(FGameplayTag InWeaponTagToRegister, AAriesWeaponBase* InWeaponToRegister, bool bReginsterAsEquippedWeapon)
{
checkf(!CharacterCarriedWeaponMap.Contains(InWeaponTagToRegister), TEXT("A named named %s has already been added as carried weapon"),*InWeaponTagToRegister.ToString());
checkf(InWeaponToRegister, TEXT("PawnCombatComponent::ReginsterSpawnedWeapon: InWeaponToRegister is nullptr!"));
CharacterCarriedWeaponMap.Emplace(InWeaponTagToRegister, InWeaponToRegister);
if (bReginsterAsEquippedWeapon)
{
CurrentEquippedWeaponTag = InWeaponTagToRegister;
}
const FString WeaponString = FString::Printf(TEXT("Registered weapon %s with tag %s"), *InWeaponToRegister->GetName(), *InWeaponTagToRegister.ToString());
Debug::Print(WeaponString);
}
AAriesWeaponBase* UPawnCombatComponent::GetCharacterCarriedWeaponByTag(FGameplayTag InWeaponTagToGet) const
{
if (CharacterCarriedWeaponMap.Contains(InWeaponTagToGet))
{
AAriesWeaponBase* const* FoundWeapon = CharacterCarriedWeaponMap.Find(InWeaponTagToGet);
return *FoundWeapon;
}
return nullptr;
}
AAriesWeaponBase* UPawnCombatComponent::GetCharacterCurrentEquippedWeapon(FGameplayTag InWeaponTagToGet) const
{
if (!CurrentEquippedWeaponTag.IsValid())
{
return nullptr;
}
return GetCharacterCarriedWeaponByTag(CurrentEquippedWeaponTag);
}
```
*ReginsterSpawnedWeapon*:添加武器到Map集合中。
*GetCharacterCarriedWeaponByTag*:根据标签获取武器Actor。
*GetCharacterCurrentEquippedWeapon*:调用GetCharacterCarriedWeaponByTag。
#### 角色追加战斗组件
```cpp
UCLASS()
class ARIESGAME_API AAriesHeroCharacter : public AAriesBaseCharacter
{
GENERATED_BODY()
public:
///....
///....
///....
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Combat", meta = (AllowPrivateAccess = "true"))
TObjectPtr HeroCombatComponent;
public:
FORCEINLINE UHeroCombatComponent* GetHeroCombatComponent() const { return HeroCombatComponent; }
};
```

蓝图调用:
GA中追加Reginster

### 6.2、HeroGameplayAbility
创建的子类AriesHeroGameplayAbility,UAriesHeroGameplayAbility。

父类GA中追加ASC。
```cpp
UAriesAbilitySystemComponent* UAriesGameplayAbility::GetAriesAbilitySystemComponentFromActorInfo() const
{
return Cast(CurrentActorInfo->AbilitySystemComponent);
}
```
#### 独有逻辑
```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystem/Abilities/AriesGameplayAbility.h"
#include "AriesHeroGameplayAbility.generated.h"
class UHeroCombatComponent;
class AAriesHeroPlayerController;
class AAriesHeroCharacter;
/**
*
*/
UCLASS()
class ARIESGAME_API UAriesHeroGameplayAbility : public UAriesGameplayAbility
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "Aries|Ability")
AAriesHeroCharacter* GetHeroCharacterFromActorInfo();// 并非 const 后面有用
UFUNCTION(BlueprintPure, Category = "Aries|Ability")
AAriesHeroPlayerController* GetHeroPlayerControllerFromActorInfo();
UFUNCTION(BlueprintPure, Category = "Aries|Ability")
UHeroCombatComponent* GetHeroCombatComponentFromActorInfo();
private:
TWeakObjectPtr CachedAriesHeroCharacter;
TWeakObjectPtr AriesHeroPlayerController;
};
```
获取角色 控制器 HeroCombatComponent
```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#include "AbilitySystem/Abilities/AriesHeroGameplayAbility.h"
#include "Character/AriesHeroCharacter.h"
#include "Syetem/AriesHeroPlayerController.h"
AAriesHeroCharacter* UAriesHeroGameplayAbility::GetHeroCharacterFromActorInfo()
{
if (!CachedAriesHeroCharacter.IsValid())
{
CachedAriesHeroCharacter = Cast(CurrentActorInfo->AvatarActor);
}
return CachedAriesHeroCharacter.IsValid()? CachedAriesHeroCharacter.Get() : nullptr;
}
AAriesHeroPlayerController* UAriesHeroGameplayAbility::GetHeroPlayerControllerFromActorInfo()
{
if (!AriesHeroPlayerController.IsValid())
{
AriesHeroPlayerController = Cast(CurrentActorInfo->PlayerController);
}
return AriesHeroPlayerController.IsValid()? AriesHeroPlayerController.Get() : nullptr;
}
UHeroCombatComponent* UAriesHeroGameplayAbility::GetHeroCombatComponentFromActorInfo()
{
return GetHeroCharacterFromActorInfo()->GetHeroCombatComponent();
}
```
蓝图创建(GA_Hero_EquipAxe)

### 6.3、Ability Input Action
#### 拓展标签

#### 拓展Input
它不需要Find这样的函数,UAriesEnhancedInputComponent里实现。先拓展UDataAsset_InputConfig里的结构体FAriesInputActionConfig。添加一个IsValid方式。
```cpp
USTRUCT(BlueprintType)
struct FAriesInputActionConfig
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (Category = "InputTag")) // 最后的说明符号,指的是Tag只能选择InputTag开头的。
FGameplayTag InputTag;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr InputAction; // UInputAction*
bool IsValid() const { return InputTag.IsValid() && InputAction; }
};
```
实现:UAriesEnhancedInputComponent
```cpp
#pragma once
#include "CoreMinimal.h"
#include "EnhancedInputComponent.h"
#include "Data/DataAsset_InputConfig.h"
#include "AriesEnhancedInputComponent.generated.h"
/**
* 输入组件 在 项目设置 default input component class 选择本类
*/
UCLASS()
class ARIESGAME_API UAriesEnhancedInputComponent : public UEnhancedInputComponent
{
GENERATED_BODY()
public:
template
void BindNativeInputAction(const UDataAsset_InputConfig* InInputConfig, const FGameplayTag& InInputTag, ETriggerEvent TriggerEvent, UserObject* ContextObject, CallbackFunc Func);
template
void BindAbilityInputAction(const UDataAsset_InputConfig* InInputConfig, UserObject* ContextObject, CallbackFunc InputPressedFunc, CallbackFunc InputReleasedFunc);
};
template
inline void UAriesEnhancedInputComponent::BindNativeInputAction(const UDataAsset_InputConfig* InInputConfig, const FGameplayTag& InInputTag, ETriggerEvent TriggerEvent, UserObject* ContextObject, CallbackFunc Func)
{
checkf(InInputConfig, TEXT("Content of InInputConfig is invalid!"));
if (UInputAction* FoundInputAction = InInputConfig->FindNativeInputActionByTag(InInputTag))
{
BindAction(FoundInputAction, TriggerEvent, ContextObject, Func);
}
}
template
void UAriesEnhancedInputComponent::BindAbilityInputAction(const UDataAsset_InputConfig* InInputConfig, UserObject* ContextObject, CallbackFunc InputPressedFunc, CallbackFunc InputReleasedFunc)
{
checkf(InInputConfig, TEXT("Content of InInputConfig is invalid!"));
for (const FAriesInputActionConfig& AriesInputActionConfig : InInputConfig->AbilityInputActions)
{
if (!AriesInputActionConfig.IsValid()) continue;
BindAction(AriesInputActionConfig.InputAction, ETriggerEvent::Started, ContextObject, InputPressedFunc, AriesInputActionConfig.InputTag);
BindAction(AriesInputActionConfig.InputAction, ETriggerEvent::Completed, ContextObject, InputReleasedFunc, AriesInputActionConfig.InputTag);
}
}
```
追加了一个新的模板类,可以绑定两个回调。蓝图绑定

#### 绑定输入
整个流程

我们需要将Tag赋予Ability,再将Ability赋予Hero
```cpp
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Data/StartUpData/DataAsset_StartUpDataBase.h"
#include "AbilitySystem/Abilities/AriesGameplayAbility.h"
#include "DataAsset_HeroStartUpData.generated.h"
USTRUCT(BlueprintType)
struct FAriesHeroAbilitySet
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (Categories = "InputTag"))
FGameplayTag InputTag;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSubclassOf AbilityToGrant;
bool IsValid() const { return InputTag.IsValid() && AbilityToGrant;};
};
UCLASS()
class ARIESGAME_API UDataAsset_HeroStartUpData : public UDataAsset_StartUpDataBase
{
GENERATED_BODY()
public:
virtual void GiveToAbilitySystemComponent(UAriesAbilitySystemComponent* InASCToGive, int32 ApplyLevel = 1) override;
private:
UPROPERTY(EditDefaultsOnly, Category="StartUpData", meta = ( Title = "InputTag"))
TArray HeroStartUpAbilitySets;
};
```
实现:
```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#include "Data/StartUpData/DataAsset_HeroStartUpData.h"
#include "AbilitySystem/AriesAbilitySystemComponent.h"
void UDataAsset_HeroStartUpData::GiveToAbilitySystemComponent(UAriesAbilitySystemComponent* InASCToGive, int32 ApplyLevel)
{
Super::GiveToAbilitySystemComponent(InASCToGive, ApplyLevel);
for (const FAriesHeroAbilitySet& AbilitySet : HeroStartUpAbilitySets)
{
if (!AbilitySet.IsValid()) continue;
FGameplayAbilitySpec AbilitySpec(AbilitySet.AbilityToGrant);
AbilitySpec.SourceObject = InASCToGive->GetAvatarActor();
AbilitySpec.Level = ApplyLevel;
AbilitySpec.GetDynamicSpecSourceTags().AddTag(AbilitySet.InputTag);
InASCToGive->GiveAbility(AbilitySpec);
}
}
```
逗号键调试。要F8选中角色。
#### 回调绑定按键
AAriesHeroCharacter里添加回到用于绑定

实现:

上文模板提到的绑定双回调的函数。拓展UAriesAbilitySystemComponent。
```cpp
UCLASS()
class ARIESGAME_API UAriesAbilitySystemComponent : public UAbilitySystemComponent
{
GENERATED_BODY()
public:
void OnAbilityInputPressed(const FGameplayTag& InInputTag);
void OnAbilityInputReleased(const FGameplayTag& InInputTag);
};
// cpp
void UAriesAbilitySystemComponent::OnAbilityInputPressed(const FGameplayTag& InInputTag)
{
if (!InInputTag.IsValid()) return;
for (const FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
{
if (!AbilitySpec.GetDynamicSpecSourceTags().HasTagExact(InInputTag)) continue;
TryActivateAbility(AbilitySpec.Handle);
}
}
void UAriesAbilitySystemComponent::OnAbilityInputReleased(const FGameplayTag& InInputTag)
{
}
```
这样AAriesHeroCharacter也可以实现。
```cpp
void AAriesHeroCharacter::Input_AbilityInputPresses(FGameplayTag InInputTag)
{
AriesAbilitySystemComponent->OnAbilityInputPressed(InInputTag);
}
void AAriesHeroCharacter::Input_AbilityInputReleased(FGameplayTag InInputTag)
{
AriesAbilitySystemComponent->OnAbilityInputReleased(InInputTag);
}
```
蓝图里绑定按键和上下文映射




通过 两个DataAsset关联了起来。
### 6.4、动画响应
#### AssetTool插曲


#### 动画蒙太奇&插槽
创建后的蒙太奇用命名修复工具修复。

#### 升级动画蓝图

> 标记下自己犯的错误;
> 
>
> 这里导致我播放的Montage 只播放了一帧。
#### 武器切换:GameplayEvent的使用
会在播放蒙太奇的时候切换武器到手上,建立动画通知来完善插槽切换。创建AnimNotify :AN_SendGameplayEventToOwner

传递的事件的行为是通过Tag完成的,方法名是:`UAbilitySystemBlueprintLibrary::SendGameplayEventToActor`

能力激活播放蒙太奇,蒙太奇之后绑定 *WaitGameplayEvent* 来响应动画通知事件。
#### AnimLayerInterface
继承接口
GA里设置下Tag代表武器

这样之后激活技能之后,人物就会僵硬切换到AnimLayer导致的。
#### MasterAnimLayer
创建同骨骼类型为*UAriesHeroLinkedAnimLayer*的动画蓝图,并继承之前的接口。
双击实现动画

取消一个维度的值输入。将BlendSpace作为可传参数。

因为这里不是直接的动画蓝图位置,所以可以开放一个线程安全的方式获取当前动画蓝图,拓展UAriesHeroLinkedAnimLayer。
```cpp
UCLASS()
class ARIESGAME_API UAriesHeroLinkedAnimLayer : public UAriesBaseAnimInstance
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe))
UAriesHeroAnimInstance* GetHeroAnimInstance() const;
};
UAriesHeroAnimInstance* UAriesHeroLinkedAnimLayer::GetHeroAnimInstance() const
{
return Cast(GetOwningComponent()->GetAnimInstance());
}
```
这里会级联一个链式方法获取更多数据。 创建真正的子类实现逻辑。


之后只要指认了**BlendSpace**就可以工作了

#### LinkAnimLayer
通过DataAsset关联AnimLayer和武器,创建数据库

```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#pragma once
#include "CoreMinimal.h"
#include "AriesStructTypes.generated.h"
class UAriesHeroLinkedAnimLayer;
USTRUCT(BlueprintType)
struct FAriesHeroWeaponData
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSubclassOf WeaponAnimLayerToLink;
};
```
这个数据里存档AnimLayer
```cpp
#pragma once
#include "CoreMinimal.h"
#include "AriesTypes/AriesStructTypes.h"
#include "Items/Weapon/AriesWeaponBase.h"
#include "AriesHeroWeapon.generated.h"
UCLASS()
class ARIESGAME_API AAriesHeroWeapon : public AAriesWeaponBase
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "WeaponData")
FAriesHeroWeaponData AriesHeroWeaponData;
};
```
武器里关联Data,UHeroCombatComponent中最佳获取武器的方法。

```cpp
AAriesHeroWeapon* UHeroCombatComponent::GetHeroCarriedWeaponByTag(FGameplayTag InWeaponTag) const
{
return Cast(GetCharacterCarriedWeaponByTag(InWeaponTag));
}
```
然后关联武器和动画。

补充GA里的逻辑

> 总结:
>
> 这样做的好处是,通过MasterLayer,创建新的Layer,而新的Layer也只需要指认混合空间,再分配给武器就可以直接使用了。
> 
> 总结:
>
> 设计能力的一个基本流程,创建GameplayTag、创建Ability蓝图、创建蒙太奇、绑定输入、激活能力。
### 6.5、标准流程卸除武器
==1、拓展基础标签==
之前能力完善标签
==2、创建蒙太奇==

==3、能力GA==

==4、Input==


### 6.6、切换动作映射上下文
#### 重构数据结构
在全局数据结构体集合里设置HeroAbilitySet。之后可以在武器里关联输入上下文、Ability、Anim Link等

完整拓展
```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "AbilitySystem/Abilities/AriesGameplayAbility.h"
#include "AriesStructTypes.generated.h"
class UInputMappingContext;
class UAriesHeroLinkedAnimLayer;
USTRUCT(BlueprintType)
struct FAriesHeroAbilitySet
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (Categories = "InputTag"))
FGameplayTag InputTag;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSubclassOf AbilityToGrant;
bool IsValid() const { return InputTag.IsValid() && AbilityToGrant;};
};
USTRUCT(BlueprintType)
struct FAriesHeroWeaponData
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSubclassOf WeaponAnimLayerToLink;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UInputMappingContext* WeaponInputMappingContext;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (TitleProperty = "InputTag"))
TArray DefaultWeaponAbilities;
};
```
创建新的UInputMappingContext

Weapon上绑定Link动画 武器 以及能力

#### 装配武器和切换上下文
优化GA_装备里的逻辑。

方法实现:

==1-1==

==1-2==

这里给1是为了设置更高的优先级。

==1-3==
UAriesAbilitySystemComponent里追加Weapon激活Weapon能力的逻辑
实现:
```cpp
void UAriesAbilitySystemComponent::GrantHeroWeaponAbilities(const TArray& InDefaultWeaponAbilities, int32 ApplyLevel)
{
if (InDefaultWeaponAbilities.IsEmpty()) return;
for (const FAriesHeroAbilitySet& AbilitySet : InDefaultWeaponAbilities)
{
if (!AbilitySet.IsValid()) continue;
FGameplayAbilitySpec AbilitySpec(AbilitySet.AbilityToGrant);
AbilitySpec.SourceObject = GetAvatarActor();
AbilitySpec.Level = ApplyLevel;
AbilitySpec.GetDynamicSpecSourceTags().AddTag(AbilitySet.InputTag);
GiveAbility(AbilitySpec);
}
}
```
### 6.7、卸除相关组件
当激活UnEquip的Ability时,还要取消关联Equip的AnimLayer、MappingContext、其它的Ability。
#### 重新绑定武器插槽
将武器放在背后。

为了保证模型统一如下设置:

之后是解除之前Weapon的Ability。在*GA_Hero_UnEquipAxe*里也定义一个Function:`HandleUnequipWeapon`
==2-1==
解除AnimLayer
==2-2==
解除MappingContext

==2-3==
取消能力,升级UAriesAbilitySystemComponent 返回句柄
实现:

拓展代码:
AAriesHeroWeapon中记录之前能力的句柄。
```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#pragma once
#include "CoreMinimal.h"
#include "AriesTypes/AriesStructTypes.h"
#include "Items/Weapon/AriesWeaponBase.h"
#include "AriesHeroWeapon.generated.h"
struct FGameplayAbilitySpecHandle;
UCLASS()
class ARIESGAME_API AAriesHeroWeapon : public AAriesWeaponBase
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "WeaponData")
FAriesHeroWeaponData AriesHeroWeaponData;
// 新增
UFUNCTION(BlueprintCallable, Category = "Weapon")
void AssignGrantedAbilitySpecHandles(const TArray& InSpecHandles);
UFUNCTION(BlueprintPure)
TArray GetGrantedAbilitySpecHandles() const;
private:
TArray GrantedAbilitySpecHandles;
};
```
实现:
```cpp
void AAriesHeroWeapon::AssignGrantedAbilitySpecHandles(const TArray& InSpecHandles)
{
GrantedAbilitySpecHandles = InSpecHandles;
}
TArray AAriesHeroWeapon::GetGrantedAbilitySpecHandles() const
{
return GrantedAbilitySpecHandles;
}
```
GA_Hero_EquipAxe中补充实现,存储句柄以之后卸除使用:

更新逻辑,然后在Unequip之后释放掉Weapon的Ability。
```cpp
UCLASS()
class ARIESGAME_API UAriesAbilitySystemComponent : public UAbilitySystemComponent
{
GENERATED_BODY()
public:
void OnAbilityInputPressed(const FGameplayTag& InInputTag);
void OnAbilityInputReleased(const FGameplayTag& InInputTag);
UFUNCTION(BlueprintCallable, Category = "Aries|Ability", meta = (ApplyLevel = "1"))
void GrantHeroWeaponAbilities(const TArray& InDefaultWeaponAbilities, int32 ApplyLevel, TArray& OutGrantedAbilitySpecHandles);
UFUNCTION(BlueprintCallable, Category = "Aries|Ability", meta = (ApplyLevel = "1"))
void RemoveGrantedHeroWeaponAbilities(UPARAM(ref) TArray& InSpecHandlesToRemove);// UPARAM(ref) 不会认为它是一个输出 没有输出引脚
};
```
实现:
```cpp
void UAriesAbilitySystemComponent::RemoveGrantedHeroWeaponAbilities(TArray& InSpecHandlesToRemove)
{
if (InSpecHandlesToRemove.IsEmpty()) return;
for (const FGameplayAbilitySpecHandle& Handle : InSpecHandlesToRemove)
{
if (Handle.IsValid())
{
ClearAbility(Handle);
}
}
InSpecHandlesToRemove.Empty();// release memory
}
```
蓝图里更新第三步

完善逻辑,释放掉Combat的GameplayTag

动画周期可以设置动画蓝图里的*Blend Time*。
## 七、轻攻击
拓展标签

### 7.1、创建GA
创建*GA_Hero_LightAttackMaster*类型为AriesHeroGameplayAbility,它有如下的标签

激活轻攻击期间不能卸/装武器,不能激活第二次轻攻击。修正之前的能力逻辑

### 7.2、处理输入
创建新的Input_Action

创建GA子类





测试打印内容,成功!

### 7.3、Combat Logic
最后一种只能在C++实现。


通过Map来控制Combo。整个Combo的Logic如下:

来到子类构建数据

创建蒙太奇和新的插槽,调整速率 2.2 2.0 1.0 1.9


升级动画蓝图

## 八、重攻击
### 8.1、基本流程
如上面一样流程:

1. 创建一个HeroAbility,是用于HeavyAttach的。赋予基本逻辑和标签。

2. 创建1中的实现GA。
3. 通过武器蓝图关联**InputMappingContext**、**InputTag**、**Ability**。(武器才有的)

4. 创建一个**InputAction**。
5. InputMappingContext里关联 InputAction。

6. DataAsset_InputConfig里关联InputTag 和 InputAction。(输入所关心的)

### 8.2、重攻击Logic




### 8.3、轻重攻击融合

追加动作终结的系统。
#### 构建蓝图库
.h
```cpp
struct FGameplayTag;
class UAriesAbilitySystemComponent;
UENUM()
enum class EAriesConfimType : uint8
{
Yes,
No
};
UCLASS()
class ARIESGAME_API UAriesFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
# pragma region OnlyForCode
static UAriesAbilitySystemComponent* NativeGetAriesASCFromActor(AActor* InActor);
static bool NativeDoesActorHaveTag(AActor* InActor, FGameplayTag TagToCheck);
# pragma endregion
# pragma region BPAndCode
UFUNCTION(BlueprintCallable, Category = "Aries|FunctionLibrary")
static void AddGameplayTagToActorInNone(AActor* InActor, FGameplayTag TagToAdd);
UFUNCTION(BlueprintCallable, Category = "Aries|FunctionLibrary")
static void RemoveGameplayTagFromActorInFound(AActor* InActor, FGameplayTag TagToRemove);
UFUNCTION(BlueprintCallable, Category = "Aries|FunctionLibrary", meta = (DisplayName = "Does Actor Have Tag", ExpandEnumAsExecs = "OutConfimType")) // 将自定义枚举作为执行体返回引脚
static bool BP_DoesActorHaveTag(AActor* InActor, FGameplayTag TagToCheck, EAriesConfimType& OutConfimType);
# pragma endregion
};
```
.cpp
```cpp
// NaughtyCat -- MyRpgGame Aries!!!
#include "AriesFunctionLibrary.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystem/AriesAbilitySystemComponent.h"
UAriesAbilitySystemComponent* UAriesFunctionLibrary::NativeGetAriesASCFromActor(AActor* InActor)
{
check(InActor);
return CastChecked(UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(InActor));
}
void UAriesFunctionLibrary::AddGameplayTagToActorInNone(AActor* InActor, FGameplayTag TagToAdd)
{
UAriesAbilitySystemComponent* ASC = UAriesFunctionLibrary::NativeGetAriesASCFromActor(InActor);
if (!ASC->HasMatchingGameplayTag(TagToAdd))
{
// 允许 GameCode 添加不受 GameplayEffect 支持的松散游戏标签。以这种方式添加的标签不会被复制!如果需要复制,请使用这些函数的 'Replicated' 版本。由调用 GameCode 确保在必要时将这些标签添加到 clientsserver 上
ASC->AddLooseGameplayTag(TagToAdd);
}
}
void UAriesFunctionLibrary::RemoveGameplayTagFromActorInFound(AActor* InActor, FGameplayTag TagToRemove)
{
UAriesAbilitySystemComponent* ASC = UAriesFunctionLibrary::NativeGetAriesASCFromActor(InActor);
if (ASC->HasMatchingGameplayTag(TagToRemove))
{
ASC->RemoveLooseGameplayTag(TagToRemove);
}
}
bool UAriesFunctionLibrary::NativeDoesActorHaveTag(AActor* InActor, FGameplayTag TagToCheck)
{
UAriesAbilitySystemComponent* ASC = UAriesFunctionLibrary::NativeGetAriesASCFromActor(InActor);
return ASC->HasMatchingGameplayTag(TagToCheck);
}
void UAriesFunctionLibrary::BP_DoesActorHaveTag(AActor* InActor, FGameplayTag TagToCheck, EAriesConfimType& OutConfimType)
{
OutConfimType = UAriesFunctionLibrary::NativeDoesActorHaveTag(InActor, TagToCheck) ? EAriesConfimType::Yes : EAriesConfimType::No;
}
```

#### 终结

==轻攻击追加可以终结的标记:==


==重攻击触发终结==

补充:

## 九、增加细节效果
### 9.1、时间膨胀
新增动画通知状态:*ANS_SlowMotion*
重写方法1:

重写方法2

添加通知:



### 9.2、音效
声音通知:

Ctrl + 空格唤出内容浏览器,已经有通知的自己加轨道复制粘贴。
去掉打印

精细化类型,换成UAriesHeroGameplayAbility

效果就是如下,只能找到Hero的GA

[第二部分](README2.md)