Combat Abilities System — Расширение Gameplay Ability System в Unreal Engine, Часть 1

от автора

Для своей новой игры Code Of Person (Как-нибудь в следующих статьях про её разработку расскажу) я решил использовать и расширить плагин Gameplay Ability System.

В серии статьей я расскажу о моём процессе создания плагина Combat Abilities System, который расширяет возможности стандартного плагина Gameplay Ability System в Unreal Engine. Мы рассмотрим шаги разработки, архитектурные решения и особенности, которые делают наш плагин уникальным и полезным для создания боевых механик в играх.

В этой части покажу свою реализацию интеграции с Enhanced Input и Game Features.

Что такое Gameplay Ability System и для чего он нужен?

Gameplay Ability System — это комплексная система, предназначенная для создания и управления способностями. Она позволяет разработчикам создавать разнообразные способности, управлять их активацией, эффектами и взаимодействием с другими системами игры. Однако, базового функционала мне недостаточно, и я хотел сделать так чтобы можно переиспользовать свои разработки между проектами + заявить себя

Для себя я определил основные желания для разработки данной системы:

  • боевая система

  • система управление ввода игрока

  • модульность

Привязка к Enhanced Input

Для начала я создал класс UPlayerControlsComponent, который будет отвечать за функциональность настройками пользовательского ввода и управления через Enhanced Input

