Для своей новой игры 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/
Добавить комментарий