// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraHeroComponent.h" #include "LyraLogChannels.h" #include "GameFramework/Pawn.h" #include "EnhancedInputComponent.h" #include "EnhancedInputSubsystems.h" #include "Player/LyraPlayerController.h" #include "Player/LyraPlayerState.h" #include "Player/LyraLocalPlayer.h" #include "Character/LyraPawnExtensionComponent.h" #include "Character/LyraPawnData.h" #include "Character/LyraCharacter.h" #include "AbilitySystem/LyraAbilitySystemComponent.h" #include "Input/LyraInputConfig.h" #include "Input/LyraInputComponent.h" #include "Camera/LyraCameraComponent.h" #include "LyraGameplayTags.h" #include "Engine/LocalPlayer.h" #include "Components/GameFrameworkComponentManager.h" #include "Settings/LyraSettingsLocal.h" #include "System/LyraAssetManager.h" #include "PlayerMappableInputConfig.h" #include "Camera/LyraCameraMode.h" #if WITH_EDITOR #include "Misc/UObjectToken.h" #endif // WITH_EDITOR namespace LyraHero { static const float LookYawRate = 300.0f; static const float LookPitchRate = 165.0f; }; const FName ULyraHeroComponent::NAME_BindInputsNow("BindInputsNow"); const FName ULyraHeroComponent::NAME_ActorFeatureName("Hero"); ULyraHeroComponent::ULyraHeroComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { AbilityCameraMode = nullptr; bReadyToBindInputs = false; } void ULyraHeroComponent::OnRegister() { Super::OnRegister(); if (!GetPawn()) { UE_LOG(LogLyra, Error, TEXT("[ULyraHeroComponent::OnRegister] This component has been added to a blueprint whose base class is not a Pawn. To use this component, it MUST be placed on a Pawn Blueprint.")); #if WITH_EDITOR if (GIsEditor) { static const FText Message = NSLOCTEXT("LyraHeroComponent", "NotOnPawnError", "has been added to a blueprint whose base class is not a Pawn. To use this component, it MUST be placed on a Pawn Blueprint. This will cause a crash if you PIE!"); static const FName HeroMessageLogName = TEXT("LyraHeroComponent"); FMessageLog(HeroMessageLogName).Error() ->AddToken(FUObjectToken::Create(this, FText::FromString(GetNameSafe(this)))) ->AddToken(FTextToken::Create(Message)); FMessageLog(HeroMessageLogName).Open(); } #endif } else { // Register with the init state system early, this will only work if this is a game world RegisterInitStateFeature(); } } bool ULyraHeroComponent::CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const { check(Manager); const FLyraGameplayTags& InitTags = FLyraGameplayTags::Get(); APawn* Pawn = GetPawn(); if (!CurrentState.IsValid() && DesiredState == InitTags.InitState_Spawned) { // As long as we have a real pawn, let us transition if (Pawn) { return true; } } else if (CurrentState == InitTags.InitState_Spawned && DesiredState == InitTags.InitState_DataAvailable) { // The player state is required. if (!GetPlayerState()) { return false; } // If we're authority or autonomous, we need to wait for a controller with registered ownership of the player state. if (Pawn->GetLocalRole() != ROLE_SimulatedProxy) { AController* Controller = GetController(); const bool bHasControllerPairedWithPS = (Controller != nullptr) && \ (Controller->PlayerState != nullptr) && \ (Controller->PlayerState->GetOwner() == Controller); if (!bHasControllerPairedWithPS) { return false; } } const bool bIsLocallyControlled = Pawn->IsLocallyControlled(); const bool bIsBot = Pawn->IsBotControlled(); if (bIsLocallyControlled && !bIsBot) { ALyraPlayerController* LyraPC = GetController(); // The input component and local player is required when locally controlled. if (!Pawn->InputComponent || !LyraPC || !LyraPC->GetLocalPlayer()) { return false; } } return true; } else if (CurrentState == InitTags.InitState_DataAvailable && DesiredState == InitTags.InitState_DataInitialized) { // Wait for player state and extension component ALyraPlayerState* LyraPS = GetPlayerState(); return LyraPS && Manager->HasFeatureReachedInitState(Pawn, ULyraPawnExtensionComponent::NAME_ActorFeatureName, InitTags.InitState_DataInitialized); } else if (CurrentState == InitTags.InitState_DataInitialized && DesiredState == InitTags.InitState_GameplayReady) { // TODO add ability initialization checks? return true; } return false; } void ULyraHeroComponent::HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) { const FLyraGameplayTags& InitTags = FLyraGameplayTags::Get(); if (CurrentState == FLyraGameplayTags::Get().InitState_DataAvailable && DesiredState == FLyraGameplayTags::Get().InitState_DataInitialized) { APawn* Pawn = GetPawn(); ALyraPlayerState* LyraPS = GetPlayerState(); if (!ensure(Pawn && LyraPS)) { return; } const bool bIsLocallyControlled = Pawn->IsLocallyControlled(); const ULyraPawnData* PawnData = nullptr; if (ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) { PawnData = PawnExtComp->GetPawnData(); // The player state holds the persistent data for this player (state that persists across deaths and multiple pawns). // The ability system component and attribute sets live on the player state. PawnExtComp->InitializeAbilitySystem(LyraPS->GetLyraAbilitySystemComponent(), LyraPS); } if (ALyraPlayerController* LyraPC = GetController()) { if (Pawn->InputComponent != nullptr) { InitializePlayerInput(Pawn->InputComponent); } } if (bIsLocallyControlled && PawnData) { if (ULyraCameraComponent* CameraComponent = ULyraCameraComponent::FindCameraComponent(Pawn)) { CameraComponent->DetermineCameraModeDelegate.BindUObject(this, &ThisClass::DetermineCameraMode); } } } } void ULyraHeroComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& Params) { if (Params.FeatureName == ULyraPawnExtensionComponent::NAME_ActorFeatureName) { if (Params.FeatureState == FLyraGameplayTags::Get().InitState_DataInitialized) { // If the extension component says all all other components are initialized, try to progress to next state CheckDefaultInitialization(); } } } void ULyraHeroComponent::CheckDefaultInitialization() { const FLyraGameplayTags& InitTags = FLyraGameplayTags::Get(); static const TArray StateChain = { InitTags.InitState_Spawned, InitTags.InitState_DataAvailable, InitTags.InitState_DataInitialized, InitTags.InitState_GameplayReady }; // This will try to progress from spawned (which is only set in BeginPlay) through the data initialization stages until it gets to gameplay ready ContinueInitStateChain(StateChain); } void ULyraHeroComponent::BeginPlay() { Super::BeginPlay(); // Listen for when the pawn extension component changes init state BindOnActorInitStateChanged(ULyraPawnExtensionComponent::NAME_ActorFeatureName, FGameplayTag(), false); // Notifies that we are done spawning, then try the rest of initialization ensure(TryToChangeInitState(FLyraGameplayTags::Get().InitState_Spawned)); CheckDefaultInitialization(); } void ULyraHeroComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { UnregisterInitStateFeature(); Super::EndPlay(EndPlayReason); } void ULyraHeroComponent::InitializePlayerInput(UInputComponent* PlayerInputComponent) { check(PlayerInputComponent); const APawn* Pawn = GetPawn(); if (!Pawn) { return; } const APlayerController* PC = GetController(); check(PC); const ULyraLocalPlayer* LP = Cast(PC->GetLocalPlayer()); check(LP); UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem(); check(Subsystem); Subsystem->ClearAllMappings(); if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) { if (const ULyraPawnData* PawnData = PawnExtComp->GetPawnData()) { if (const ULyraInputConfig* InputConfig = PawnData->InputConfig) { const FLyraGameplayTags& GameplayTags = FLyraGameplayTags::Get(); // Register any default input configs with the settings so that they will be applied to the player during AddInputMappings for (const FMappableConfigPair& Pair : DefaultInputConfigs) { if (Pair.bShouldActivateAutomatically && Pair.CanBeActivated()) { FModifyContextOptions Options = {}; Options.bIgnoreAllPressedKeysUntilRelease = false; // Actually add the config to the local player Subsystem->AddPlayerMappableConfig(Pair.Config.LoadSynchronous(), Options); } } ULyraInputComponent* LyraIC = CastChecked(PlayerInputComponent); LyraIC->AddInputMappings(InputConfig, Subsystem); TArray BindHandles; LyraIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles); LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move, /*bLogIfNotFound=*/ false); LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Look_Mouse, ETriggerEvent::Triggered, this, &ThisClass::Input_LookMouse, /*bLogIfNotFound=*/ false); LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Look_Stick, ETriggerEvent::Triggered, this, &ThisClass::Input_LookStick, /*bLogIfNotFound=*/ false); LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_Crouch, ETriggerEvent::Triggered, this, &ThisClass::Input_Crouch, /*bLogIfNotFound=*/ false); LyraIC->BindNativeAction(InputConfig, GameplayTags.InputTag_AutoRun, ETriggerEvent::Triggered, this, &ThisClass::Input_AutoRun, /*bLogIfNotFound=*/ false); } } } if (ensure(!bReadyToBindInputs)) { bReadyToBindInputs = true; } UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast(PC), NAME_BindInputsNow); UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast(Pawn), NAME_BindInputsNow); } void ULyraHeroComponent::AddAdditionalInputConfig(const ULyraInputConfig* InputConfig) { TArray BindHandles; const APawn* Pawn = GetPawn(); if (!Pawn) { return; } ULyraInputComponent* LyraIC = Pawn->FindComponentByClass(); check(LyraIC); const APlayerController* PC = GetController(); check(PC); const ULocalPlayer* LP = PC->GetLocalPlayer(); check(LP); UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem(); check(Subsystem); if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) { LyraIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles); } } void ULyraHeroComponent::RemoveAdditionalInputConfig(const ULyraInputConfig* InputConfig) { //@TODO: Implement me! } bool ULyraHeroComponent::IsReadyToBindInputs() const { return bReadyToBindInputs; } void ULyraHeroComponent::Input_AbilityInputTagPressed(FGameplayTag InputTag) { if (const APawn* Pawn = GetPawn()) { if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) { if (ULyraAbilitySystemComponent* LyraASC = PawnExtComp->GetLyraAbilitySystemComponent()) { LyraASC->AbilityInputTagPressed(InputTag); } } } } void ULyraHeroComponent::Input_AbilityInputTagReleased(FGameplayTag InputTag) { const APawn* Pawn = GetPawn(); if (!Pawn) { return; } if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) { if (ULyraAbilitySystemComponent* LyraASC = PawnExtComp->GetLyraAbilitySystemComponent()) { LyraASC->AbilityInputTagReleased(InputTag); } } } void ULyraHeroComponent::Input_Move(const FInputActionValue& InputActionValue) { APawn* Pawn = GetPawn(); AController* Controller = Pawn ? Pawn->GetController() : nullptr; // If the player has attempted to move again then cancel auto running if (ALyraPlayerController* LyraController = Cast(Controller)) { LyraController->SetIsAutoRunning(false); } if (Controller) { const FVector2D Value = InputActionValue.Get(); const FRotator MovementRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f); if (Value.X != 0.0f) { const FVector MovementDirection = MovementRotation.RotateVector(FVector::RightVector); Pawn->AddMovementInput(MovementDirection, Value.X); } if (Value.Y != 0.0f) { const FVector MovementDirection = MovementRotation.RotateVector(FVector::ForwardVector); Pawn->AddMovementInput(MovementDirection, Value.Y); } } } void ULyraHeroComponent::Input_LookMouse(const FInputActionValue& InputActionValue) { APawn* Pawn = GetPawn(); if (!Pawn) { return; } const FVector2D Value = InputActionValue.Get(); if (Value.X != 0.0f) { Pawn->AddControllerYawInput(Value.X); } if (Value.Y != 0.0f) { Pawn->AddControllerPitchInput(Value.Y); } } void ULyraHeroComponent::Input_LookStick(const FInputActionValue& InputActionValue) { APawn* Pawn = GetPawn(); if (!Pawn) { return; } const FVector2D Value = InputActionValue.Get(); const UWorld* World = GetWorld(); check(World); if (Value.X != 0.0f) { Pawn->AddControllerYawInput(Value.X * LyraHero::LookYawRate * World->GetDeltaSeconds()); } if (Value.Y != 0.0f) { Pawn->AddControllerPitchInput(Value.Y * LyraHero::LookPitchRate * World->GetDeltaSeconds()); } } void ULyraHeroComponent::Input_Crouch(const FInputActionValue& InputActionValue) { if (ALyraCharacter* Character = GetPawn()) { Character->ToggleCrouch(); } } void ULyraHeroComponent::Input_AutoRun(const FInputActionValue& InputActionValue) { if (APawn* Pawn = GetPawn()) { if (ALyraPlayerController* Controller = Cast(Pawn->GetController())) { // Toggle auto running Controller->SetIsAutoRunning(!Controller->GetIsAutoRunning()); } } } TSubclassOf ULyraHeroComponent::DetermineCameraMode() const { if (AbilityCameraMode) { return AbilityCameraMode; } const APawn* Pawn = GetPawn(); if (!Pawn) { return nullptr; } if (ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn)) { if (const ULyraPawnData* PawnData = PawnExtComp->GetPawnData()) { return PawnData->DefaultCameraMode; } } return nullptr; } void ULyraHeroComponent::SetAbilityCameraMode(TSubclassOf CameraMode, const FGameplayAbilitySpecHandle& OwningSpecHandle) { if (CameraMode) { AbilityCameraMode = CameraMode; AbilityCameraModeOwningSpecHandle = OwningSpecHandle; } } void ULyraHeroComponent::ClearAbilityCameraMode(const FGameplayAbilitySpecHandle& OwningSpecHandle) { if (AbilityCameraModeOwningSpecHandle == OwningSpecHandle) { AbilityCameraMode = nullptr; AbilityCameraModeOwningSpecHandle = FGameplayAbilitySpecHandle(); } }