自定义输入框架思路:系统通过Tag判断用户输入,而不是EnhancedInput告诉系统玩家输入,EnhancedInput接受输入,传入Tag告诉系统
文件结构
Source
-
Private
- AbilitySystem
- Data
- AttributeInfo.cpp
- ModMagCalc
- MMC_MaxHealth.cpp
- MMC_MaxMana.cpp
- RPGAbilitySystemComponent.cpp
- RPGAbilitySystemLibrary.cpp
- RPGAttributeSet.cpp
- Data
- 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
- HUD
- RPGAssetManager.cpp
- RPGGameplayTags.cpp
- AbilitySystem
-
Public
-
AbilitySystem
- Data
- AttributeInfo.h
- ModMagCalc
- MMC_MaxHealth.h
- MMC_MaxMana.h
- RPGAbilitySystemComponent.h
- RPGAbilitySystemLibrary.h
- RPGAttributeSet.h
- Data
-
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
- HUD
-
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;
}
}
}