目录
效果

步骤
一、角色接触武器后直接拾取武器
在蓝图中我们可以通过如下节点让角色接触"BP_Weapon"时将其附加到角色到手上,但是我们准备用C++实现。

如下是蓝图的C++版本,可以实现同样效果

此时角色接触武器后,武器就会附加到角色手上

二、角色接触武器后按E键拾取武器
新建一个输入操作,这里命名为"IA_Equip"


打开输入映射上下文,添加一个映射

在"SlashCharacter.h"中首先声明一个输入动作对象的指针"InteractAction",用来关联输入映射"中定义的 IA_Equip。然后定义输入响应函数"Interact",当玩家触发 InteractAction 对应的输入时,虚幻引擎会自动调用这个函数。

在"SlashCharacter.cpp"中先不实现函数"Interact"具体逻辑

通过增强输入组件 执行输入动作绑定,把输入动作"InteractAction"和响应函数"Interact"关联起来

在"BP_SlashCharacter"中设置"Interact Action"为"IA_Equip"

此时我们按下E键就会执行函数"Interact",因此接下来需要实现函数"Interact"逻辑。这里逻辑是希望只有角色和Item(包括武器)发生重叠时,才能被拾取。
首先在"SlashCharacter.h"中定义一个变量"OverlappingItem",用于存储角色拾取的物品

再定义一个内联函数用于设置变量"OverlappingItem"的值

在"Item.cpp"中设置物品与角色重叠后,角色可以获取到Item,结束重叠后角色无法获取到Item

在"Weapon"中定义函数"Equip",作用是将武器的网格组件附着到目标父组件的指定插槽上


回到"SlashCharacter.cpp",具体实现"Interact"函数如下,如果拾取的物品可以转换为武器类,就把武器附加到角色的手上

此时运行游戏可以看到角色在未接触Item时,"OverlappingItem"为空

接触到Item后,"OverlappingItem"就有值了。当角色再次离开Item范围,"OverlappingItem"会再次为空。

当角色接触到Item条件下,并且Item是一个武器,此时按下E键就可以将武器拿在手中。

三、站立姿态切换到持剑站立姿态
如果要实现姿态的切换,就需要在动画蓝图中获取到此时角色是否处于持剑状态。因此我们需要在"SlashCharacter"中添加一个这种状态。首先新建一个头文件

这里命名为"CharacterTypes.h"

在"CharacterTypes.h"中定义一个一个枚举类 ECharacterState,如下图

在ASlashCharacter类中定义一个"ECharacterState"类型的变量"CharacterState",用于在角色中记录它当前的装备状态,默认为未装备武器状态

再定义一个内联函数来获取这个"CharacterState"的值

当角色按E拾取武器后,修改变量"CharacterState"的值

在"SlashAnimInstance.h"中也定义一个"CharacterState"

逐帧获取角色类中的状态变量来更新动画实例中的状态变量

打开动画蓝图"ABP_SlashCharacter",根据变量"CharacterState"来切换播放不同的动画序列。如下图,"Blend Poses"节点可以根据枚举的不同来输出不同的动作姿态,其中"Blend Time"表示姿势切换时过渡所用的时间

添加元素引脚

如下图,此时就可以根据"CharacterState"来决定使用哪一种站立姿势。

四、跑步姿态切换到持剑跑步姿态
在Mixamo中下载持剑跑步的动画资源

下载后导入UE


打开IK重定向器"RTG_XBot"

将持剑跑步动画重定向到角色Echo上


在动画蓝图中,根据变量"Character State"来输出不同的持剑跑步姿势

注意设置新动画循环播放

