Created
March 6, 2025 18:00
-
-
Save LuviKunG/ebdbdec4372c1a591044862659ba8532 to your computer and use it in GitHub Desktop.
A simple spectator pawn class for Unreal Engine 5.X C++ which replacement of a default pawn. Using Enhanced Inputs and implement move speed changing and zooming by change camera field of view.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2025 Thanut Panichyotai (@LuviKunG) All Rights Reserved. | |
#include "SimpleSpectatorPawn.h" | |
#include "EnhancedInputComponent.h" | |
#include "EnhancedInputSubsystems.h" | |
ASimpleSpectatorPawn::ASimpleSpectatorPawn(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) | |
{ | |
PrimaryActorTick.bCanEverTick = true; | |
PrimaryActorTick.bStartWithTickEnabled = true; | |
bUseControllerRotationYaw = true; | |
bUseControllerRotationPitch = true; | |
SphereComponent = ObjectInitializer.CreateDefaultSubobject<USphereComponent>(this, TEXT("SphereComponent")); | |
SphereComponent->InitSphereRadius(20.0f); | |
SphereComponent->SetCollisionProfileName(TEXT("Pawn")); | |
SphereComponent->SetGenerateOverlapEvents(false); | |
SphereComponent->CanCharacterStepUpOn = ECB_No; | |
SphereComponent->bApplyImpulseOnDamage = false; | |
SphereComponent->bReplicatePhysicsToAutonomousProxy = false; | |
RootComponent = SphereComponent; | |
CameraComponent = ObjectInitializer.CreateDefaultSubobject<UCameraComponent>(this, TEXT("CameraComponent")); | |
CameraComponent->SetupAttachment(SphereComponent.Get()); | |
FloatingPawnMovement = ObjectInitializer.CreateDefaultSubobject<UFloatingPawnMovement>(this, TEXT("FloatingPawnMovement")); | |
FloatingPawnMovement->UpdatedComponent = SphereComponent; | |
MinSpeed = 10.0f; | |
MaxSpeed = 1000.0f; | |
SpeedIncrement = 10.0f; | |
NormalFieldOfView = 90.0f; | |
ZoomFieldOfView = 30.0f; | |
bIsZooming = false; | |
CurrentFieldOfView = 0.0f; | |
} | |
void ASimpleSpectatorPawn::BeginPlay() | |
{ | |
Super::BeginPlay(); | |
CurrentFieldOfView = NormalFieldOfView; | |
ResetInputValue(); | |
} | |
void ASimpleSpectatorPawn::TickActor(const float DeltaTime, const ELevelTick TickType, FActorTickFunction& ThisTickFunction) | |
{ | |
Super::TickActor(DeltaTime, TickType, ThisTickFunction); | |
const float TargetFieldOfView = bIsZooming ? ZoomFieldOfView : NormalFieldOfView; | |
if (FMath::IsNearlyEqual(CameraComponent->FieldOfView, TargetFieldOfView, 0.1f)) | |
{ | |
CameraComponent->FieldOfView = TargetFieldOfView; | |
} | |
else | |
{ | |
CameraComponent->FieldOfView = FMath::InterpEaseOut(CameraComponent->FieldOfView, TargetFieldOfView, DeltaTime, 10.0f); | |
} | |
} | |
void ASimpleSpectatorPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) | |
{ | |
Super::SetupPlayerInputComponent(PlayerInputComponent); | |
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent)) | |
{ | |
EnhancedInputComponent->BindAction(InputActionMove.Get(), ETriggerEvent::Triggered, this, &ASimpleSpectatorPawn::OnInputMoveTriggered); | |
EnhancedInputComponent->BindAction(InputActionLook.Get(), ETriggerEvent::Triggered, this, &ASimpleSpectatorPawn::OnInputLookTriggered); | |
EnhancedInputComponent->BindAction(InputActionSpeed.Get(), ETriggerEvent::Triggered, this, &ASimpleSpectatorPawn::OnInputSpeedTriggered); | |
EnhancedInputComponent->BindAction(InputActionZoom.Get(), ETriggerEvent::Started, this, &ASimpleSpectatorPawn::OnInputZoomPressed); | |
EnhancedInputComponent->BindAction(InputActionZoom.Get(), ETriggerEvent::Canceled, this, &ASimpleSpectatorPawn::OnInputZoomReleased); | |
EnhancedInputComponent->BindAction(InputActionZoom.Get(), ETriggerEvent::Completed, this, &ASimpleSpectatorPawn::OnInputZoomReleased); | |
} | |
} | |
void ASimpleSpectatorPawn::PossessedBy(AController* NewController) | |
{ | |
Super::PossessedBy(NewController); | |
check(InputMappingContext); | |
PlayerController = Cast<APlayerController>(NewController); | |
if (!PlayerController) | |
return; | |
if (UEnhancedInputLocalPlayerSubsystem* EnhancedInputLocalPlayerSubsystem = PlayerController->GetLocalPlayer()->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>()) | |
{ | |
EnhancedInputLocalPlayerSubsystem->AddMappingContext(InputMappingContext.Get(), 0); | |
} | |
} | |
void ASimpleSpectatorPawn::UnPossessed() | |
{ | |
if (!PlayerController) | |
{ | |
Super::UnPossessed(); | |
return; | |
} | |
check(InputMappingContext); | |
if (UEnhancedInputLocalPlayerSubsystem* EnhancedInputLocalPlayerSubsystem = PlayerController->GetLocalPlayer()->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>()) | |
{ | |
EnhancedInputLocalPlayerSubsystem->RemoveMappingContext(InputMappingContext.Get()); | |
} | |
ResetInputValue(); | |
Super::UnPossessed(); | |
} | |
float ASimpleSpectatorPawn::GetSpeed() const | |
{ | |
return FloatingPawnMovement->MaxSpeed; | |
} | |
float ASimpleSpectatorPawn::GetFieldOfView() const | |
{ | |
return CameraComponent->FieldOfView; | |
} | |
void ASimpleSpectatorPawn::ResetInputValue() | |
{ | |
bIsZooming = false; | |
FloatingPawnMovement->MaxSpeed = FMath::Lerp(MinSpeed, MaxSpeed, 0.5f); | |
CameraComponent->FieldOfView = NormalFieldOfView; | |
} | |
// ReSharper disable once CppMemberFunctionMayBeConst - Delegate Event | |
void ASimpleSpectatorPawn::OnInputMoveTriggered(const FInputActionValue& InputActionValue) | |
{ | |
const FVector2D InputValue = InputActionValue.Get<FVector2D>(); | |
const FVector ForwardVector = GetActorForwardVector(); | |
const FVector RightVector = GetActorRightVector(); | |
const FVector MovementDirection = ForwardVector * InputValue.Y + RightVector * InputValue.X; | |
FloatingPawnMovement->AddInputVector(MovementDirection); | |
} | |
// ReSharper disable once CppMemberFunctionMayBeConst - Delegate Event | |
void ASimpleSpectatorPawn::OnInputLookTriggered(const FInputActionValue& InputActionValue) | |
{ | |
const FVector2D InputValue = InputActionValue.Get<FVector2D>(); | |
const FRotator CurrentRotation = GetControlRotation(); | |
const FRotator DeltaRotation = FRotator(-InputValue.Y, InputValue.X, 0.0f); | |
const FRotator NewRotation = CurrentRotation + DeltaRotation; | |
if (Controller) | |
{ | |
Controller->SetControlRotation(NewRotation); | |
} | |
} | |
// ReSharper disable once CppMemberFunctionMayBeConst - Delegate Event | |
void ASimpleSpectatorPawn::OnInputSpeedTriggered(const FInputActionValue& InputActionValue) | |
{ | |
const float InputValue = InputActionValue.Get<float>(); | |
FloatingPawnMovement->MaxSpeed = FMath::Clamp(FloatingPawnMovement->MaxSpeed + InputValue * SpeedIncrement, MinSpeed, MaxSpeed); | |
if (OnSpeedChanged.IsBound()) | |
OnSpeedChanged.Broadcast(FloatingPawnMovement->MaxSpeed); | |
#if WITH_EDITOR | |
UE_LOG(LogTemp, Log, TEXT("Speed: %f"), FloatingPawnMovement->MaxSpeed); | |
#endif | |
} | |
void ASimpleSpectatorPawn::OnInputZoomPressed() | |
{ | |
bIsZooming = true; | |
if (OnZoomChanged.IsBound()) | |
OnZoomChanged.Broadcast(true); | |
#if WITH_EDITOR | |
UE_LOG(LogTemp, Log, TEXT("Zoom: true")); | |
#endif | |
} | |
void ASimpleSpectatorPawn::OnInputZoomReleased() | |
{ | |
bIsZooming = false; | |
if (OnZoomChanged.IsBound()) | |
OnZoomChanged.Broadcast(false); | |
#if WITH_EDITOR | |
UE_LOG(LogTemp, Log, TEXT("Zoom: false")); | |
#endif | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2025 Thanut Panichyotai (@LuviKunG) All Rights Reserved. | |
// ReSharper disable CppUEBlueprintCallableFunctionUnused | |
#pragma once | |
#include "CoreMinimal.h" | |
#include "InputMappingContext.h" | |
#include "Camera/CameraComponent.h" | |
#include "Components/SphereComponent.h" | |
#include "GameFramework/FloatingPawnMovement.h" | |
#include "GameFramework/Pawn.h" | |
#include "SimpleSpectatorPawn.generated.h" | |
UCLASS(Abstract) | |
class ASimpleSpectatorPawn : public APawn | |
{ | |
GENERATED_BODY() | |
public: | |
explicit ASimpleSpectatorPawn(const FObjectInitializer& ObjectInitializer); | |
virtual void BeginPlay() override; | |
virtual void TickActor(float DeltaTime, ELevelTick TickType, FActorTickFunction& ThisTickFunction) override; | |
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override; | |
virtual void PossessedBy(AController* NewController) override; | |
virtual void UnPossessed() override; | |
UDELEGATE() | |
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpeedChangeDelegate, const float&, Speed); | |
UPROPERTY(BlueprintAssignable, Category = "Pawn") | |
FSpeedChangeDelegate OnSpeedChanged; | |
UDELEGATE() | |
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FZoomChangeDelegate, const bool, bIsZooming); | |
UPROPERTY(BlueprintAssignable, Category = "Pawn") | |
FZoomChangeDelegate OnZoomChanged; | |
UFUNCTION(BlueprintPure, Category = "Pawn") | |
float GetSpeed() const; | |
UFUNCTION(BlueprintPure, Category = "Pawn") | |
float GetFieldOfView() const; | |
protected: | |
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Components") | |
TObjectPtr<USphereComponent> SphereComponent; | |
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Components") | |
TObjectPtr<UCameraComponent> CameraComponent; | |
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Components") | |
TObjectPtr<UFloatingPawnMovement> FloatingPawnMovement; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inputs") | |
TObjectPtr<UInputMappingContext> InputMappingContext; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inputs") | |
TObjectPtr<UInputAction> InputActionMove; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inputs") | |
TObjectPtr<UInputAction> InputActionLook; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inputs") | |
TObjectPtr<UInputAction> InputActionSpeed; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inputs") | |
TObjectPtr<UInputAction> InputActionZoom; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Pawn|Speed") | |
float MinSpeed; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Pawn|Speed") | |
float MaxSpeed; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Pawn|Speed") | |
float SpeedIncrement; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Pawn|Zoom") | |
float NormalFieldOfView; | |
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Pawn|Zoom") | |
float ZoomFieldOfView; | |
UPROPERTY(BlueprintReadOnly, Category = "Pawn|Controller") | |
TObjectPtr<APlayerController> PlayerController; | |
bool bIsZooming; | |
float CurrentFieldOfView; | |
void ResetInputValue(); | |
private: | |
UFUNCTION() | |
void OnInputMoveTriggered(const FInputActionValue& InputActionValue); | |
UFUNCTION() | |
void OnInputLookTriggered(const FInputActionValue& InputActionValue); | |
UFUNCTION() | |
void OnInputSpeedTriggered(const FInputActionValue& InputActionValue); | |
UFUNCTION() | |
void OnInputZoomPressed(); | |
UFUNCTION() | |
void OnInputZoomReleased(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment