系列文章目录
文章目录
前言

正常运行:

一、错误原因?
SpawnActor 生成出来的武器对象
没有正确传给后面的 Is Valid
结果后面的 Attach Actor To Component 没有基于正确的武器实例去执行
所以武器虽然生成了,但没有真正挂接到角色组件上
最终表现就是"武器生成了,但不跟随角色移动"
SpawnActor 返回的武器实例没有被正确传递到后续校验和挂接链路里,所以武器没有成功 Attach 到角色组件上。
二、解决步骤
1.角色骨骼挂载插槽并配置武器资产,调整武器位置

2.配置技能数据表

3.技能蓝图
蓝图逻辑:
Event ActivateAbility
-> SpawnActor
-> Is Valid(SpawnActor.ReturnValue)
-> Attach Actor To Component

注意:武器没跟随角色移动的问题
武器运行却不跟随原因是:如图,SpawnActor节点的输出值ReturnValue,没有连接到Is Valid节点的输入端Input Object
更准确地说,不是单纯"武器未跟随是因为没接到 Is Valid",而是:
因为 SpawnActor 返回的武器实例没有被正确传递到后续校验和挂接链路里,所以武器没有成功 Attach 到角色组件上。
Is Valid 这根线是这个问题暴露出来的关键。
4.c++部分
(1)角色能力基类:头文件
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystem/WarriorAbilitySystemComponent.h"
#include "AbilitySystem/WarriorAttributeSet.h"
#include "AbilitySystemInterface.h"
#include "DataAssets/StartUpData/DataAsset_StartUpDataBase.h"
#include "WarriorCharacterBase.generated.h"
class UWarriorAbilitysystemComponent;
class UWarriorAttributeSet;
class UDataAsset_StartUpDataBase;
UCLASS()
class GODOFWAR_DEMO_API AWarriorCharacterBase : public ACharacter, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AWarriorCharacterBase();
//获取GAS组件
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const ;
protected:
//角色控制相关
virtual void PossessedBy(AController* NewController) override;
//技能系统组件
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AbilitySystem")
UWarriorAbilitySystemComponent* WarriorAbilitySystemComponent;
//属性集
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AbilitySystem")
UWarriorAttributeSet* WarriorAttributeSet;
//角色初始化数据资产
//TSoftObjectPtr存储资源的路径 使用软引用,避免资源丢失
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "CharacterData")
TSoftObjectPtr<UDataAsset_StartUpDataBase> CharacterStartUpData;
public:
//获取技能系统组件
FORCEINLINE UWarriorAbilitySystemComponent* GetWarriorAbilitySystemComponent() const
{
return WarriorAbilitySystemComponent;
}
//获取属性集
FORCEINLINE UWarriorAttributeSet* GetWarriorAttributeSet() const
{
return WarriorAttributeSet;
}
};
角色能力基类:实现部分
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Characters/WarriorCharacterBase.h"
// Sets default values
AWarriorCharacterBase::AWarriorCharacterBase()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
//表示在游戏开始时,这个actor的tick不会被自动启用,即使bCanEverTick=true,只要我们这个bStartWithTickEnabled=false,那么这个actor也不会在游戏开始时自动开始tick
PrimaryActorTick.bStartWithTickEnabled = false;
//mesh不会被贴花影响
GetMesh()->bReceivesDecals = false;
//创建并初始化自定义技能系统组件
WarriorAbilitySystemComponent = CreateDefaultSubobject<UWarriorAbilitySystemComponent>(TEXT("WarriorAbilitySystemComponent"));
//设置技能系统组件为可复制
WarriorAbilitySystemComponent->SetIsReplicated(true);
//创建并初始化自定义属性集
WarriorAttributeSet = CreateDefaultSubobject<UWarriorAttributeSet>(TEXT("WarriorAttributeSet"));
}
// 返回技能系统组件
UAbilitySystemComponent* AWarriorCharacterBase::GetAbilitySystemComponent() const
{
return GetWarriorAbilitySystemComponent();;
}
//当控制器占据此角色时调用
void AWarriorCharacterBase::PossessedBy(AController* NewController)
{
//父类方法
Super::PossessedBy(NewController);
//检查技能系统组件是否为空
if (WarriorAbilitySystemComponent)
{
//设置技能系统组件角色信息,this-1拥有角色的Actor(一般是控制器),this-2技能系统组件在游戏中
WarriorAbilitySystemComponent->InitAbilityActorInfo(this, this);
//验证角色初始化数据是否设置
//如果没有设置,就报错,并且提示原因
ensureMsgf(!CharacterStartUpData.IsNull(), TEXT("Forgot to assign start up data to %s"), *GetName());
}
}
(2)数据资产:头文件
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "DataAsset_StartUpDataBase.generated.h"
class UWarriorGameplayAbility;
class UWarriorAbilitySystemComponent;
UCLASS()
class GODOFWAR_DEMO_API UDataAsset_StartUpDataBase : public UDataAsset
{
GENERATED_BODY()
public:
//将此数据资产中的能力赋予给能力系统组件,InASCToGive目标能力系统组件, ApplyLevel应用的能力等级
virtual void GiveToAbilitySystemComponent(UWarriorAbilitySystemComponent* InASCToGive, int32 ApplyLevel = 1);
protected:
//配置,获取时立即激活的被动能力
UPROPERTY(EditDefaultsOnly, Category = "StartUpData")
TArray<TSubclassOf<UWarriorGameplayAbility>> ActivateOnGivenAbilities;
//配置,需要手动触发的主动能力
UPROPERTY(EditDefaultsOnly, Category = "StartUpData")
TArray<TSubclassOf<UWarriorGameplayAbility>> ReactiveAbilities;
//将一组能力赋予给能力系统组件
void GrantAbilities(const TArray<TSubclassOf<UWarriorGameplayAbility>>& InAbilitiesToGive, UWarriorAbilitySystemComponent* InASCToGive, int32 ApplyLevel = 1);
};
数据资产:实现
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "DataAssets/StartUpData/DataAsset_StartUpDataBase.h"
#include "AbilitySystem/WarriorAbilitySystemComponent.h"
#include "AbilitySystem/Abilities/WarriorGameplayAbility.h"
//将启动数据赋予能力系统组件
void UDataAsset_StartUpDataBase::GiveToAbilitySystemComponent(UWarriorAbilitySystemComponent* InASCToGive, int32 ApplyLevel)
{
//确保目标能力系统组件有效
check(InASCToGive);
//赋予被动能力
GrantAbilities(ActivateOnGivenAbilities, InASCToGive, ApplyLevel);
//赋予主动能力
GrantAbilities(ReactiveAbilities, InASCToGive, ApplyLevel);
}
//将一组能力赋予能力系统组件
void UDataAsset_StartUpDataBase::GrantAbilities(const TArray<TSubclassOf<UWarriorGameplayAbility>>& InAbilitiesToGive, UWarriorAbilitySystemComponent* InASCToGive, int32 ApplyLevel)
{
//如果能力数组为空,则直接返回
if (InAbilitiesToGive.IsEmpty())
{
return;
}
//遍历能力数组
for (const TSubclassOf<UWarriorGameplayAbility>& Ability : InAbilitiesToGive)
{
if (!Ability) continue;
//基于能力类创建规格
FGameplayAbilitySpec AbilitySpec(Ability);
//设置能力来源对象
AbilitySpec.SourceObject = InASCToGive->GetAvatarActor();
//设置能力等级
AbilitySpec.Level = ApplyLevel;
//技能注册
InASCToGive->GiveAbility(AbilitySpec);
}
}
(3)角色移动和能力:头文件
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Characters/WarriorCharacterBase.h"
#include "InputActionValue.h"
#include "WarriorCharacterHero.generated.h"
class USpringArmComponent;
class UCameraComponent;
class UWarriorDataAsset_InputConfig;
struct FInputActionValue;
UCLASS()
class GODOFWAR_DEMO_API AWarriorCharacterHero : public AWarriorCharacterBase
{
GENERATED_BODY()
public:
AWarriorCharacterHero();
protected:
virtual void PossessedBy(AController* NewController) override;
virtual void BeginPlay() override;
//调用以将功能绑定到输入
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
private:
//组件
#pragma region Components
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera", meta = (AllowPrivateAccess = "true"))
USpringArmComponent* CameraBoom;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera", meta = (AllowPrivateAccess = "true"))
UCameraComponent* FollowCamera;
#pragma endregion
//输入
#pragma region Inputs
//声明一个输入配置数据资产
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "CharacterData", meta = (AllowPrivateAccess = "true"))
UWarriorDataAsset_InputConfig* InputConfigDataAsset;
////声明移动输入函数
void Input_Move(const FInputActionValue& InputActionValue);
void Input_Look(const FInputActionValue& InputActionValue);
#pragma endregion Inputs
};
(3)角色移动和能力:实现部分
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Characters/WarriorCharacterHero.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "EnhancedInputSubsystems.h"
#include "DataAssets/Input/WarriorDataAsset_InputConfig.h"
#include "Components/Input/WarriorEnhancedInputComponent.h"
#include "WarriorGameplayTags.h"
#include "AbilitySystem/WarriorAbilitySystemComponent.h"
#include "DataAssets/StartUpData/DataAsset_HeroStartUpData.h"
#include "GameFramework/PlayerController.h"
#include "Characters/WarriorCharacterBase.h"
#include "WarriorDebugHelper.h"
AWarriorCharacterHero::AWarriorCharacterHero()
{
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(GetRootComponent());
CameraBoom->TargetArmLength = 200.0f;
//弹簧臂偏移值
CameraBoom->SocketOffset = FVector(0.0f, 55.f, 65.f);
CameraBoom->bUsePawnControlRotation = true;
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
//角色转身速度
GetCharacterMovement()->RotationRate = FRotator(0.f, 500.f, 0.f);
GetCharacterMovement()->MaxWalkSpeed = 400.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
//获取输入数据资产引用
static ConstructorHelpers::FObjectFinder<UWarriorDataAsset_InputConfig> InputConfigAssetRef(
TEXT("/Game/PlayerCharacter/DA_InputConfig.DA_InputConfig")
);
if (InputConfigAssetRef.Succeeded())
{
InputConfigDataAsset = InputConfigAssetRef.Object;
}
else
{
UE_LOG(LogTemp, Error, TEXT("Failed to load DA_InputConfig from /Game/PlayCharacter/DA_InputConfig.DA_InputConfig"));
}
}
void AWarriorCharacterHero::PossessedBy(AController* NewController)
{
//先调用父类方法,处理基础的角色控制逻辑和GAS初始化
Super::PossessedBy(NewController);
//判断技能数据资产是否有效
if (!CharacterStartUpData.IsNull())
{
//将武器同步加载
if (UDataAsset_StartUpDataBase* LoadedData = CharacterStartUpData.LoadSynchronous())
{
LoadedData->GiveToAbilitySystemComponent(WarriorAbilitySystemComponent);
}
}
}
void AWarriorCharacterHero::BeginPlay()
{
Super::BeginPlay();
}
void AWarriorCharacterHero::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
//确保父类输入设置执行
Super::SetupPlayerInputComponent(PlayerInputComponent);
/*if (!InputConfigDataAsset)
{
UE_LOG(LogTemp, Error, TEXT("InputConfigDataAsset is null on %s"), *GetName());
return;
}*/
//强制验证数据资产有效性
checkf(InputConfigDataAsset, TEXT("Forgot to assign a valid data asset as input config"));
//获取本地玩家控制器引用
ULocalPlayer* LocalPlayer = GetController<APlayerController>()->GetLocalPlayer();
//获取输入子系统引用
UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(LocalPlayer);
//确保输入子系统有效
checkf(Subsystem, TEXT("EnhancedInputLocalPlayerSubsystem is null"));
//将输入映射上下文添加到增强输入子系统
Subsystem->AddMappingContext(InputConfigDataAsset->DefaultMappingContext, 0);
//输入组件转换,使用自定义的WarriorInputComponent替换基础输入组件
UWarriorEnhancedInputComponent* WarriorInputComponent = CastChecked<UWarriorEnhancedInputComponent>(PlayerInputComponent);
//绑定输入映射
WarriorInputComponent->BindNativeInputAction(InputConfigDataAsset, WarriorGameplayTags::InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move);
//绑定输入视角移动
WarriorInputComponent->BindNativeInputAction(InputConfigDataAsset, WarriorGameplayTags::InputTag_Look, ETriggerEvent::Triggered, this, &ThisClass::Input_Look);
}
//输入移动
void AWarriorCharacterHero::Input_Move(const FInputActionValue& InputActionValue)
{
const FVector2D MovementVector = InputActionValue.Get<FVector2D>();
const FRotator MovementRotation(0.f, Controller->GetControlRotation().Yaw, 0.f);
if (MovementVector.Y != 0.f)
{
//获取向前方向Forward Direction
const FVector ForwardDirection = MovementRotation.RotateVector(FVector::ForwardVector);
//添加角色向前的移动输入
AddMovementInput(ForwardDirection, MovementVector.Y);
}
if (MovementVector.X != 0.f)
{
//获取向右方向Right Direction
const FVector RightDirection = MovementRotation.RotateVector(FVector::RightVector);
//添加角色向右的移动输入
AddMovementInput(RightDirection, MovementVector.X);
}
}
//输入视角移动
void AWarriorCharacterHero::Input_Look(const FInputActionValue& InputActionValue)
{
const FVector2D LookAxisVector = InputActionValue.Get<FVector2D>();
if (LookAxisVector.X != 0.f)
{
AddControllerYawInput(LookAxisVector.X);
}
if (LookAxisVector.Y != 0.f)
{
AddControllerPitchInput(LookAxisVector.Y);
}
}