02 英雄角色与基础框架

上一章:[[01-项目基础]] 返回:[[00-课程索引]] 下一章:[[03-近战连招系统]]

[!info] 阅读说明
每节包含课程对应的实际类/函数或资产职责。Content/*.uasset 为二进制,笔记只记录提交历史可以确认的资产关系,不虚构蓝图内部节点。

本章最终调用链

1
2
3
4
5
6
7
8
9
BP_BaseGameMode
→ BP_HeroCharacter / BP_HeroController
→ Camera + Enhanced Input
→ UWarriorCharacterAnimInstance / UWarriorHeroAnimInstance
→ ASC.InitAbilityActorInfo
→ CharacterStartUpData.LoadSynchronous
→ GiveAbility
→ OnGiven Ability 自动激活
→ 生成并注册 Hero Weapon

P5 Set Up Hero Character Section Overview(00:46)

类型:章节概览,无独立代码提交。

  • 类结构:GameMode、BaseCharacter、HeroCharacter、HeroController。
  • 输入:Gameplay Tag 驱动的 Enhanced Input。
  • 动画:通用 AnimInstance + Hero AnimInstance + AnimBP。
  • GAS:ASC、AttributeSet、Gameplay Ability 激活策略。
  • 数据:Startup Data 软引用与武器配置。

P6 Basic Class Structure(07:10)

提交4705f83 BaseClassesCreated

类层级

1
2
3
AGameModeBase → AWarriorBaseGameMode → BP_BaseGameMode
ACharacter → AWarriorBaseCharacter → AWarriorHeroCharacter → BP_HeroCharacter
APlayerController → AWarriorHeroController → BP_HeroController

关键文件:

  • Public/Characters/WarriorBaseCharacter.h
  • Public/Characters/WarriorHeroCharacter.h
  • Public/Controllers/WarriorHeroController.h
  • Public/GameModes/WarriorBaseGameMode.h

角色构造中的基础设置:

1
2
3
4
5
6
PrimaryActorTick.bCanEverTick = false;
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.f);
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
GetCharacterMovement()->bOrientRotationToMovement = true;

DefaultEngine.ini 通过 GlobalDefaultGameMode 指向 BP_BaseGameMode,再由 GameMode 蓝图选择 Hero Pawn 和 Hero Controller。

设计重点:公共能力放 Base,玩家专属行为放 Hero;蓝图负责选择 Mesh、AnimBP 和默认类。


P7 Debug Helper(04:54)

提交ead75ab Debug helper created

文件:Public/WarriorDebugHelper.h

1
2
3
4
5
6
7
8
9
10
11
namespace Debug
{
static void Print(const FString& Msg,
const FColor& Color = FColor::MakeRandomColor(),
int32 InKey = -1);

static void Print(const FString& FloatTitle,
float FloatValueToPrint,
int32 InKey = -1,
const FColor& Color = FColor::MakeRandomColor());
}

调用链:

1
2
3
Debug::Print
├─ GEngine->AddOnScreenDebugMessage
└─ UE_LOG(LogTemp, Warning, ...)
  • InKey=-1 每次新增消息;固定 Key 可刷新同一条消息。
  • 屏幕输出用于即时观察,日志用于保存和检索,断点用于检查调用栈与内存。
  • 头文件 static 函数具有内部链接;适合简单工具,但大型项目更推荐独立 Log Category。

P8 Set Up Hero Camera(09:52)

提交22978e6 CameraDone

字段:

1
2
USpringArmComponent* CameraBoom;
UCameraComponent* FollowCamera;

构造逻辑:

1
2
3
4
5
6
7
8
9
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(GetRootComponent());
CameraBoom->TargetArmLength = 200.f;
CameraBoom->SocketOffset = FVector(0.f, 55.f, 65.f);
CameraBoom->bUsePawnControlRotation = true;

FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;

旋转职责:

1
2
Controller Rotation → SpringArm → Camera
Movement Input → CharacterMovement.bOrientRotationToMovement → Character Yaw

因此相机跟随控制器,角色不直接跟随控制器 Yaw,而是自然朝向移动方向。


P9 Native Gameplay Tags(09:37)

提交a74dc12 NativeTagsDone

文件:WarriorGameplayTags.h/.cpp

1
2
3
4
5
namespace WarriorGameplayTags
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_Move);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_Look);
}
1
2
UE_DEFINE_GAMEPLAY_TAG(InputTag_Move, "InputTag.Move");
UE_DEFINE_GAMEPLAY_TAG(InputTag_Look, "InputTag.Look");
  • C++ 标识符和 Tag 字符串是两层概念。
  • MatchesTag 支持父子层级;MatchesTagExact/HasTagExact 要求精确。
  • GameplayTags 必须进入模块依赖。

[!warning] UE_DECLARE_GAMEPLAY_TAG_EXTERN 的导出写法要以当前 UE5.4 宏定义与模块编译结果为准。声明、定义的命名空间和符号名必须完全一致;链接错误首先检查 .cpp 是否编入当前模块,而不是只修改 Tag 字符串。


P10 Input Config Data Asset(10:30)

提交f5b4a2a InputConfigDone

课程公开最终源码原文:

1
2
3
4
5
6
7
8
9
10
11
USTRUCT(BlueprintType)
struct FWarriorInputActionConfig
{
GENERATED_BODY()

UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(Categories="InputTag"))
FGameplayTag InputTag;

UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UInputAction* InputAction;
};

建议修正版应显式写成 UInputAction* InputAction = nullptr;,否则 UE 结构体反射检查可能报告对象属性未初始化;用户本地工程目前就存在过这一日志错误。

1
2
3
4
5
6
7
8
UCLASS()
class UDataAsset_InputConfig : public UDataAsset
{
UInputMappingContext* DefaultMappingContext;
TArray<FWarriorInputActionConfig> NativeInputActions;

UInputAction* FindNativeInputActionByTag(const FGameplayTag& InInputTag) const;
};

查找算法遍历 NativeInputActions,同时检查 Tag 相等和 Action 非空。资产配置:

1
2
3
4
5
DA_InputConfig
├─ DefaultMappingContext = IMC_Default
└─ NativeInputActions
├─ InputTag.Move → IA_Move
└─ InputTag.Look → IA_Look

[!bug] 用户当前工程中的 InputAction 若未初始化为 nullptr,UE 会产生 ObjectProperty ... is not initialized properly。结构体对象指针必须显式默认初始化。


P11 Custom Enhanced Input Component(07:24)

提交69488a7 Custom input component done

1
2
UCLASS()
class UWarriorInputComponent : public UEnhancedInputComponent

核心模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<class UserObject, typename CallbackFunc>
void BindNativeInputAction(const UDataAsset_InputConfig* InInputConfig,
const FGameplayTag& InInputTag,
ETriggerEvent TriggerEvent,
UserObject* ContextObject,
CallbackFunc Func)
{
checkf(InInputConfig, TEXT("Input config data asset is null"));

if (UInputAction* FoundAction = InInputConfig->FindNativeInputActionByTag(InInputTag))
{
BindAction(FoundAction, TriggerEvent, ContextObject, Func);
}
}

DefaultInput.ini

1
2
DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput
DefaultInputComponentClass=/Script/Warrior.WarriorInputComponent

这是后续 CastChecked<UWarriorInputComponent> 成立的前提。


P12 Binding Input(15:43)

提交ef005ac Input binding done

SetupPlayerInputComponent

1
2
3
4
5
6
检查 InputConfigDataAsset
→ Controller 获取 LocalPlayer
→ EnhancedInputLocalPlayerSubsystem
→ AddMappingContext(DefaultMappingContext, 0)
→ CastChecked<UWarriorInputComponent>
→ 按 Tag 绑定 Move / Look

移动实现只采用控制器 Yaw:

1
2
3
4
5
6
const FVector2D InputVector = InputActionValue.Get<FVector2D>();
const FRotator MovementRotation(0.f, Controller->GetControlRotation().Yaw, 0.f);
const FVector ForwardDirection = MovementRotation.RotateVector(FVector::ForwardVector);
const FVector RightDirection = MovementRotation.RotateVector(FVector::RightVector);
AddMovementInput(ForwardDirection, InputVector.Y);
AddMovementInput(RightDirection, InputVector.X);

视角:

1
2
AddControllerYawInput(InputVector.X);
AddControllerPitchInput(InputVector.Y);

[!warning] 当前用户工程应对 ControllerLocalPlayerInputConfigDataAsset 做空值保护;课程代码大量使用 check/checkf,适合开发期暴露配置错误,但 Shipping 行为和崩溃策略需要自行设计。


P13 Character Anim Instance(12:57)

提交8653e2b Animinstances done

继承:

1
2
3
4
UAnimInstance
└─ UWarriorBaseAnimInstance
└─ UWarriorCharacterAnimInstance
└─ UWarriorHeroAnimInstance

初始化缓存:

1
2
3
4
5
OwningCharacter = Cast<AWarriorBaseCharacter>(TryGetPawnOwner());
if (OwningCharacter)
{
OwningMovementComponent = OwningCharacter->GetCharacterMovement();
}

线程安全更新:

1
2
GroundSpeed = OwningCharacter->GetVelocity().Size2D();
bHasAcceleration = OwningMovementComponent->GetCurrentAcceleration().SizeSquared2D() > 0.f;
  • 初始化阶段缓存 UObject 指针。
  • NativeThreadSafeUpdateAnimation 只读取可安全访问的数据,不生成 Actor、不修改世界状态。
  • 最终源码后续又加入 LocomotionDirection,但它属于敌人 Strafing 章节。

P14 Hero AnimBP(04:42)

提交7fd5959 HeroAnimBP done;纯资产课。

资产:

  • ABP_Hero
  • BS_UnarmedLocomotion
  • BP_HeroCharacter

数据流:

1
2
3
4
5
Character Velocity/Acceleration
→ UWarriorHeroAnimInstance
→ GroundSpeed / bHasAcceleration
→ ABP_Hero 状态机
→ BS_UnarmedLocomotion

AnimBP 的父类应为 UWarriorHeroAnimInstance,Skeletal Mesh 的 Anim Class 指向 ABP_Hero


P15 Hero Anim Instance(09:31)

提交e7b741d Relax state done

课程公开最终源码保留了两处历史拼写:

1
2
3
4
AWarriorHeroCharacter* OwningHeroCharacter;
bool bShouldEnterRelaxState;
float EnterRelaxtStateThreshold = 5.f;
float IdleElpasedTime;

对应更新逻辑:

1
2
3
4
5
6
7
8
9
10
if (bHasAcceleration)
{
IdleElpasedTime = 0.f;
bShouldEnterRelaxState = false;
}
else
{
IdleElpasedTime += DeltaSeconds;
bShouldEnterRelaxState = IdleElpasedTime >= EnterRelaxtStateThreshold;
}

用户本地工程已规范化为 EnterRelaxStateThresholdIdleElapsedTime = 0.f,这是推荐修正版,不是课程仓库原始拼写。

  • 必须先调用 Super::NativeThreadSafeUpdateAnimation,确保 bHasAcceleration 已更新。
  • 移动后立即重置计时和 Relax 状态。
  • 原课程仓库中存在 EnterRelaxtStateThresholdIdleElpasedTime 的历史拼写;用户工程已改为规范名称。

P16 Gameplay Ability System(17:01)

提交959f8cb GAS done

新类:

1
2
UAbilitySystemComponent → UWarriorAbilitySystemComponent
UAttributeSet → UWarriorAttributeSet

基础角色:

1
class AWarriorBaseCharacter : public ACharacter, public IAbilitySystemInterface
1
2
WarriorAbilitySystemComponent = CreateDefaultSubobject<UWarriorAbilitySystemComponent>(...);
WarriorAttributeSet = CreateDefaultSubobject<UWarriorAttributeSet>(...);

初始化:

1
2
3
4
5
void AWarriorBaseCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
WarriorAbilitySystemComponent->InitAbilityActorInfo(this, this);
}
1
2
Owner Actor = Character
Avatar Actor = Character
  • Owner 持有能力系统状态。
  • Avatar 是能力实际作用和表现的实体。
  • 本课程 ASC 位于 Character,因此二者相同;若迁移到 PlayerState,二者会不同。

[!warning] GameplayAbilities 应作为直接模块依赖声明。只依赖 .uproject 的 AdditionalDependencies 不如 Build.cs 明确可靠。


P17 Ability Activation Policy(11:08)

提交d871f47 Activation Policy done

1
2
3
4
5
6
UENUM(BlueprintType)
enum class EWarriorAbilityActivationPolicy : uint8
{
OnTriggered,
OnGiven
};

UWarriorGameplayAbility 覆盖 OnGiveAbility

1
2
3
4
5
if (AbilityActivationPolicy == EWarriorAbilityActivationPolicy::OnGiven &&
ActorInfo && !Spec.IsActive())
{
ActorInfo->AbilitySystemComponent->TryActivateAbility(Spec.Handle);
}

OnGiven 能力结束后清除自身 Spec:

1
GiveAbility → OnGiveAbility → TryActivateAbility → EndAbility → ClearAbility

适合一次性初始化能力,例如出生时生成武器。OnTriggered 能力保留在 ASC,等待输入/事件激活。


P18 Set Up Weapon Class(08:49)

提交cff1059 Weapon class created

1
2
3
AActor
└─ AWarriorWeaponBase
└─ AWarriorHeroWeapon

组件:

1
2
UStaticMeshComponent* WeaponMesh;
UBoxComponent* WeaponCollisionBox;

构造:

1
2
3
4
5
6
PrimaryActorTick.bCanEverTick = false;
WeaponMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WeaponMesh"));
SetRootComponent(WeaponMesh);
WeaponCollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("WeaponCollisionBox"));
WeaponCollisionBox->SetupAttachment(GetRootComponent());
WeaponCollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);

本节只搭 Weapon Actor 骨架。命中委托、Overlap 和伤害逻辑属于后续 P58-P60。


P19 Set Up Hero Weapon(08:34)

提交52b3a04 Set up hero weapon done;主要是蓝图/资产配置。

资产:

  • BP_HeroWeaponBase
  • BP_HeroAxe
  • GA_Shared_SpawnWeapon
  • GA_Hero_SpawnAxe

职责:

1
2
3
4
GA_Hero_SpawnAxe
→ Spawn BP_HeroAxe
→ 设置 Owner/Instigator
→ Attach 到 Hero SkeletalMesh Socket

武器碰撞默认关闭;Socket 和相对 Transform 在 Skeleton/蓝图中配置,C++ 不硬编码具体美术偏移。


P20 Start Up Data(12:15)

提交依据:启动数据类型与 P21 的同步加载在 d1883dd Sync loading done 中共同落地;B站将概念拆成两集。

类型:

1
2
3
UDataAsset
└─ UDataAsset_StartUpDataBase
└─ UDataAsset_HeroStartUpData

核心字段:

1
2
3
4
5
// 授予后按 OnGiven 策略自动执行,例如生成武器
TArray<TSubclassOf<UWarriorGameplayAbility>> ActivateOnGivenAbilities;

// 常驻等待事件/Tag 触发,例如后续 HitReact、Death
TArray<TSubclassOf<UWarriorGameplayAbility>> ReactiveAbilities;

GiveToAbilitySystemComponent 会依次调用 GrantAbilities 授予这两组能力;后续受击与死亡链依赖 ReactiveAbilities,不能只配置第一组。

核心函数:

1
2
3
4
5
6
virtual void GiveToAbilitySystemComponent(UWarriorAbilitySystemComponent* InASCToGive,
int32 ApplyLevel = 1);

void GrantAbilities(const TArray<TSubclassOf<UWarriorGameplayAbility>>& InAbilitiesToGive,
UWarriorAbilitySystemComponent* InASCToGive,
int32 ApplyLevel = 1);

授予:

1
2
3
4
FGameplayAbilitySpec AbilitySpec(Ability);
AbilitySpec.SourceObject = InASCToGive->GetAvatarActor();
AbilitySpec.Level = ApplyLevel;
InASCToGive->GiveAbility(AbilitySpec);

DA_Hero 配置出生时的 OnGiven 能力,例如生成斧头。


P21 Synchronous Loading(13:10)

提交d1883dd Sync loading done

角色字段:

1
TSoftObjectPtr<UDataAsset_StartUpDataBase> CharacterStartUpData;

英雄初始化:

1
2
3
4
5
6
7
if (!CharacterStartUpData.IsNull())
{
if (UDataAsset_StartUpDataBase* LoadedData = CharacterStartUpData.LoadSynchronous())
{
LoadedData->GiveToAbilitySystemComponent(WarriorAbilitySystemComponent, 1);
}
}

完整链:

1
2
3
4
5
6
7
8
9
Controller Possess Hero
→ Base::PossessedBy
→ ASC.InitAbilityActorInfo
→ Hero::PossessedBy
→ CharacterStartUpData.LoadSynchronous
→ DA_Hero.GiveToAbilitySystemComponent
→ GiveAbility
→ OnGiven Policy 自动激活
→ GA_Hero_SpawnAxe

同步加载简单、顺序明确,但会阻塞游戏线程。单个英雄出生数据可以接受;大量敌人生成时课程后续改用异步加载。


P22 Section Wrap Up(01:42)

类型:总结课,无独立提交。

本章完成:

1
2
3
4
5
6
7
8
类层级 + 相机
→ Tag 驱动输入
→ 动画数据层
→ GAS ActorInfo
→ Ability 激活策略
→ Weapon Actor
→ Startup Data
→ 同步加载与启动能力授予

尚未完成:武器注册、武器能力授予/撤销、轻重攻击连招、实际伤害。

本章源码检查清单

  • FWarriorInputActionConfig::InputAction 初始化为 nullptr
  • 动画实例初始化和更新调用 Super
  • ASC 在授予能力前执行 InitAbilityActorInfo
  • GameplayAbilities 作为直接模块依赖。
  • 非 Editor 构建中不要调用 AActor::GetActorLabel()
  • Startup Data 只在正确生命周期加载一次。

本文从 LearnByCompany 原始文档 自动同步。