PlayerControlsComponent.h  #pragma once  #include "EnhancedInputComponent.h" #include "Components/PawnComponent.h" #include "PlayerControlsComponent.generated.h"  class UEnhancedInputLocalPlayerSubsystem; class UInputAction; class UInputMappingContext; /**  *   */ UCLASS(BlueprintType, Blueprintable, meta=(BlueprintSpawnableComponent)) class COMBATABILITIESSYSTEMRUNTIME_API UPlayerControlsComponent : public UPawnComponent { GENERATED_BODY()  public: virtual void OnRegister() override; virtual void OnUnregister() override;  protected: UFUNCTION(BlueprintNativeEvent, Category="Player Controls") void SetupPlayerControls(UEnhancedInputComponent* InPlayerInputComponent);  UFUNCTION(BlueprintNativeEvent, Category="Player Controls") void TeardownPlayerControls(UEnhancedInputComponent* InPlayerInputComponent);  template<class UserClass, typename FuncType> bool BindInputAction(const UInputAction* InAction, const ETriggerEvent InEventType, UserClass* InObject, FuncType InFunction) { if(ensure(InputComponent != nullptr) && ensure(InAction != nullptr)) { InputComponent->BindAction(InAction, InEventType, InObject, InFunction); return true; } return false; }  UFUNCTION() virtual void OnPawnRestarted(APawn* InPawn); UFUNCTION() virtual void OnControllerChanged(APawn* InPawn, AController* InOldController, AController* NewController);  virtual void SetupInputComponent(APawn* InPawn); virtual void ReleaseInputComponent(AController* OldController = nullptr); UEnhancedInputLocalPlayerSubsystem* GetEnhancedInputSubsystem(AController* OldController = nullptr) const;  UEnhancedInputComponent* GetInputComponent() const { return InputComponent;}  private: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Player Controls", meta=(AllowPrivateAccess="true")) TObjectPtr<UInputMappingContext> InputMappingContext{nullptr}; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Player Controls", meta=(AllowPrivateAccess="true")) int32 InputPriority{0};  UPROPERTY(Transient) UEnhancedInputComponent* InputComponent;  };
UPlayerControlsComponent.cpp   #include "Components/PlayerControlsComponent.h"  #include "EnhancedInputSubsystems.h"  #include UE_INLINE_GENERATED_CPP_BY_NAME(PlayerControlsComponent)  void UPlayerControlsComponent::SetupPlayerControls_Implementation(UEnhancedInputComponent* InPlayerInputComponent) { }  void UPlayerControlsComponent::TeardownPlayerControls_Implementation(UEnhancedInputComponent* InPlayerInputComponent) { }  void UPlayerControlsComponent::OnRegister() { Super::OnRegister();  const UWorld* World {GetWorld()}; APawn* PawnOwner{GetPawn<APawn>()}; if(!ensure(PawnOwner) && !World->IsGameWorld()) return;  PawnOwner->ReceiveRestartedDelegate.AddDynamic(this, &UPlayerControlsComponent::OnPawnRestarted); PawnOwner->ReceiveControllerChangedDelegate.AddDynamic(this, &UPlayerControlsComponent::OnControllerChanged);  if(PawnOwner->InputComponent) { OnPawnRestarted(PawnOwner); }  }  void UPlayerControlsComponent::OnUnregister() { if(const UWorld* World {GetWorld()}; World && World->IsGameWorld()) { ReleaseInputComponent(); if(auto* PawnOwner{GetPawn<APawn>()}; PawnOwner) { PawnOwner->ReceiveRestartedDelegate.RemoveAll(this); PawnOwner->ReceiveControllerChangedDelegate.RemoveAll(this); } }  Super::OnUnregister(); }  void UPlayerControlsComponent::OnPawnRestarted(APawn* InPawn) { if(ensure(InPawn && InPawn == GetOwner()) && InPawn->InputComponent) { ReleaseInputComponent(); if(InPawn->InputComponent) { SetupInputComponent(InPawn); } } }  void UPlayerControlsComponent::OnControllerChanged(APawn* InPawn, AController* InOldController, AController* NewController) { if(ensure(InPawn && InPawn == GetOwner()) && InOldController) { ReleaseInputComponent(InOldController); } }  void UPlayerControlsComponent::SetupInputComponent(APawn* InPawn) { InputComponent = CastChecked<UEnhancedInputComponent>(InPawn->InputComponent);  UEnhancedInputLocalPlayerSubsystem* Subsystem = {GetEnhancedInputSubsystem()}; check(Subsystem); if(InputMappingContext) { Subsystem->AddMappingContext(InputMappingContext, InputPriority); } SetupPlayerControls(InputComponent); }  void UPlayerControlsComponent::ReleaseInputComponent(AController* OldController) { if(UEnhancedInputLocalPlayerSubsystem* Subsystem {GetEnhancedInputSubsystem(OldController)}; Subsystem && InputComponent) { TeardownPlayerControls(InputComponent); if(InputMappingContext) { Subsystem->RemoveMappingContext(InputMappingContext); } } InputComponent = nullptr; }  UEnhancedInputLocalPlayerSubsystem* UPlayerControlsComponent::GetEnhancedInputSubsystem( AController* OldController) const { const APlayerController* PlayerController {GetController<APlayerController>()}; if(!PlayerController) { PlayerController = Cast<APlayerController>(OldController); if(!PlayerController) { return nullptr; } }  const ULocalPlayer* LocalPlayer {PlayerController->GetLocalPlayer()}; if(!LocalPlayer) return nullptr;  return LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(); } 

Далее нужно создать от компонента, описанного выше, дочерний класс UAbilityInputBindingComponent, который будет отвечать за привязку ввода и игровыми способностями:

