# 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. 添加第三人称游戏模板。并重构结构。 ![image-20241221104932559](./images/image-20241221104932559.png) ![image-20250104184828198](./images/image-20250104184828198.png) #### 软硬引用的对比 1. 右键查看资产的Size Map... ![image-20241221110945562](./images/image-20241221110945562.png) ![image-20241221111918336](./images/image-20241221111918336.png) ![image-20241221112031368](./images/image-20241221112031368.png) 2. 引用关系图 ![image-20241221111036259](./images/image-20241221111036259.png) 3. 解决 - 使用更为廉价的父类、C++类、接口等。并不是持有蓝图才厚重,而是硬引用的对象厚重。 - 避免直接传递BP引用。 - 只把逻辑部分留在父类。 - 非必要将一切资产加载。而是动态生成。 4. 软引用 - C++里的硬引用。 ![image-20241221113359775](./images/image-20241221113359775.png) - 软引用 ![image-20241221113419292](./images/image-20241221113419292.png) ![image-20241221113503578](./images/image-20241221113503578.png) 缺点是需要自己加载资源和掌握正确的时机。 5. 总结 - 应该在关键资源使用硬引用,其他资源使用软引用。 ![image-20241223110828026](./images/image-20241223110828026.png) #### 角色 基本架构: ![image-20241221114749169](./images/image-20241221114749169.png) #### 行为 不在使用传统按键 而是和 GAS结合 ![image-20241221151533288](./images/image-20241221151533288.png) ![image-20241221175926476](./images/image-20241221175926476.png) ![image-20241221175903610](./images/image-20241221175903610.png) ![image-20241221195028380](./images/image-20241221195028380.png) ![image-20241221195132260](./images/image-20241221195132260.png) ## 二、基础框架 ### 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会通过其在服务器之间传递控制。 image-20241223105950735 实现: ```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; } ``` ![image-20241223125521095](./images/image-20241223125521095.png) ### 2.4、全局设置 创建Character、Controller,GameMode的蓝图对象。 image-20241223111030721 image-20241223110622114 #### GameMode image-20241223111017498 #### Project Settings ![image-20241223111154568](./images/image-20241223111154568.png) > **总结**:项目设置可以设置默认启动的关卡和GameMode、而GameMode中可以装填Character、PlayerController等。 ### 2.5、Gameplay Tag 因为GAS围绕Tag展开的,所以推荐使用Tag。这里使用GameplayTag。 ![image-20241223125623389](./images/image-20241223125623389.png) ```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"); } ``` ## 三、用户输入 ![image-20241223123728757](./images/image-20241223123728757.png) 使用DataAsset将Tag和Input关联起来。 ### 3.1、定义用户输入和标签的关联资产 ![image-20241223125659605](./images/image-20241223125659605.png) ```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、自定义增强输入组件 ![image-20241223125740117](./images/image-20241223125740117.png) ```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再绑定回调。 #### 绑定动作资产 image-20241223130718856 image-20241223130806126 #### 角色指认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,增强输入系统里添加上下文映射,应用绑定的动作映射。转化我们之前自定义的增强。绑定回调。 ## 四、![image-20241223163516992](./images/image-20241223163516992.png)动画 ### 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的子类。 ![image-20241223135306303](./images/image-20241223135306303.png) ### 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。 image-20241223135650203 image-20241223135614904 #### 动画蓝图实现 image-20241223140443015 image-20241223140506112 判断Idle -- Jog 使用*bHasAcceleration*,Idle -- Relax 使用*bShouldEnableRelaxState*。 ## 五、GAS 开启插件 ![image-20241223141308565](./images/image-20241223141308565.png) ### 5.1、创建ASC和Set ![image-20241223160118057](./images/image-20241223160118057.png) ```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的实现类,并在蓝图里实现想要实现的逻辑。 ![image-20241223155828473](./images/image-20241223155828473.png) #### 武器Actor制作 ![image-20241223160138652](./images/image-20241223160138652.png) ```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); } ``` 子类实现类 ![image-20241223160329427](./images/image-20241223160329427.png) ![image-20241223160414388](./images/image-20241223160414388.png) ![image-20241223160505158](./images/image-20241223160505158.png) 给角色追加武器Socket image-20241223160611603 #### 完善武器GA蓝图逻辑 生成武器Actor image-20241223160816224 image-20241223160845101 追加到指定插槽(中间的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分别激活。 再创建它的派生类: ![image-20241223162644717](./images/image-20241223162644717.png) 分配之前的GA到蓝图的DataAsset上。 ![image-20241223162758522](./images/image-20241223162758522.png) #### 给角色添加DataAsset 通过软引用的方式来加载DA,DA数据会很大里面关联大量资产。 ![image-20241223163013840](./images/image-20241223163013840.png) ![image-20241223163227936](./images/image-20241223163227936.png) 只做验证不在这里使用。蓝图使用如下 ![image-20241223163449337](./images/image-20241223163449337.png) ```cpp void AAriesHeroCharacter::PossessedBy(AController* NewController) { Super::PossessedBy(NewController); if (!CharacterStartUpData.IsNull()) { if (UDataAsset_StartUpDataBase* LoadedData = CharacterStartUpData.LoadSynchronous()) { LoadedData->GiveToAbilitySystemComponent(AriesAbilitySystemComponent); } } } ``` 这里会注册GA给角色。被注册的GA会执行它的逻辑 ![image-20241223164151911](./images/image-20241223164151911.png) ## 六、能力系统 ### 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; } }; ``` ![image-20241223170112994](./images/image-20241223170112994.png) 蓝图调用: GA中追加Reginster ![image-20241223170236246](./images/image-20241223170236246.png) ### 6.2、HeroGameplayAbility 创建的子类AriesHeroGameplayAbility,UAriesHeroGameplayAbility。 ![image-20241223173913352](./images/image-20241223173913352.png) 父类GA中追加ASC。 image-20241223174001360 ```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(); } ``` image-20241223180422601 蓝图创建(GA_Hero_EquipAxe) ![image-20241223180609413](./images/image-20241223180609413.png) ### 6.3、Ability Input Action #### 拓展标签 ![image-20241223190625996](./images/image-20241223190625996.png) #### 拓展Input image-20241223191009745 它不需要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); } } ``` 追加了一个新的模板类,可以绑定两个回调。蓝图绑定 ![image-20241223193623709](./images/image-20241223193705072.png) #### 绑定输入 整个流程 ![image-20241223194403825](./images/image-20241223194403825.png) 我们需要将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); } } ``` image-20241223201448558 逗号键调试。要F8选中角色。 image-20241223201953944 image-20241223202120764 #### 回调绑定按键 AAriesHeroCharacter里添加回到用于绑定 ![image-20241223203915739](./images/image-20241223203915739.png) 实现: ![image-20241223203356790](./images/image-20241223203356790.png) 上文模板提到的绑定双回调的函数。拓展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); } ``` 蓝图里绑定按键和上下文映射 ![image-20241223204850182](./images/image-20241223204850182.png) ![image-20241223205143813](./images/image-20241223205143813.png) ![image-20241223205322823](./images/image-20241223205322823.png) ![image-20241223205442168](./images/image-20241223205442168.png) 通过 两个DataAsset关联了起来。 ### 6.4、动画响应 #### AssetTool插曲 ![image-20241224092235101](./images/image-20241224092235101.png) ![image-20241224092350074](./images/image-20241224092350074.png) #### 动画蒙太奇&插槽 image-20241224092852323 创建后的蒙太奇用命名修复工具修复。 image-20241224092837040 ![image-20241224103048018](./images/image-20241224103048018.png) image-20241224094935663 #### 升级动画蓝图 ![image-20241224101345333](./images/image-20241224101345333.png) image-20241224102650101 > 标记下自己犯的错误; > ![image-20241224104956947](./images/image-20241224104956947.png) > > 这里导致我播放的Montage 只播放了一帧。 #### 武器切换:GameplayEvent的使用 会在播放蒙太奇的时候切换武器到手上,建立动画通知来完善插槽切换。创建AnimNotify :AN_SendGameplayEventToOwner image-20241224110740484image-20241224110838813 传递的事件的行为是通过Tag完成的,方法名是:`UAbilitySystemBlueprintLibrary::SendGameplayEventToActor` ![image-20241224112510621](./images/image-20241224112510621.png) image-20241224113048708 image-20241224141513133 能力激活播放蒙太奇,蒙太奇之后绑定 *WaitGameplayEvent* 来响应动画通知事件。 #### AnimLayerInterface image-20241224144429949 image-20241224172008330 继承接口 image-20241224172305698 image-20241224172624038 image-20241224173021844 GA里设置下Tag代表武器 ![image-20241224173314881](./images/image-20241224173314881.png) 这样之后激活技能之后,人物就会僵硬切换到AnimLayer导致的。 image-20241224173625594 #### MasterAnimLayer 创建同骨骼类型为*UAriesHeroLinkedAnimLayer*的动画蓝图,并继承之前的接口。 image-20241224174718049 双击实现动画 ![image-20241224175039391](./images/image-20241224175039391.png) 取消一个维度的值输入。将BlendSpace作为可传参数。 ![image-20241224175307558](./images/image-20241224175307558.png) 因为这里不是直接的动画蓝图位置,所以可以开放一个线程安全的方式获取当前动画蓝图,拓展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()); } ``` image-20241224180320292 这里会级联一个链式方法获取更多数据。 创建真正的子类实现逻辑。 ![image-20241224180832116](./images/image-20241224180832116.png) ![image-20241224181006098](./images/image-20241224181006098.png) 之后只要指认了**BlendSpace**就可以工作了 image-20241224183335138 ![image-20241224183749732](./images/image-20241224183749732.png) image-20241224183918405 #### LinkAnimLayer 通过DataAsset关联AnimLayer和武器,创建数据库 ![image-20241224193253035](./images/image-20241224193253035.png) ```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中最佳获取武器的方法。 ![image-20241224204907470](./images/image-20241224204907470.png) ```cpp AAriesHeroWeapon* UHeroCombatComponent::GetHeroCarriedWeaponByTag(FGameplayTag InWeaponTag) const { return Cast(GetCharacterCarriedWeaponByTag(InWeaponTag)); } ``` 然后关联武器和动画。 ![image-20241224205918428](./images/image-20241224205918428.png) 补充GA里的逻辑 ![image-20241224210823657](./images/image-20241224210823657.png) > 总结: > > 这样做的好处是,通过MasterLayer,创建新的Layer,而新的Layer也只需要指认混合空间,再分配给武器就可以直接使用了。 > ![image-20241224211715178](./images/image-20241224211715178.png) > 总结: > > ​ 设计能力的一个基本流程,创建GameplayTag、创建Ability蓝图、创建蒙太奇、绑定输入、激活能力。 ### 6.5、标准流程卸除武器 ==1、拓展基础标签== ![image-20241224222307354](./images/image-20241224222307354.png)之前能力完善标签 image-20241224222711197 ==2、创建蒙太奇== ![image-20241224223945412](./images/image-20241224223945412.png) image-20241224224021685 ==3、能力GA== ![image-20241224224457773](./images/image-20241224224457773.png) ==4、Input== ![image-20241224224723224](./images/image-20241224224723224.png) ![image-20241224224746007](./images/image-20241224224746007.png) ### 6.6、切换动作映射上下文 #### 重构数据结构 在全局数据结构体集合里设置HeroAbilitySet。之后可以在武器里关联输入上下文、Ability、Anim Link等 ![image-20241224230515834](./images/image-20241224230515834.png) 完整拓展 ```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; }; ``` ![image-20241224231427021](./images/image-20241224231427021.png)创建新的UInputMappingContext ![image-20241224231645951](./images/image-20241224231645951.png) Weapon上绑定Link动画 武器 以及能力 ![image-20241224231832940](./images/image-20241224231832940.png) #### 装配武器和切换上下文 优化GA_装备里的逻辑。 image-20241225091554134 ![image-20241225110206137](./images/image-20241225110206137.png) 方法实现: ![image-20241225092902963](./images/image-20241225092902963.png) ==1-1== ![image-20241225092937090](./images/image-20241225092937090.png) ==1-2== ![image-20241225093001357](./images/image-20241225093001357.png) 这里给1是为了设置更高的优先级。 ![image-20241225093249618](./images/image-20241225093249618.png) ==1-3== UAriesAbilitySystemComponent里追加Weapon激活Weapon能力的逻辑 ![image-20241225103929077](./images/image-20241225103929077.png)实现: ```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); } } ``` image-20241225104047233 ### 6.7、卸除相关组件 当激活UnEquip的Ability时,还要取消关联Equip的AnimLayer、MappingContext、其它的Ability。 #### 重新绑定武器插槽 将武器放在背后。 ![image-20241225113957025](./images/image-20241225113957025.png) 为了保证模型统一如下设置: ![image-20241225113859609](./images/image-20241225113859609.png) 之后是解除之前Weapon的Ability。在*GA_Hero_UnEquipAxe*里也定义一个Function:`HandleUnequipWeapon` ==2-1== 解除AnimLayer image-20241225123958538 ==2-2== 解除MappingContext ![image-20241225130334840](./images/image-20241225130334840.png) ==2-3== 取消能力,升级UAriesAbilitySystemComponent 返回句柄 ![image-20241225134316964](./images/image-20241225134316964.png)实现: ![image-20241225134449858](./images/image-20241225134449858.png) 拓展代码: 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中补充实现,存储句柄以之后卸除使用: ![image-20241225134916445](./images/image-20241225134916445.png) 更新逻辑,然后在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 } ``` 蓝图里更新第三步 ![image-20241225140849279](./images/image-20241225140849279.png) 完善逻辑,释放掉Combat的GameplayTag ![image-20241225141301775](./images/image-20241225141301775.png) 动画周期可以设置动画蓝图里的*Blend Time*。 ## 七、轻攻击 拓展标签 ![image-20241225145353509](./images/image-20241225145353509.png) ### 7.1、创建GA 创建*GA_Hero_LightAttackMaster*类型为AriesHeroGameplayAbility,它有如下的标签 ![image-20241225152740088](./images/image-20241225152740088.png) 激活轻攻击期间不能卸/装武器,不能激活第二次轻攻击。修正之前的能力逻辑 ![image-20241225153623901](./images/image-20241225153623901.png) ### 7.2、处理输入 创建新的Input_Action ![image-20241225154300421](./images/image-20241225154300421.png) 创建GA子类 ![image-20241225154546663](./images/image-20241225154546663.png) ![image-20241225154730028](./images/image-20241225154730028.png) ![image-20241225155001172](./images/image-20241225155001172.png) ![image-20241225155204403](./images/image-20241225155204403.png) ![image-20241225162226384](./images/image-20241225162226384.png) 测试打印内容,成功! ![image-20241225201132169](./images/image-20241225201132169.png) ### 7.3、Combat Logic image-20241225165404618 最后一种只能在C++实现。 ![image-20241225165141430](./images/image-20241225165141430.png) ![image-20241225165942466](./images/image-20241225165942466.png) 通过Map来控制Combo。整个Combo的Logic如下: ![image-20241225173216568](./images/image-20241225173216568.png) 来到子类构建数据 ![image-20241225175840861](./images/image-20241225175840861.png) 创建蒙太奇和新的插槽,调整速率 2.2 2.0 1.0 1.9 ![image-20241225182445713](./images/image-20241225182445713.png) ![image-20241225182539197](./images/image-20241225182539197.png) 升级动画蓝图 ![image-20241225182726641](./images/image-20241225182726641.png) ## 八、重攻击 ### 8.1、基本流程 如上面一样流程: ![image-20241225201808018](./images/image-20241225201808018.png) 1. 创建一个HeroAbility,是用于HeavyAttach的。赋予基本逻辑和标签。 ![image-20241225201917214](./images/image-20241225201917214.png) 2. 创建1中的实现GA。 3. 通过武器蓝图关联**InputMappingContext**、**InputTag**、**Ability**。(武器才有的) ![image-20241225202319608](./images/image-20241225202319608.png) 4. 创建一个**InputAction**。 5. InputMappingContext里关联 InputAction。 ![image-20241225202333376](./images/image-20241225202333376.png) 6. DataAsset_InputConfig里关联InputTag 和 InputAction。(输入所关心的) ![image-20241225202357430](./images/image-20241225202357430.png) ### 8.2、重攻击Logic ![image-20241225210959733](./images/image-20241225210959733.png) ![image-20241225205720330](./images/image-20241225205720330.png) ![image-20241225210919226](./images/image-20241225210919226.png) ![image-20241225210908223](./images/image-20241225210908223.png) ### 8.3、轻重攻击融合 ![image-20241225212930543](./images/image-20241225212930543.png) 追加动作终结的系统。 #### 构建蓝图库 .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; } ``` ![image-20241225221702195](./images/image-20241225221702195.png) #### 终结 ![image-20241225222258924](./images/image-20241225222258924.png) ==轻攻击追加可以终结的标记:== ![image-20241225224909280](./images/image-20241225224909280.png) ![image-20241225223624717](./images/image-20241225223624717.png) ==重攻击触发终结== ![image-20241225224258575](./images/image-20241225224258575.png) 补充: ![image-20241225232336832](./images/image-20241225232336832.png) ## 九、增加细节效果 ### 9.1、时间膨胀 新增动画通知状态:*ANS_SlowMotion* 重写方法1: ![image-20241226091935804](./images/image-20241226091935804.png) 重写方法2 ![image-20241226092409095](./images/image-20241226092409095.png) 添加通知: ![image-20241226092327104](./images/image-20241226092327104.png) ![image-20241226092714074](./images/image-20241226092714074.png) ![image-20241226093139081](./images/image-20241226093139081.png) ### 9.2、音效 声音通知: ![image-20241226094331644](./images/image-20241226094331644.png) Ctrl + 空格唤出内容浏览器,已经有通知的自己加轨道复制粘贴。 去掉打印 ![image-20241226100007450](./images/image-20241226100007450.png) 精细化类型,换成UAriesHeroGameplayAbility ![image-20241226100125009](./images/image-20241226100125009.png) 效果就是如下,只能找到Hero的GA ![image-20241226100748815](./images/image-20241226100748815.png) [第二部分](README2.md)