# 第9章---自定义输入框架搭建以及Ability初识

自定义输入框架思路:系统通过Tag判断用户输入,而不是EnhancedInput告诉系统玩家输入,EnhancedInput接受输入,传入Tag告诉系统

文件结构

Source

  • Private

    • AbilitySystem
      • Data
        • AttributeInfo.cpp
      • ModMagCalc
        • MMC_MaxHealth.cpp
        • MMC_MaxMana.cpp
      • RPGAbilitySystemComponent.cpp
      • RPGAbilitySystemLibrary.cpp
      • RPGAttributeSet.cpp
    • Character
      • PGGameCharacterBase.cpp
      • RPGGameEnemy.cpp
      • RPGGamePlayerCharacter.cpp
    • Game
      • RPGGameModeBase.cpp
    • Input
      • RPGInputComponent.cpp
      • RPGInputConfig.cpp
    • Interaction
      • EnemyInterface.cpp
      • CombatInterface.cpp
    • Player
      • RPGPlayerController.cpp
      • RPGPlayerState.cpp
    • Actor
      • RPGEffectActor.cpp
    • UI
      • HUD
        • RPGHUD.cpp
      • WidgetController
        • OverlayWidgetController.cpp
        • AttributeMenuWidgetController.cpp
        • RPGWidgetController.cpp
      • Widgets
        • RPGUserWidget.cpp
    • RPGAssetManager.cpp
    • RPGGameplayTags.cpp
  • Public

    • AbilitySystem

      • Data
        • AttributeInfo.h
      • ModMagCalc
        • MMC_MaxHealth.h
        • MMC_MaxMana.h
      • RPGAbilitySystemComponent.h
      • RPGAbilitySystemLibrary.h
      • RPGAttributeSet.h
    • Character

      • RPGGameCharacterBase.h
      • RPGGameEnemy.h
      • RPGGamePlayerCharacter.h
    • Game

      • RPGGameModeBase.h
    • Input

      • RPGInputComponent.h
      • RPGInputConfig.h
    • Interaction

      • EnemyInterface.h
      • CombatInterface.h
    • Player

      • RPGPlayerController.h
      • RPGPlayerState.h
    • Actor

      • RPGEffectActor.h
    • UI

      • HUD
        • RPGHUD.h
      • WidgetController
        • OverlayWidgetController.h
        • AttributeMenuWidgetController.h
        • RPGWidgetController.h
      • Widgets
        • RPGUserWidget.h
    • RPGAssetManager.h

    • RPGGameplayTags.h

文件概述

RPGAbilitySystemComponent

.h文件
// Copyright KimiLiu

#pragma once

#include "CoreMinimal.h"
#include "AbilitySystemComponent.h"
#include "RPGAbilitySystemComponent.generated.h"

DECLARE_MULTICAST_DELEGATE_OneParam(FEffectAssetTags, const FGameplayTagContainer&/*AssetTags*/);

/**
 * Ability System Component 拥有两个Owner: Owner Actor 以及Avatar Actor,这两个Actor有可能相同,有可能不相同
 *  Owner Actor 指的是实例化组件的Actor
 *  Avatar Actor 指的是ASC实际上服务的Actor,例如
 *     - RPGGameEnemy 内的ASC的 Owner Actor 和 Avatar Actor 都是 RPGGameEnemy 这个 Actor 类自身。因为ASC由 RPGGameEnemy 实例
 *        化,同时服务于 RPGGameEnemy
 *     - RPGGamePlayerCharacter 内的ASC的 Owner Actor 是 RPGPlayerState,而 Avatar Actor 是RPGGamePlayerCharacter。因为
 *        ASC由RPGPlayerState实例化,但是ASC服务于PRGGamePlayerCharacter
 */
UCLASS()
class AURA_API URPGAbilitySystemComponent : public UAbilitySystemComponent
{
    GENERATED_BODY()

public:
    // ASC初始化完成(设置了实际所有者以及逻辑所有者)
    void AbilityActorInfoSet();

    // 委托,用来处理捡起血瓶或水晶时显示UI用
    FEffectAssetTags EffectAssetTags;

    // 给ASC增加Abilities
    void AddCharacterAbilities(const TArray<TSubclassOf<UGameplayAbility>>& StartupAbilities);