AbilityInputBindingComponent.h  #pragma once  #include "GameplayAbilitySpec.h" #include "GameplayAbilitySpecHandle.h" #include "Components/PlayerControlsComponent.h" #include "AbilityInputBindingComponent.generated.h"  class UAbilitySystemComponent;  USTRUCT() struct FAbilityInputBinding { GENERATED_BODY()  int32 InputID{0}; uint32 OnPressedHandle{0}; uint32 OnReleasedHandle{0}; TArray<FGameplayAbilitySpecHandle> BoundAbilitiesStack;  };  UCLASS(meta=(BlueprintSpawnableComponent)) class COMBATABILITIESSYSTEMRUNTIME_API UAbilityInputBindingComponent : public UPlayerControlsComponent { GENERATED_BODY()  public: UFUNCTION(BlueprintCallable, Category="Abilities") void SetInputBinding(UInputAction* InInputAction, FGameplayAbilitySpecHandle AbilitySpecHandle);  UFUNCTION(BlueprintCallable, Category="Abilities") void ClearInputBinding(FGameplayAbilitySpecHandle InAbilityHandle);  UFUNCTION(BlueprintCallable, Category="Abilities") void ClearAbilityBindings(UInputAction* InInputAction);  virtual void SetupPlayerControls_Implementation(UEnhancedInputComponent* InPlayerInputComponent) override; virtual void ReleaseInputComponent(AController* OldController) override;  private: void ResetBindings(); void RunAbilitySystemSetup(); void OnAbilityInputPressed(UInputAction* InInputAction); void OnAbilityInputReleased(UInputAction* InInputAction);  void RemoveEntry(UInputAction* InInputAction);  FGameplayAbilitySpec* FindAbilitySpec(FGameplayAbilitySpecHandle InHandle); void TryBindAbilityInput(UInputAction* InInputAction, FAbilityInputBinding& InAbilityInputBinding);  private: UPROPERTY(Transient) UAbilitySystemComponent* AbilityComponent;  UPROPERTY(Transient) TMap<UInputAction*, FAbilityInputBinding> MappedAbilities;  };
AbilityInputBindingComponent.cpp  #include "Input/AbilityInputBindingComponent.h"  #include "AbilitySystemComponent.h" #include "AbilitySystemGlobals.h"  #include UE_INLINE_GENERATED_CPP_BY_NAME(AbilityInputBindingComponent)  namespace AbilityInputBindingComponent_Impl { constexpr int32 InvalidInputID{0}; int32 IncrementingInputID{InvalidInputID}; static int32 GetNextInputID() { return ++IncrementingInputID; } }  void UAbilityInputBindingComponent::SetInputBinding(UInputAction* InInputAction, FGameplayAbilitySpecHandle AbilitySpecHandle) { FGameplayAbilitySpec* BindingAbility {FindAbilitySpec(AbilitySpecHandle)};  FAbilityInputBinding* AbilityInputBinding {MappedAbilities.Find(InInputAction)}; if(AbilityInputBinding) { if(FGameplayAbilitySpec* OldBoundAbility {FindAbilitySpec(AbilityInputBinding->BoundAbilitiesStack.Top())}; OldBoundAbility && OldBoundAbility->InputID == AbilityInputBinding->InputID) { OldBoundAbility->InputID = AbilityInputBindingComponent_Impl::InvalidInputID; } } else { AbilityInputBinding = &MappedAbilities.Add(InInputAction); AbilityInputBinding->InputID = AbilityInputBindingComponent_Impl::GetNextInputID(); }  if(BindingAbility) { BindingAbility->InputID = AbilityInputBinding->InputID; }  AbilityInputBinding->BoundAbilitiesStack.Push(AbilitySpecHandle); TryBindAbilityInput(InInputAction, *AbilityInputBinding);  }  void UAbilityInputBindingComponent::ClearInputBinding(FGameplayAbilitySpecHandle InAbilityHandle) { FGameplayAbilitySpec* FoundAbility {FindAbilitySpec(InAbilityHandle)}; if(!FoundAbility) return;  auto MappedIterator = MappedAbilities.CreateIterator(); while (MappedIterator) { if(MappedIterator.Value().InputID == FoundAbility->InputID) { break; } ++MappedIterator; }  if(!MappedIterator) return;  FAbilityInputBinding& AbilityInputBinding = MappedIterator.Value(); if(AbilityInputBinding.BoundAbilitiesStack.Remove(InAbilityHandle) > 0) { if(AbilityInputBinding.BoundAbilitiesStack.Num() > 0) { FGameplayAbilitySpec* StackedAbility {FindAbilitySpec(AbilityInputBinding.BoundAbilitiesStack.Top())}; if(StackedAbility && StackedAbility->InputID == 0) { StackedAbility->InputID = AbilityInputBinding.InputID; } } else { RemoveEntry(MappedIterator.Key()); }  FoundAbility->InputID = AbilityInputBindingComponent_Impl::InvalidInputID; }  }  void UAbilityInputBindingComponent::ClearAbilityBindings(UInputAction* InInputAction) { RemoveEntry(InInputAction);  }  void UAbilityInputBindingComponent::SetupPlayerControls_Implementation(UEnhancedInputComponent* InPlayerInputComponent) { for(auto& InputBinding : MappedAbilities) { if(auto* CurrentInputComponent {GetInputComponent()}; CurrentInputComponent) { CurrentInputComponent->RemoveBindingByHandle(InputBinding.Value.OnPressedHandle); CurrentInputComponent->RemoveBindingByHandle(InputBinding.Value.OnReleasedHandle); } if(AbilityComponent) { const int32 ExpectedInputID = InputBinding.Value.InputID; for(FGameplayAbilitySpecHandle AbilityHandle : InputBinding.Value.BoundAbilitiesStack) { if(FGameplayAbilitySpec* FoundAbility = AbilityComponent->FindAbilitySpecFromHandle(AbilityHandle); FoundAbility && FoundAbility->InputID == ExpectedInputID) { FoundAbility->InputID = AbilityInputBindingComponent_Impl::InvalidInputID; } } } }  AbilityComponent = nullptr;  }  void UAbilityInputBindingComponent::ReleaseInputComponent(AController* OldController) { ResetBindings();  Super::ReleaseInputComponent(OldController);  }  void UAbilityInputBindingComponent::ResetBindings() { for(auto& InputBinding : MappedAbilities) { if(auto* CurrentInputComponent {GetInputComponent()}; CurrentInputComponent) { CurrentInputComponent->RemoveBindingByHandle(InputBinding.Value.OnPressedHandle); CurrentInputComponent->RemoveBindingByHandle(InputBinding.Value.OnReleasedHandle); } if(AbilityComponent) { const int32 ExpectedInputID = InputBinding.Value.InputID; for(FGameplayAbilitySpecHandle AbilitySpecHandle : InputBinding.Value.BoundAbilitiesStack) { FGameplayAbilitySpec* FoundAbility = AbilityComponent->FindAbilitySpecFromHandle(AbilitySpecHandle); if(FoundAbility && FoundAbility->InputID == ExpectedInputID) { FoundAbility->InputID = AbilityInputBindingComponent_Impl::InvalidInputID; } } }  }  AbilityComponent = nullptr;  }  void UAbilityInputBindingComponent::RunAbilitySystemSetup() { const AActor* Owner {GetOwner()}; check(Owner);  AbilityComponent = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Owner); if(!AbilityComponent) return;  for (auto& InputBinding : MappedAbilities) { const int32 NewInputID {AbilityInputBindingComponent_Impl::GetNextInputID()}; InputBinding.Value.InputID = NewInputID; for(const FGameplayAbilitySpecHandle AbilityHandle : InputBinding.Value.BoundAbilitiesStack) { if(FGameplayAbilitySpec* FoundAbility = AbilityComponent->FindAbilitySpecFromHandle(AbilityHandle); FoundAbility) { FoundAbility->InputID = NewInputID; } } }  }  void UAbilityInputBindingComponent::OnAbilityInputPressed(UInputAction* InInputAction) { if(!AbilityComponent) { RunAbilitySystemSetup(); } if(AbilityComponent) { if(const FAbilityInputBinding* FoundBinding {MappedAbilities.Find(InInputAction)}; FoundBinding && ensure(FoundBinding->InputID) != AbilityInputBindingComponent_Impl::InvalidInputID) { AbilityComponent->AbilityLocalInputPressed(FoundBinding->InputID); } }  }  void UAbilityInputBindingComponent::OnAbilityInputReleased(UInputAction* InInputAction) { if(!AbilityComponent) return;  if(const FAbilityInputBinding* FoundBinding {MappedAbilities.Find(InInputAction)}; FoundBinding && ensure(FoundBinding->InputID != AbilityInputBindingComponent_Impl::InvalidInputID)) { AbilityComponent->AbilityLocalInputReleased(FoundBinding->InputID); }  }  void UAbilityInputBindingComponent::RemoveEntry(UInputAction* InInputAction) { if(FAbilityInputBinding* Binding {MappedAbilities.Find(InInputAction)}) { if(auto* CurrentInputComponent {GetInputComponent()}; CurrentInputComponent) { CurrentInputComponent->RemoveBindingByHandle(Binding->OnPressedHandle); CurrentInputComponent->RemoveBindingByHandle(Binding->OnReleasedHandle); }  for(FGameplayAbilitySpecHandle AbilityHandle : Binding->BoundAbilitiesStack) { FGameplayAbilitySpec* AbilitySpec = FindAbilitySpec(AbilityHandle); if(AbilitySpec && AbilitySpec->InputID == Binding->InputID) { AbilitySpec->InputID = AbilityInputBindingComponent_Impl::InvalidInputID; } }  MappedAbilities.Remove(InInputAction); }  }  FGameplayAbilitySpec* UAbilityInputBindingComponent::FindAbilitySpec(FGameplayAbilitySpecHandle InHandle) { FGameplayAbilitySpec* FoundAbility{nullptr}; if(AbilityComponent) { FoundAbility = AbilityComponent->FindAbilitySpecFromHandle(InHandle); } return FoundAbility;  }  void UAbilityInputBindingComponent::TryBindAbilityInput(UInputAction* InInputAction, FAbilityInputBinding& InAbilityInputBinding) { if(auto* CurrentInputComponent {GetInputComponent()}; CurrentInputComponent) { if(InAbilityInputBinding.OnPressedHandle == 0) { InAbilityInputBinding.OnPressedHandle = CurrentInputComponent->BindAction(InInputAction, ETriggerEvent::Started, this, &UAbilityInputBindingComponent::OnAbilityInputPressed, InInputAction).GetHandle(); }  if(InAbilityInputBinding.OnReleasedHandle == 0) { InAbilityInputBinding.OnReleasedHandle = CurrentInputComponent->BindAction(InInputAction, ETriggerEvent::Completed, this, &UAbilityInputBindingComponent::OnAbilityInputReleased, InInputAction).GetHandle(); } }  }

