【UE5.3 C++】ARPG游戏 06-拾取武器
目录
效果

步骤
一、角色接触武器后直接拾取武器
在蓝图中我们可以通过如下节点让角色接触“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”来输出不同的持剑跑步姿势

注意设置新动画循环播放

此时效果如文章开头所示。
代码
// 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; }; // 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); } // 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; } };// 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); } } }// 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; };// 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); } }// 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") };// 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; };// 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(); } }