此时效果如文章开头所示。
代码
cpp
// Weapon.h
#pragma once
#include "CoreMinimal.h"
#include "Items/Item.h"
#include "Weapon.generated.h"
UCLASS()
class SLASH_API AWeapon : public AItem
{
GENERATED_BODY()
public:
void Equip(USceneComponent* InParent, FName InSocketName);
protected:
virtual void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) override;
virtual void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) override;
};
cpp
// Weapon.cpp
#include "Items/Weapons/Weapon.h"
#include "Characters/SlashCharacter.h"
void AWeapon::Equip(USceneComponent* InParent, FName InSocketName)
{
FAttachmentTransformRules TransformRules(EAttachmentRule::SnapToTarget, true);
ItemMesh->AttachToComponent(InParent, TransformRules, InSocketName);
}
void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
Super::OnSphereOverlap(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
}
void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
Super::OnSphereEndOverlap(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);
}
cpp
// SlashCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "CharacterTypes.h"
#include "SlashCharacter.generated.h"
UCLASS()
class SLASH_API ASlashCharacter : public ACharacter
{
GENERATED_BODY()
public:
ASlashCharacter();
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
private:
ECharacterState CharacterState = ECharacterState::ECS_Unequipped;
UPROPERTY(VisibleAnywhere)
class USpringArmComponent* SpringArm;
UPROPERTY(VisibleAnywhere)
class UCameraComponent* ViewCamera;
UPROPERTY(VisibleAnywhere)
class UGroomComponent* Hair;
UPROPERTY(VisibleAnywhere)
class UGroomComponent* Eyebrows;
UPROPERTY(VisibleInstanceOnly)
class AItem* OverlappingItem;
protected:
virtual void BeginPlay() override;
// 映射上下文
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputMappingContext* SlashContext;
// 声明4个独立的动作变量
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* MoveForwardAction; // 对应 IA_MoveForward
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* MoveRightAction; // 对应 IA_MoveRight
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* TurnAction; // 对应 IA_Turn (鼠标X)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* LookUpAction; // 对应 IA_LookUp (鼠标Y)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* JumpAction; // 对应 IA_Jump
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* InteractAction; // 对应 IA_Equip
// 声明4个对应的函数
void MoveForward(const FInputActionValue& Value);
void MoveRight(const FInputActionValue& Value);
void Turn(const FInputActionValue& Value);
void LookUp(const FInputActionValue& Value);
void Interact(const FInputActionValue& Value);
public:
FORCEINLINE void SetOverlappingItem(AItem* Item) { OverlappingItem = Item; }
FORCEINLINE ECharacterState GetCharacterState() const { return CharacterState; }
};
cpp
// SlashCharacter.cpp
#include "Characters/SlashCharacter.h"
#include "Components/InputComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "GroomComponent.h"
#include "Items/Item.h"
#include "Items/Weapons/Weapon.h"
ASlashCharacter::ASlashCharacter()
{
PrimaryActorTick.bCanEverTick = true;
// 让角色不要跟着控制器旋转(只让摄像机转,人脸朝移动方向)
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// 添加组件
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpingArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->TargetArmLength = 300.f;
ViewCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("ViewCamera"));
ViewCamera->SetupAttachment(SpringArm);
Hair = CreateDefaultSubobject<UGroomComponent>(TEXT("Hair"));
Hair->SetupAttachment(GetMesh());
Eyebrows = CreateDefaultSubobject<UGroomComponent>(TEXT("Eyebrows"));
Eyebrows->SetupAttachment(GetMesh());
}
void ASlashCharacter::BeginPlay()
{
Super::BeginPlay();
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
// 注意:SlashContext 必须在蓝图里被赋值,否则这里也会失败
if (SlashContext)
{
Subsystem->AddMappingContext(SlashContext, 0);
}
}
}
}
void ASlashCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// 1. 实现 MoveForward (只负责前后)
void ASlashCharacter::MoveForward(const FInputActionValue& Value)
{
const float MovementValue = Value.Get<float>(); // 获取浮点数
// UE_LOG(LogTemp, Warning, TEXT("MovementValue: %f"), MovementValue);
if (MovementValue != 0.f && Controller != nullptr)
{
// 找方向:基于摄像机的 Yaw
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// 获取前方向量 (X轴)
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, MovementValue);
}
}
// 2. 实现 MoveRight (只负责左右)
void ASlashCharacter::MoveRight(const FInputActionValue& Value)
{
const float MovementValue = Value.Get<float>();
if (MovementValue != 0.f && Controller != nullptr)
{
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// 获取右方向量 (Y轴)
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, MovementValue);
}
}
// 3. 实现 Turn (鼠标左右动 -> 角色左右转)
void ASlashCharacter::Turn(const FInputActionValue& Value)
{
const float TurnValue = Value.Get<float>();
AddControllerYawInput(TurnValue);
}
// 4. 实现 LookUp (鼠标上下动 -> 摄像机上下看)
void ASlashCharacter::LookUp(const FInputActionValue& Value)
{
const float LookUpValue = Value.Get<float>();
AddControllerPitchInput(LookUpValue);
}
// 5. 实现拾取功能
void ASlashCharacter::Interact(const FInputActionValue& Value)
{
AWeapon* OverlappingWeapon = Cast<AWeapon>(OverlappingItem);
if (OverlappingWeapon)
{
OverlappingWeapon->Equip(GetMesh(), FName("RightHandSocket"));
CharacterState = ECharacterState::ECS_EquippedOneHandWeapon;
}
}
void ASlashCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
// 分别绑定4个动作
if (MoveForwardAction)
EnhancedInputComponent->BindAction(MoveForwardAction, ETriggerEvent::Triggered, this, &ASlashCharacter::MoveForward);
if (MoveRightAction)
EnhancedInputComponent->BindAction(MoveRightAction, ETriggerEvent::Triggered, this, &ASlashCharacter::MoveRight);
if (TurnAction)
EnhancedInputComponent->BindAction(TurnAction, ETriggerEvent::Triggered, this, &ASlashCharacter::Turn);
if (LookUpAction)
EnhancedInputComponent->BindAction(LookUpAction, ETriggerEvent::Triggered, this, &ASlashCharacter::LookUp);
if (JumpAction)
{
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
}
if (InteractAction)
{
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Triggered, this, &ASlashCharacter::Interact);
}
}
}
cpp
// Item.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"
UCLASS()
class SLASH_API AItem : public AActor
{
GENERATED_BODY()
public:
AItem();
virtual void Tick(float DeltaTime) override;
protected:
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Sine Parameters")
float Amplitude = 30.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sine Parameters")
float RunningTime;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FVector InitialLocation;
UFUNCTION(BlueprintPure)
float TransformedSin();
UFUNCTION()
virtual void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
virtual void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UStaticMeshComponent* ItemMesh;
private:
UPROPERTY(VisibleAnywhere)
class USphereComponent* Sphere;
};
cpp
// Item.cpp
#include "Items/Item.h"
#include "Slash/DebugMacros.h"
#include "Components/SphereComponent.h"
#include "Characters/SlashCharacter.h"
AItem::AItem()
{
PrimaryActorTick.bCanEverTick = true;
ItemMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ItemMeshComponent"));
RootComponent = ItemMesh;
Sphere = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere"));
Sphere->SetupAttachment(RootComponent);
}
void AItem::BeginPlay()
{
Super::BeginPlay();
InitialLocation = GetActorLocation();
Sphere->OnComponentBeginOverlap.AddDynamic(this, &AItem::OnSphereOverlap);
Sphere->OnComponentEndOverlap.AddDynamic(this, &AItem::OnSphereEndOverlap);
}
void AItem::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
RunningTime += DeltaTime;
}
float AItem::TransformedSin()
{
return InitialLocation.Z + Amplitude * FMath::Sin(RunningTime * 5.f);
}
void AItem::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
ASlashCharacter* SlashCharacter = Cast<ASlashCharacter>(OtherActor);
if (SlashCharacter)
{
SlashCharacter->SetOverlappingItem(this);
}
}
void AItem::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
ASlashCharacter* SlashCharacter = Cast<ASlashCharacter>(OtherActor);
if (SlashCharacter)
{
SlashCharacter->SetOverlappingItem(nullptr);
}
}
cpp
// CharacterTypes.h
#pragma once
UENUM(BlueprintType)
enum class ECharacterState : uint8
{
ECS_Unequipped UMETA(DisplayName = "Unequipped"),
ECS_EquippedOneHandWeapon UMETA(DisplayName = "EquippedOneHandWeapon"),
ECS_EquippedTwoHandWeapon UMETA(DisplayName = "EquippedTwoHandWeapon")
};
cpp
// SlashAnimInstance.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "CharacterTypes.h"
#include "SlashAnimInstance.generated.h"
/**
*
*/
UCLASS()
class SLASH_API USlashAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
virtual void NativeInitializeAnimation() override;
virtual void NativeUpdateAnimation(float DeltaTime) override;
UPROPERTY(BlueprintReadOnly)
class ASlashCharacter* SlashCharacter;
UPROPERTY(BlueprintReadOnly, Category = Movement)
class UCharacterMovementComponent* SlashCharacterMovement;
UPROPERTY(BlueprintReadOnly, Category = Movement)
float GroundSpeed;
UPROPERTY(BlueprintReadOnly, Category = Movement)
bool IsFalling;
UPROPERTY(BlueprintReadOnly, Category = "Movement | Character State")
ECharacterState CharacterState;
};
cpp
// SlashAnimInstance.cpp
#include "Characters/SlashAnimInstance.h"
#include "Characters/SlashCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"
void USlashAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation(); //调用父类的NativeInitializeAnimation函数
SlashCharacter = Cast<ASlashCharacter>(TryGetPawnOwner());
if (SlashCharacter)
{
SlashCharacterMovement = SlashCharacter->GetCharacterMovement();
}
}
void USlashAnimInstance::NativeUpdateAnimation(float DeltaTime)
{
Super::NativeUpdateAnimation(DeltaTime);
if (SlashCharacterMovement)
{
GroundSpeed = UKismetMathLibrary::VSizeXY(SlashCharacterMovement->Velocity);
IsFalling = SlashCharacterMovement->IsFalling();
CharacterState = SlashCharacter->GetCharacterState();
}
}