Game Features

Плагин Game Features помогает создавать отдельные функциональности, которые можно включать и выключать в зависимости от потребностей проекта, также помогает избегать лишних зависимостей.

После создания компонентов для контроля компонентов, нужно создать действие в Game Features, которое будет добавлять Mapping Context в общую субсистему пользовательского ввода.

AddInputContextMapping_GameFeatureAction.h  #pragma once  #include "CoreMinimal.h" #include "GameFeature/GameFeature_WorldActionBase.h" #include "AddInputContextMapping_GameFeatureAction.generated.h"  class UInputMappingContext; struct FComponentRequestHandle;   UCLASS(DisplayName="Add Input Context Mapping") class COMBATABILITIESSYSTEMRUNTIME_API UAddInputContextMapping_GameFeatureAction : public UGameFeature_WorldActionBase { GENERATED_BODY()  public:  virtual void OnGameFeatureActivating() override; virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) override;  #if WITH_EDITORONLY_DATA virtual void AddAdditionalAssetBundleData(FAssetBundleData& AssetBundleData) override; #endif  #if WITH_EDITOR virtual EDataValidationResult IsDataValid(TArray<FText>& ValidationErrors) override; #endif   private: virtual void AddToWorld(const FWorldContext& InWorldContext) override;  void Reset(); void HandleControllerExtension(AActor* InActor, FName EventName); void AddInputMappingForPlayer(UPlayer* InPlayer); void RemoveInputMapping(APlayerController* InPlayerController);  private: UPROPERTY(EditAnywhere, Category="Input", meta=(AllowPrivateAccess="true")) TSoftObjectPtr<UInputMappingContext> InputMapping; UPROPERTY(EditAnywhere, Category="Input", meta=(AllowPrivateAccess="true")) int32 Priority{0};  TArray<TSharedPtr<FComponentRequestHandle>> ExtensionRequestHandles; TArray<TWeakObjectPtr<APlayerController>> ControllersAddedTo;  };
AddInputContextMapping_GameFeatureAction.cpp  #include "GameFeature/AddInputContextMapping_GameFeatureAction.h" #include "CombatAbilitiesSystemRuntime/Public/CombatAbilitiesSystemRuntimeModule.h" #include "Engine/AssetManager.h" #include "GameFeaturesSubsystemSettings.h" #include "Components/GameFrameworkComponentManager.h" #include "GameFramework/PlayerController.h" #include "EnhancedInputSubsystems.h" #include "InputMappingContext.h"  #define LOCTEXT_NAMESPACE "CombatAbilitiesSystemRuntimeFeatures"  #include UE_INLINE_GENERATED_CPP_BY_NAME(AddInputContextMapping_GameFeatureAction)  void UAddInputContextMapping_GameFeatureAction::OnGameFeatureActivating() { if (!ensure(ExtensionRequestHandles.IsEmpty()) || !ensure(ControllersAddedTo.IsEmpty())) { Reset(); } Super::OnGameFeatureActivating(); }  void UAddInputContextMapping_GameFeatureAction::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) { Super::OnGameFeatureDeactivating(Context); Reset(); }  #if WITH_EDITORONLY_DATA void UAddInputContextMapping_GameFeatureAction::AddAdditionalAssetBundleData(FAssetBundleData& AssetBundleData) { if (UAssetManager::IsValid()) { AssetBundleData.AddBundleAsset(UGameFeaturesSubsystemSettings::LoadStateClient, InputMapping.ToSoftObjectPath()); AssetBundleData.AddBundleAsset(UGameFeaturesSubsystemSettings::LoadStateServer, InputMapping.ToSoftObjectPath()); } } #endif  #if WITH_EDITOR EDataValidationResult UAddInputContextMapping_GameFeatureAction::IsDataValid(TArray<FText>& ValidationErrors) { EDataValidationResult Result {CombineDataValidationResults(Super::IsDataValid(ValidationErrors), EDataValidationResult::Valid)};  if (InputMapping.IsNull()) { Result = EDataValidationResult::Invalid; ValidationErrors.Add(LOCTEXT("NullInputMapping", "Null InputMapping.")); }  return Result; } #endif  void UAddInputContextMapping_GameFeatureAction::AddToWorld(const FWorldContext& WorldContext) { const UWorld* World {WorldContext.World()}; const UGameInstance* GameInstance {WorldContext.OwningGameInstance.Get()};  if ((GameInstance != nullptr) && (World != nullptr) && World->IsGameWorld()) { if (UGameFrameworkComponentManager* ComponentMan {UGameInstance::GetSubsystem<UGameFrameworkComponentManager>(GameInstance)}) { if (!InputMapping.IsNull()) { const UGameFrameworkComponentManager::FExtensionHandlerDelegate AddAbilitiesDelegate {UGameFrameworkComponentManager::FExtensionHandlerDelegate::CreateUObject( this, &UAddInputContextMapping_GameFeatureAction::HandleControllerExtension)}; const TSharedPtr<FComponentRequestHandle> ExtensionRequestHandle {ComponentMan->AddExtensionHandler(APlayerController::StaticClass(), AddAbilitiesDelegate)};  ExtensionRequestHandles.Add(ExtensionRequestHandle); } } } }  void UAddInputContextMapping_GameFeatureAction::Reset() { ExtensionRequestHandles.Empty();  while (!ControllersAddedTo.IsEmpty()) { TWeakObjectPtr<APlayerController> ControllerPtr {ControllersAddedTo.Top()}; if (ControllerPtr.IsValid()) { RemoveInputMapping(ControllerPtr.Get()); } else { ControllersAddedTo.Pop(); } } }  void UAddInputContextMapping_GameFeatureAction::HandleControllerExtension(AActor* InActor, FName InEventName) { APlayerController* AsController {CastChecked<APlayerController>(InActor)};  if (InEventName == UGameFrameworkComponentManager::NAME_ExtensionRemoved || InEventName == UGameFrameworkComponentManager::NAME_ReceiverRemoved) { RemoveInputMapping(AsController); } else if (InEventName == UGameFrameworkComponentManager::NAME_ExtensionAdded || InEventName == UGameFrameworkComponentManager::NAME_GameActorReady) { AddInputMappingForPlayer(AsController->GetLocalPlayer()); } }  void UAddInputContextMapping_GameFeatureAction::AddInputMappingForPlayer(UPlayer* InPlayer) { if (const ULocalPlayer* LocalPlayer {Cast<ULocalPlayer>(InPlayer)}) { if (UEnhancedInputLocalPlayerSubsystem* InputSystem {LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>()}) { if (!InputMapping.IsNull()) { InputSystem->AddMappingContext(InputMapping.LoadSynchronous(), Priority); } } else { UE_LOG(LogCombatAbilitySystem, Error, TEXT("Failed to find `UEnhancedInputLocalPlayerSubsystem` for local player. Input mappings will not be added. Make sure you're set to use the EnhancedInput system via config file.")); } } }  void UAddInputContextMapping_GameFeatureAction::RemoveInputMapping(APlayerController* InPlayerController) { if (const ULocalPlayer* LocalPlayer {InPlayerController->GetLocalPlayer()}) { if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>()) { InputSystem->RemoveMappingContext(InputMapping.Get()); } }  ControllersAddedTo.Remove(InPlayerController); }  #undef LOCTEXT_NAMESPACE