    // 供PlayerController调用, Held时尝试激活拥有的Ability,通过Tag来调用指定的Ability
    void AbilityInputTagHeld(const FGameplayTag& InputTag);
    void AbilityInputTagReleased(const FGameplayTag& InputTag);

protected:
    // 当有任何GE在自身上被应用时会触发委托调用该函数,在客户端也会被调用
    UFUNCTION(Client, Reliable)
    void ClientEffectApplied(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayEffectSpec& GameplayEffectSpec, FActiveGameplayEffectHandle ActiveGameplayEffectHandle);
    
};
.cpp文件
// Copyright KimiLiu


#include "AbilitySytstem/RPGAbilitySystemComponent.h"

#include "RPGGameplayTags.h"
#include "AbilitySytstem/Abilities/RPGGameplayAbility.h"

void URPGAbilitySystemComponent::AbilityActorInfoSet()
{
    // 绑定委托
    OnGameplayEffectAppliedDelegateToSelf.AddUObject(this, &URPGAbilitySystemComponent::ClientEffectApplied);

    const FRPGGameplayTags& GameplayTags = FRPGGameplayTags::Get();
    //GameplayTags.Attributes_Secondary_Armor.ToString()
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Orange, FString::Printf(TEXT("Tag: %s"), *GameplayTags.Attributes_Secondary_Armor.ToString()));
}

// 给Character增加Ability
void URPGAbilitySystemComponent::AddCharacterAbilities(const TArray<TSubclassOf<UGameplayAbility>>& StartupAbilities)
{
    for (const TSubclassOf<UGameplayAbility> AbilityClass : StartupAbilities)
    {
       FGameplayAbilitySpec AbilitySpec = FGameplayAbilitySpec(AbilityClass, 1);
       if (const URPGGameplayAbility* RPGAbility = Cast<URPGGameplayAbility>(AbilitySpec.Ability))
       {
          AbilitySpec.DynamicAbilityTags.AddTag(RPGAbility->StartupInputTag);
          GiveAbility(AbilitySpec);
       }
    }
}

void URPGAbilitySystemComponent::AbilityInputTagHeld(const FGameplayTag& InputTag)
{
    if (!InputTag.IsValid()) return;

    // 获取所有能够激活的Abilities,找到符合输入Tag的那个Ability,激活Ability
    for (FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
    {
       if (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))
       {
          AbilitySpecInputPressed(AbilitySpec);
          if (!AbilitySpec.IsActive())
          {
             TryActivateAbility(AbilitySpec.Handle);
          }
       }
    }
}

void URPGAbilitySystemComponent::AbilityInputTagReleased(const FGameplayTag& InputTag)
{
    if (!InputTag.IsValid()) return;

    // 获取所有能够激活的Abilities,找到符合输入Tag的那个Ability,调用AbilitySpecInputReleased
    for (FGameplayAbilitySpec& AbilitySpec : GetActivatableAbilities())
    {
       if (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag))
       {
          AbilitySpecInputReleased(AbilitySpec);
       }
    }
}

void URPGAbilitySystemComponent::ClientEffectApplied_Implementation(UAbilitySystemComponent* AbilitySystemComponent,
                                               const FGameplayEffectSpec& GameplayEffectSpec, FActiveGameplayEffectHandle ActiveGameplayEffectHandle)
{
    //GEngine->AddOnScreenDebugMessage(1, 5.f, FColor::Red, FString("Effect Applied"));
    
    FGameplayTagContainer TagContainer;
    GameplayEffectSpec.GetAllAssetTags(TagContainer);

    // 广播
    EffectAssetTags.Broadcast(TagContainer);
    

    
}

RPGInputComponent

.h文件
// Copyright KimiLiu

#pragma once

#include "CoreMinimal.h"
#include "EnhancedInputComponent.h"
#include "RPGInputConfig.h"
#include "RPGInputComponent.generated.h"

/**
 * 
 */
UCLASS()
class AURA_API URPGInputComponent : public UEnhancedInputComponent
{
    GENERATED_BODY()
public:

    // 模板函数,可以接受函数或函数指针
    // 自定义输入绑定函数, 将输入AS内的所有
    template<class UserClass, typename PressedFuncType, typename ReleasedFuncType, typename HeldFuncType>
    void BindAbilityActions(const URPGInputConfig* InputConfig, UserClass* Object, PressedFuncType PressedFunc, ReleasedFuncType ReleasedFunc, HeldFuncType HeldFunc);
};

// 给一个IA绑定3个函数
template <class UserClass, typename PressedFuncType, typename ReleasedFuncType, typename HeldFuncType>
void URPGInputComponent::BindAbilityActions(const URPGInputConfig* InputConfig, UserClass* Object,
    PressedFuncType PressedFunc, ReleasedFuncType ReleasedFunc, HeldFuncType HeldFunc)
{
    check(InputConfig);

    for (const FRPGInputAction& Action : InputConfig->AbilityInputActions)
    {
       if (Action.InputAction && Action.InputTag.IsValid())
       {
          if (PressedFunc)
          {
             BindAction(Action.InputAction, ETriggerEvent::Started, Object, PressedFunc, Action.InputTag);
          }
          if (ReleasedFunc)
          {
             BindAction(Action.InputAction, ETriggerEvent::Completed, Object, ReleasedFunc, Action.InputTag);
          }
          if (HeldFunc)
          {
             BindAction(Action.InputAction, ETriggerEvent::Triggered, Object, HeldFunc, Action.InputTag);
          }
          
       }
    }
}
.cpp文件

CPP文件为空

RPGInputConfig

.h文件
// Copyright KimiLiu

#pragma once

#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Engine/DataAsset.h"
#include "RPGInputConfig.generated.h"

class UInputAction;

USTRUCT(BlueprintType)
struct FRPGInputAction
{
    GENERATED_BODY()

    UPROPERTY(EditDefaultsOnly)
    const UInputAction* InputAction = nullptr;

    UPROPERTY(EditDefaultsOnly)
    FGameplayTag InputTag = FGameplayTag();
};

/**
 * 
 */
UCLASS()
class AURA_API URPGInputConfig : public UDataAsset
{
    GENERATED_BODY()

public:

    const UInputAction* FindAbilityInputActionForTag(const FGameplayTag& InputTag, bool bLogNotFound = false) const;
    
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
    TArray<FRPGInputAction> AbilityInputActions;
};
.cpp文件
// Copyright KimiLiu


#include "Input/RPGInputConfig.h"

const UInputAction* URPGInputConfig::FindAbilityInputActionForTag(const FGameplayTag& InputTag, bool bLogNotFound) const
{
    for (const FRPGInputAction& InputAction : AbilityInputActions)
    {
       if (InputAction.InputAction && InputAction.InputTag.MatchesTag(InputTag))
       {
          return InputAction.InputAction;
       }
    }
    if (bLogNotFound)
    {
       UE_LOG(LogTemp, Error,
          TEXT("Can't find Info for InputAction [%s] on InputConfig [%s]."),*InputTag.ToString(), *GetNameSafe(this) );
    }
    return nullptr;
}

RPGPlayerController

.h文件
// Copyright KimiLiu

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "GameplayTagContainer.h"
#include "RPGPlayerController.generated.h"

class URPGInputConfig;
class UInputMappingContext;
class UInputAction;
class IEnemyInterface;
class URPGAbilitySystemComponent;
class USplineComponent;
struct FInputActionValue;

/**
 * 
 */
UCLASS()
class AURA_API ARPGPlayerController : public APlayerController
{
    GENERATED_BODY()

public:
    ARPGPlayerController();

    virtual void PlayerTick(float DeltaTime) override;
    
protected:
    virtual void BeginPlay() override;

    virtual void SetupInputComponent() override;

private:
    /**
     * ---Property---------------------------------------------------------------------
     */
    UPROPERTY(EditAnywhere, Category="Input")
    TObjectPtr<UInputMappingContext> PlayerContext;

    UPROPERTY(EditAnywhere, Category="Input")
    TObjectPtr<UInputAction> MoveAction;

    //鼠标指针追踪处理参数,表示上一帧鼠标指针悬停的对象以及当前帧鼠标指针悬停的对象
    //鼠标指针追踪
    void CursorTrace();
    IEnemyInterface* LastActor;
    IEnemyInterface* ThisActor;
    FHitResult CursorHit;
    
    //移动函数
    void Move(const FInputActionValue& InputActionValue);

    

    void AbilityInputTagPressed(FGameplayTag InputTag);
    void AbilityInputTagReleased(FGameplayTag InputTag);
    void AbilityInputHeld(FGameplayTag InputTag);
    
    UPROPERTY(EditDefaultsOnly,Category="Input")
    TObjectPtr<URPGInputConfig> InputConfig;

    UPROPERTY()
    TObjectPtr<URPGAbilitySystemComponent> RPGAbilitySystemComponent;

    URPGAbilitySystemComponent* GetASC();

    // 鼠标点击移动功能
    FVector CachedDestination = FVector::ZeroVector;
    float FollowTime = 0.f;
    float ShortPressThreshold = 0.5f;
    bool bAutoRunning = false;
    bool bTargeting = false;
    
    UPROPERTY(EditDefaultsOnly)
    float AutoRunAcceptanceRadius = 50.f;

    UPROPERTY(VisibleAnywhere)
    TObjectPtr<USplineComponent> Spline;

    void AutoRun();
};
.cpp文件
// Copyright KimiLiu


#include "Player/RPGPlayerController.h"

#include "AbilitySystemBlueprintLibrary.h"
#include "EnhancedInputSubsystems.h"
#include "NavigationPath.h"
#include "NavigationSystem.h"
#include "RPGGameplayTags.h"
#include "AbilitySytstem/RPGAbilitySystemComponent.h"
#include "Components/SplineComponent.h"
#include "Input/RPGInputComponent.h"
#include "Interaction/EnemyInterface.h"

ARPGPlayerController::ARPGPlayerController()
{
    bReplicates = true;

    Spline = CreateDefaultSubobject<USplineComponent>("Spline");
}

void ARPGPlayerController::PlayerTick(float DeltaTime)
{
    Super::PlayerTick(DeltaTime);

    // 每帧追踪鼠标,高亮选择的对象,取消高亮不选择的对象
    CursorTrace();

    // 当bAutoRun为真时,鼠标左键单点前往可以去的地方,每帧都调用运动输入
    AutoRun();
}

void ARPGPlayerController::BeginPlay()
{
    Super::BeginPlay();

    //检查上下文是否为空,为空则直接中断系统,免得运行崩溃
    check(PlayerContext);
    UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
    //只有本地玩家才会有一个Subsystem,其它玩家并不会被分配Subsystem
    if (Subsystem)
    {
       Subsystem->AddMappingContext(PlayerContext, 0);
    }

    //显示鼠标指针,设置光标样式
    bShowMouseCursor = true;
    DefaultMouseCursor = EMouseCursor::Default;

    //设置交互方式,不将鼠标锁在游戏内,同时在鼠标不被ui捕捉时照样显示ui
    FInputModeGameAndUI InputModeData;
    InputModeData.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
    InputModeData.SetHideCursorDuringCapture(false);
    SetInputMode(InputModeData);
}

void ARPGPlayerController::SetupInputComponent()
{
    Super::SetupInputComponent();

    //检查是否为空以及是否为增强输入系统
    URPGInputComponent* RPGInputComponent = CastChecked<URPGInputComponent>(InputComponent);
    //绑定输入
    RPGInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ARPGPlayerController::Move);
    RPGInputComponent->BindAbilityActions(InputConfig, this, &ThisClass::AbilityInputTagPressed, &ThisClass::AbilityInputTagReleased, &ThisClass::AbilityInputHeld);
}

void ARPGPlayerController::Move(const FInputActionValue& InputActionValue)
{
    //将输入的数据结构转变为FVector2D,我们只需要向量
    const FVector2d InputAxisVector = InputActionValue.Get<FVector2d>();

    //获得控制器的转向信息,转向信息是基于世界坐标系的
    //创建水平转向的Rotator
    const FRotator Rotation = GetControlRotation();
    const FRotator YawRotation(0.f, Rotation.Yaw, 0.f);

    //获取当前角色在世界坐标系下的前向方向(XY平面)以及右向方向(XY平面)的向量
    //FrotationMatrix是一个结构体,表示旋转矩阵
    //GetUnitAxis函数返回矩阵的第x行参数,x取决于入参
    const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

    //Move函数会在游戏的每一帧被调用,因此有可能控制器还没有控制人物,这个函数就被调用了,那么系统会崩溃,我们不用Check来中断,只需要无视就好
    //等到控制器被分配到了人物之后再进入if函数内执行逻辑
    if (APawn* ControlledPawn = GetPawn<APawn>())
    {
       ControlledPawn->AddMovementInput(ForwardDirection, InputAxisVector.Y);
       ControlledPawn->AddMovementInput(RightDirection, InputAxisVector.X);
    }
    
}

void ARPGPlayerController::CursorTrace()
{
    
    GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);
    
    if (!CursorHit.bBlockingHit)return;

    //刷新指针悬浮的Actor
    LastActor = ThisActor;
    //获取点击对象的IEnemyInterface接口,查看对象是否实现了该接口,如未实现则返回nullptr
    ThisActor = Cast<IEnemyInterface>(CursorHit.GetActor());

    /**
     *鼠标指针追踪会有多种结果:
     * A. LastActor is null && ThisActor is null 
     *    - 不做任何处理
     * B. LastActor is null && ThisActor is valid
     *    - 高亮ThisActor
     * C. LastActor is valid && ThisActor is null
     *    - 取消高亮LastActor
     * D. LastActor is valid && ThisActor is valid , but LastActor != ThisActor
     *    - 取消高亮LastActor并高亮ThisActor
     * E. LastActor is valid && ThisActor is valid , but LastActor == ThisActor
     *    - 不做任何处理
     */

    if (LastActor == nullptr)
    {
       if (ThisActor != nullptr)
       {
          // case B
          ThisActor->HighlightActor();
       }
       else
       {
          // case A
       }
       
    }
    else
    {
       if (ThisActor == nullptr)
       {
          // case C
          LastActor->UnHighlightActor();
       }
       else
       {
          if (LastActor != ThisActor)
          {
             // case D
             LastActor->UnHighlightActor();
             ThisActor->HighlightActor();
          }
          else
          {
             // case E
          }
       }
    }
    
}

// 集中处理所有按键事件
void ARPGPlayerController::AbilityInputTagPressed(FGameplayTag InputTag)
{
    GEngine->AddOnScreenDebugMessage(1, 3.f, FColor::Red, *InputTag.ToString());

    if(InputTag.MatchesTagExact(FRPGGameplayTags::Get().InputTag_LMB))
    {
       bTargeting = ThisActor ? true : false ;
       bAutoRunning = false;
    }
    
}

void ARPGPlayerController::AbilityInputTagReleased(FGameplayTag InputTag)
{
    if(!InputTag.MatchesTagExact(FRPGGameplayTags::Get().InputTag_LMB))
    {
       if (GetASC())
       {
          GetASC()->AbilityInputTagReleased(InputTag);
       }
       return;
    }
    if (bTargeting)
    {
       if (GetASC())
       {
          GetASC()->AbilityInputTagReleased(InputTag);
       }
    }
    else
    {
       APawn* ControlledPawn = GetPawn();
       if (FollowTime <= ShortPressThreshold && ControlledPawn)
       {
          if(UNavigationPath* NavPath = UNavigationSystemV1::FindPathToLocationSynchronously(this, ControlledPawn->GetActorLocation(), CachedDestination))
          {
             Spline->ClearSplinePoints();
             for (const FVector& PointLoc : NavPath->PathPoints)
             {
                Spline->AddSplinePoint(PointLoc, ESplineCoordinateSpace::World);
                DrawDebugSphere(GetWorld(), PointLoc, 8.f, 8, FColor::Green, false, 5.f);
             }
             CachedDestination = NavPath->PathPoints[NavPath->PathPoints.Num()-1];
             bAutoRunning = true;
          }
       }
       FollowTime = 0.f;
       bTargeting = false;
    }
    
}

void ARPGPlayerController::AbilityInputHeld(FGameplayTag InputTag)
{

    if(!InputTag.MatchesTagExact(FRPGGameplayTags::Get().InputTag_LMB))
    {
       if (GetASC())
       {
          GetASC()->AbilityInputTagHeld(InputTag);
       }
       return;
    }

    if (bTargeting)
    {
       if (GetASC())
       {
          GetASC()->AbilityInputTagHeld(InputTag);
       }
    }
    else
    {
       FollowTime += GetWorld()->GetDeltaSeconds();

       
       if (CursorHit.bBlockingHit)
       {
          CachedDestination = CursorHit.ImpactPoint;
       }

       if (APawn* ControlledPawn = GetPawn())
       {
          const FVector WorldDirection = (CachedDestination - ControlledPawn->GetActorLocation()).GetSafeNormal();
          ControlledPawn->AddMovementInput(WorldDirection);
       }
    }
}

URPGAbilitySystemComponent* ARPGPlayerController::GetASC()
{
    if (RPGAbilitySystemComponent == nullptr)
    {
       RPGAbilitySystemComponent = Cast<URPGAbilitySystemComponent>(UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetPawn<APawn>()));
    }
    return RPGAbilitySystemComponent;
}

void ARPGPlayerController::AutoRun()
{
    if (!bAutoRunning)return;
    if(APawn* ControlledPawn = GetPawn())
    {
       const FVector LocationOnSpline = Spline->FindLocationClosestToWorldLocation(ControlledPawn->GetActorLocation(), ESplineCoordinateSpace::World);
       const FVector Direction = Spline->FindDirectionClosestToWorldLocation(LocationOnSpline, ESplineCoordinateSpace::World);
       ControlledPawn->AddMovementInput(Direction);

       const float DistanceToDestination = (LocationOnSpline - CachedDestination).Length();
       if (DistanceToDestination <= AutoRunAcceptanceRadius)
       {
          bAutoRunning = false;
          
       }
    }
}
  • 16
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

楚江_wog1st

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值