Для того чтобы была возможность добавлять компоненты к актору, надо сделать чтобы он зарегистрировал себя на получение компонентов у Game Framework Component Manager. Рассмотрим на примере для PlayerController, с другими акторами тоже самое, но без ReceivedPlayer() и PlayerTick():

AModularPlayerController.h  #pragma once  #include "GameFramework/PlayerController.h"  #include "ModularPlayerController.generated.h"   UCLASS(Blueprintable) class MODULARGAMEPLAYACTORS_API AModularPlayerController : public APlayerController { GENERATED_BODY()  public: virtual void PreInitializeComponents() override;  virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;  virtual void ReceivedPlayer() override;  virtual void PlayerTick(float DeltaTime) override;  };
ModularPlayerController.cpp  #include "ModularPlayerController.h"  #include "Components/ControllerComponent.h" #include "Components/GameFrameworkComponentManager.h"  #include UE_INLINE_GENERATED_CPP_BY_NAME(ModularPlayerController)  void AModularPlayerController::PreInitializeComponents() { Super::PreInitializeComponents();  UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);  }  void AModularPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason) { UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);  Super::EndPlay(EndPlayReason);  }  void AModularPlayerController::ReceivedPlayer() { UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);  Super::ReceivedPlayer();  TArray<UControllerComponent*> ModularComponents; GetComponents(ModularComponents); for (UControllerComponent* Component : ModularComponents) { Component->ReceivedPlayer(); }  }  void AModularPlayerController::PlayerTick(float DeltaTime) { Super::PlayerTick(DeltaTime);  TArray<UControllerComponent*> ModularComponents; GetComponents(ModularComponents); for (UControllerComponent* Component : ModularComponents) { Component->PlayerTick(DeltaTime); }  }

Применение

После всей создания необходимых классов, пришло время это использовать.

В GameMode выставляем в Player Controller Class AModularPlayerController.
Далле добавляем AddInputContextMapping_GameFeatureAction (Add Input Context Mapping) и указываем InputMappingContext в файл GameFeatureData:

Далее чтобы это чудо заработало при запуске игры, надо в чертеже уровня прописать выполнение консольной команды — «LoadGameFeaturePlugin CombatAbilitiesSystem»:

Заключение

Далее в следующей части будет рассмотрено расширение самого AbilitySystemComponent и создание Abilities.

Спасибо за внимание! Ставьте лайки, подписывайтесь и оставляйте комментарии.


ссылка на оригинал статьи https://habr.com/ru/articles/832914/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *