Add PS_Editor plugin Phase 1: GameMode + Camera navigation
Runtime editor plugin with UE viewport-style camera controls: - ZQSD/Arrows movement (always active), E/A for up/down - LMB: pan (horizontal move + yaw), RMB: free fly look - Alt+LMB / MMB: orbit around focal point, scroll: zoom - HUD with controls help, cursor auto-hide during camera ops Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
#include "PS_Editor_GameMode.h"
|
||||
#include "PS_Editor_Pawn.h"
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
#include "PS_Editor_HUD.h"
|
||||
|
||||
APS_Editor_GameMode::APS_Editor_GameMode()
|
||||
{
|
||||
DefaultPawnClass = APS_Editor_Pawn::StaticClass();
|
||||
PlayerControllerClass = APS_Editor_PlayerController::StaticClass();
|
||||
HUDClass = APS_Editor_HUD::StaticClass();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
#include "PS_Editor_GameMode.generated.h"
|
||||
|
||||
/**
|
||||
* Game mode for the PS_Editor runtime editing mode.
|
||||
* Sets up the editor pawn, controller, and HUD.
|
||||
*/
|
||||
UCLASS()
|
||||
class PS_EDITOR_API APS_Editor_GameMode : public AGameModeBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
APS_Editor_GameMode();
|
||||
|
||||
/** Whether the editor is currently in edit mode (vs play/preview mode). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
||||
bool bIsEditMode = true;
|
||||
};
|
||||
@@ -0,0 +1,362 @@
|
||||
#include "PS_Editor_Pawn.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "EnhancedInputComponent.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "InputAction.h"
|
||||
#include "InputMappingContext.h"
|
||||
#include "InputModifiers.h"
|
||||
#include "InputTriggers.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
|
||||
APS_Editor_Pawn::APS_Editor_Pawn()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
|
||||
SetRootComponent(RootScene);
|
||||
|
||||
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
|
||||
SpringArm->SetupAttachment(RootScene);
|
||||
SpringArm->TargetArmLength = 0.0f;
|
||||
SpringArm->bDoCollisionTest = false;
|
||||
SpringArm->bEnableCameraLag = false;
|
||||
SpringArm->bUsePawnControlRotation = false;
|
||||
|
||||
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
|
||||
Camera->SetupAttachment(SpringArm);
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
|
||||
// Track Alt key state every frame
|
||||
if (APlayerController* PC = Cast<APlayerController>(GetController()))
|
||||
{
|
||||
bAltHeld = PC->IsInputKeyDown(EKeys::LeftAlt) || PC->IsInputKeyDown(EKeys::RightAlt);
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::CreateInputActions()
|
||||
{
|
||||
// --- Create Input Actions ---
|
||||
|
||||
IA_Move = NewObject<UInputAction>(this, TEXT("IA_Move"));
|
||||
IA_Move->ValueType = EInputActionValueType::Axis3D;
|
||||
IA_Move->bConsumeInput = false;
|
||||
|
||||
IA_Look = NewObject<UInputAction>(this, TEXT("IA_Look"));
|
||||
IA_Look->ValueType = EInputActionValueType::Axis2D;
|
||||
|
||||
IA_Scroll = NewObject<UInputAction>(this, TEXT("IA_Scroll"));
|
||||
IA_Scroll->ValueType = EInputActionValueType::Axis1D;
|
||||
|
||||
IA_LeftClick = NewObject<UInputAction>(this, TEXT("IA_LeftClick"));
|
||||
IA_LeftClick->ValueType = EInputActionValueType::Boolean;
|
||||
|
||||
IA_RightClick = NewObject<UInputAction>(this, TEXT("IA_RightClick"));
|
||||
IA_RightClick->ValueType = EInputActionValueType::Boolean;
|
||||
|
||||
IA_MiddleClick = NewObject<UInputAction>(this, TEXT("IA_MiddleClick"));
|
||||
IA_MiddleClick->ValueType = EInputActionValueType::Boolean;
|
||||
|
||||
// --- Create Mapping Context ---
|
||||
EditorMappingContext = NewObject<UInputMappingContext>(this, TEXT("EditorMappingContext"));
|
||||
|
||||
// Movement helpers
|
||||
auto MapForward = [&](FKey Key)
|
||||
{
|
||||
FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, Key);
|
||||
UInputModifierSwizzleAxis* Swizzle = NewObject<UInputModifierSwizzleAxis>(this);
|
||||
Swizzle->Order = EInputAxisSwizzle::YXZ;
|
||||
Mapping.Modifiers.Add(Swizzle);
|
||||
};
|
||||
|
||||
auto MapBackward = [&](FKey Key)
|
||||
{
|
||||
FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, Key);
|
||||
UInputModifierSwizzleAxis* Swizzle = NewObject<UInputModifierSwizzleAxis>(this);
|
||||
Swizzle->Order = EInputAxisSwizzle::YXZ;
|
||||
Mapping.Modifiers.Add(Swizzle);
|
||||
Mapping.Modifiers.Add(NewObject<UInputModifierNegate>(this));
|
||||
};
|
||||
|
||||
auto MapRight = [&](FKey Key)
|
||||
{
|
||||
EditorMappingContext->MapKey(IA_Move, Key);
|
||||
};
|
||||
|
||||
auto MapLeft = [&](FKey Key)
|
||||
{
|
||||
FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, Key);
|
||||
Mapping.Modifiers.Add(NewObject<UInputModifierNegate>(this));
|
||||
};
|
||||
|
||||
// ZQSD (AZERTY)
|
||||
MapForward(EKeys::Z);
|
||||
MapBackward(EKeys::S);
|
||||
MapRight(EKeys::D);
|
||||
MapLeft(EKeys::Q);
|
||||
|
||||
// Arrow keys
|
||||
MapForward(EKeys::Up);
|
||||
MapBackward(EKeys::Down);
|
||||
MapRight(EKeys::Right);
|
||||
MapLeft(EKeys::Left);
|
||||
|
||||
// E - Up (+Z)
|
||||
{
|
||||
FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::E);
|
||||
UInputModifierSwizzleAxis* Swizzle = NewObject<UInputModifierSwizzleAxis>(this);
|
||||
Swizzle->Order = EInputAxisSwizzle::ZYX;
|
||||
Mapping.Modifiers.Add(Swizzle);
|
||||
}
|
||||
|
||||
// A - Down (-Z)
|
||||
{
|
||||
FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::A);
|
||||
UInputModifierSwizzleAxis* Swizzle = NewObject<UInputModifierSwizzleAxis>(this);
|
||||
Swizzle->Order = EInputAxisSwizzle::ZYX;
|
||||
Mapping.Modifiers.Add(Swizzle);
|
||||
Mapping.Modifiers.Add(NewObject<UInputModifierNegate>(this));
|
||||
}
|
||||
|
||||
// Mouse
|
||||
EditorMappingContext->MapKey(IA_Look, EKeys::Mouse2D);
|
||||
EditorMappingContext->MapKey(IA_Scroll, EKeys::MouseWheelAxis);
|
||||
EditorMappingContext->MapKey(IA_LeftClick, EKeys::LeftMouseButton);
|
||||
EditorMappingContext->MapKey(IA_RightClick, EKeys::RightMouseButton);
|
||||
EditorMappingContext->MapKey(IA_MiddleClick, EKeys::MiddleMouseButton);
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||
{
|
||||
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
||||
|
||||
CreateInputActions();
|
||||
|
||||
if (APlayerController* PC = Cast<APlayerController>(GetController()))
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
|
||||
{
|
||||
Subsystem->ClearAllMappings();
|
||||
Subsystem->AddMappingContext(EditorMappingContext, 0);
|
||||
}
|
||||
}
|
||||
|
||||
UEnhancedInputComponent* EIC = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);
|
||||
|
||||
EIC->BindAction(IA_Move, ETriggerEvent::Triggered, this, &APS_Editor_Pawn::HandleMove);
|
||||
EIC->BindAction(IA_Look, ETriggerEvent::Triggered, this, &APS_Editor_Pawn::HandleLook);
|
||||
EIC->BindAction(IA_Scroll, ETriggerEvent::Triggered, this, &APS_Editor_Pawn::HandleScroll);
|
||||
|
||||
EIC->BindAction(IA_LeftClick, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleLeftClickStarted);
|
||||
EIC->BindAction(IA_LeftClick, ETriggerEvent::Completed, this, &APS_Editor_Pawn::HandleLeftClickCompleted);
|
||||
|
||||
EIC->BindAction(IA_RightClick, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleRightClickStarted);
|
||||
EIC->BindAction(IA_RightClick, ETriggerEvent::Completed, this, &APS_Editor_Pawn::HandleRightClickCompleted);
|
||||
|
||||
EIC->BindAction(IA_MiddleClick, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleMiddleClickStarted);
|
||||
EIC->BindAction(IA_MiddleClick, ETriggerEvent::Completed, this, &APS_Editor_Pawn::HandleMiddleClickCompleted);
|
||||
}
|
||||
|
||||
// ---- Input Handlers ----
|
||||
|
||||
void APS_Editor_Pawn::HandleMove(const FInputActionValue& Value)
|
||||
{
|
||||
const FVector MoveInput = Value.Get<FVector>();
|
||||
const float DeltaTime = GetWorld()->GetDeltaSeconds();
|
||||
|
||||
const FRotator Rotation = GetActorRotation();
|
||||
const FVector Forward = FRotationMatrix(Rotation).GetUnitAxis(EAxis::X);
|
||||
const FVector Right = FRotationMatrix(Rotation).GetUnitAxis(EAxis::Y);
|
||||
const FVector Up = FVector::UpVector;
|
||||
|
||||
const FVector Movement = (Forward * MoveInput.Y) + (Right * MoveInput.X) + (Up * MoveInput.Z);
|
||||
AddActorWorldOffset(Movement * FlySpeed * DeltaTime, true);
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
||||
{
|
||||
if (CameraMode == EPS_Editor_CameraMode::Idle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector2D LookInput = Value.Get<FVector2D>();
|
||||
|
||||
if (CameraMode == EPS_Editor_CameraMode::Pan)
|
||||
{
|
||||
// Left-click drag: mouse X = yaw rotation (Z axis only), mouse Y = move forward/back on horizontal plane
|
||||
const float YawDelta = LookInput.X * PanYawSensitivity;
|
||||
FRotator CurrentRotation = GetActorRotation();
|
||||
CurrentRotation.Yaw += YawDelta;
|
||||
SetActorRotation(CurrentRotation);
|
||||
|
||||
// Forward/back movement on horizontal plane
|
||||
const FVector HorizontalForward = FVector(GetActorForwardVector().X, GetActorForwardVector().Y, 0.0f).GetSafeNormal();
|
||||
const FVector PanMovement = HorizontalForward * LookInput.Y * PanSpeed;
|
||||
AddActorWorldOffset(PanMovement, true);
|
||||
}
|
||||
else if (CameraMode == EPS_Editor_CameraMode::Fly)
|
||||
{
|
||||
// Right-click drag: full free look
|
||||
const float YawDelta = LookInput.X * LookSensitivity;
|
||||
const float PitchDelta = LookInput.Y * LookSensitivity;
|
||||
|
||||
FRotator CurrentRotation = GetActorRotation();
|
||||
CurrentRotation.Yaw += YawDelta;
|
||||
CurrentRotation.Pitch = FMath::Clamp(CurrentRotation.Pitch + PitchDelta, -89.0f, 89.0f);
|
||||
CurrentRotation.Roll = 0.0f;
|
||||
SetActorRotation(CurrentRotation);
|
||||
}
|
||||
else if (CameraMode == EPS_Editor_CameraMode::Orbit)
|
||||
{
|
||||
OrbitYaw += LookInput.X * OrbitSensitivity;
|
||||
OrbitPitch = FMath::Clamp(OrbitPitch + (LookInput.Y * OrbitSensitivity), -89.0f, 89.0f);
|
||||
|
||||
const FRotator OrbitRotation(OrbitPitch, OrbitYaw, 0.0f);
|
||||
const FVector Offset = OrbitRotation.Vector() * -OrbitDistance;
|
||||
|
||||
SetActorLocation(OrbitFocalPoint + Offset);
|
||||
SetActorRotation((OrbitFocalPoint - GetActorLocation()).Rotation());
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::HandleScroll(const FInputActionValue& Value)
|
||||
{
|
||||
const float ScrollValue = Value.Get<float>();
|
||||
|
||||
if (CameraMode == EPS_Editor_CameraMode::Fly)
|
||||
{
|
||||
FlySpeed = FMath::Clamp(FlySpeed + ScrollValue * FlySpeedScrollStep, FlySpeedMin, FlySpeedMax);
|
||||
}
|
||||
else if (CameraMode == EPS_Editor_CameraMode::Orbit)
|
||||
{
|
||||
OrbitDistance = FMath::Clamp(OrbitDistance - ScrollValue * OrbitZoomStep, OrbitDistanceMin, OrbitDistanceMax);
|
||||
|
||||
const FRotator OrbitRotation(OrbitPitch, OrbitYaw, 0.0f);
|
||||
const FVector Offset = OrbitRotation.Vector() * -OrbitDistance;
|
||||
SetActorLocation(OrbitFocalPoint + Offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Idle or Pan: dolly forward/backward
|
||||
const FVector Forward = GetActorForwardVector();
|
||||
AddActorWorldOffset(Forward * ScrollValue * OrbitZoomStep * 5.0f, true);
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
{
|
||||
if (bAltHeld)
|
||||
{
|
||||
// Alt + left-click = orbit
|
||||
ComputeOrbitFocalPoint();
|
||||
SetCameraMode(EPS_Editor_CameraMode::Orbit);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Left-click = pan (yaw + forward/back on horizontal plane)
|
||||
SetCameraMode(EPS_Editor_CameraMode::Pan);
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::HandleLeftClickCompleted(const FInputActionValue& Value)
|
||||
{
|
||||
if (CameraMode == EPS_Editor_CameraMode::Pan || CameraMode == EPS_Editor_CameraMode::Orbit)
|
||||
{
|
||||
SetCameraMode(EPS_Editor_CameraMode::Idle);
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::HandleRightClickStarted(const FInputActionValue& Value)
|
||||
{
|
||||
SetCameraMode(EPS_Editor_CameraMode::Fly);
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::HandleRightClickCompleted(const FInputActionValue& Value)
|
||||
{
|
||||
if (CameraMode == EPS_Editor_CameraMode::Fly)
|
||||
{
|
||||
SetCameraMode(EPS_Editor_CameraMode::Idle);
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::HandleMiddleClickStarted(const FInputActionValue& Value)
|
||||
{
|
||||
ComputeOrbitFocalPoint();
|
||||
SetCameraMode(EPS_Editor_CameraMode::Orbit);
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::HandleMiddleClickCompleted(const FInputActionValue& Value)
|
||||
{
|
||||
if (CameraMode == EPS_Editor_CameraMode::Orbit)
|
||||
{
|
||||
SetCameraMode(EPS_Editor_CameraMode::Idle);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Helpers ----
|
||||
|
||||
void APS_Editor_Pawn::SetCameraMode(EPS_Editor_CameraMode NewMode)
|
||||
{
|
||||
CameraMode = NewMode;
|
||||
|
||||
if (NewMode == EPS_Editor_CameraMode::Orbit)
|
||||
{
|
||||
const FRotator CurrentRotation = GetActorRotation();
|
||||
OrbitPitch = CurrentRotation.Pitch;
|
||||
OrbitYaw = CurrentRotation.Yaw;
|
||||
}
|
||||
|
||||
UpdateCursorVisibility();
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::UpdateCursorVisibility()
|
||||
{
|
||||
APlayerController* PC = Cast<APlayerController>(GetController());
|
||||
if (!PC)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool bShowCursor = (CameraMode == EPS_Editor_CameraMode::Idle);
|
||||
PC->bShowMouseCursor = bShowCursor;
|
||||
|
||||
if (bShowCursor)
|
||||
{
|
||||
PC->SetInputMode(FInputModeGameAndUI().SetHideCursorDuringCapture(false));
|
||||
}
|
||||
else
|
||||
{
|
||||
PC->SetInputMode(FInputModeGameOnly());
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::ComputeOrbitFocalPoint()
|
||||
{
|
||||
const FVector Start = GetActorLocation();
|
||||
const FVector Forward = GetActorForwardVector();
|
||||
const FVector End = Start + Forward * OrbitDistance;
|
||||
|
||||
FHitResult Hit;
|
||||
FCollisionQueryParams Params;
|
||||
Params.AddIgnoredActor(this);
|
||||
|
||||
if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params))
|
||||
{
|
||||
OrbitFocalPoint = Hit.ImpactPoint;
|
||||
OrbitDistance = FVector::Dist(Start, OrbitFocalPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
OrbitFocalPoint = End;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "InputActionValue.h"
|
||||
#include "PS_Editor_Pawn.generated.h"
|
||||
|
||||
class UCameraComponent;
|
||||
class USpringArmComponent;
|
||||
class UInputAction;
|
||||
class UInputMappingContext;
|
||||
|
||||
/**
|
||||
* Camera modes for the editor pawn.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EPS_Editor_CameraMode : uint8
|
||||
{
|
||||
/** Default mode - cursor visible, ZQSD/arrows still move. */
|
||||
Idle,
|
||||
/** Left-click held: mouse Y = forward/back on horizontal plane, mouse X = yaw rotation. */
|
||||
Pan,
|
||||
/** Right-click held: full free look + ZQSD movement. */
|
||||
Fly,
|
||||
/** Alt + left-click held: orbit around focal point. */
|
||||
Orbit
|
||||
};
|
||||
|
||||
/**
|
||||
* Editor camera pawn mimicking UE viewport navigation.
|
||||
*
|
||||
* Controls:
|
||||
* - Left-click held: Pan (mouse Y = move forward/back, mouse X = yaw)
|
||||
* - Right-click held: Fly (ZQSD + free mouse look)
|
||||
* - Alt + left-click held: Orbit around focal point
|
||||
* - Middle-click held: Orbit around focal point (alternative)
|
||||
* - ZQSD / Arrows: Always active movement
|
||||
* - E/A: Up/Down
|
||||
* - Mouse wheel: Zoom / adjust fly speed
|
||||
*/
|
||||
UCLASS()
|
||||
class PS_EDITOR_API APS_Editor_Pawn : public APawn
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
APS_Editor_Pawn();
|
||||
|
||||
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
// ---- Components ----
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
||||
TObjectPtr<USceneComponent> RootScene;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
||||
TObjectPtr<USpringArmComponent> SpringArm;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
||||
TObjectPtr<UCameraComponent> Camera;
|
||||
|
||||
// ---- Camera Settings ----
|
||||
|
||||
/** Base movement speed in fly mode (cm/s). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Fly")
|
||||
float FlySpeed = 1000.0f;
|
||||
|
||||
/** Minimum fly speed. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Fly")
|
||||
float FlySpeedMin = 100.0f;
|
||||
|
||||
/** Maximum fly speed. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Fly")
|
||||
float FlySpeedMax = 10000.0f;
|
||||
|
||||
/** Mouse look sensitivity. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Fly")
|
||||
float LookSensitivity = 2.5f;
|
||||
|
||||
/** Speed change per scroll tick. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Fly")
|
||||
float FlySpeedScrollStep = 200.0f;
|
||||
|
||||
/** Pan speed multiplier (left-click drag forward/back). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Pan")
|
||||
float PanSpeed = 5.0f;
|
||||
|
||||
/** Pan yaw sensitivity (left-click drag left/right). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Pan")
|
||||
float PanYawSensitivity = 1.0f;
|
||||
|
||||
/** Orbit distance from focal point. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Orbit")
|
||||
float OrbitDistance = 500.0f;
|
||||
|
||||
/** Orbit zoom step per scroll tick. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Orbit")
|
||||
float OrbitZoomStep = 50.0f;
|
||||
|
||||
/** Minimum orbit distance. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Orbit")
|
||||
float OrbitDistanceMin = 50.0f;
|
||||
|
||||
/** Maximum orbit distance. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Orbit")
|
||||
float OrbitDistanceMax = 5000.0f;
|
||||
|
||||
/** Orbit rotation sensitivity. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Orbit")
|
||||
float OrbitSensitivity = 1.0f;
|
||||
|
||||
// ---- State ----
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
||||
EPS_Editor_CameraMode CameraMode = EPS_Editor_CameraMode::Idle;
|
||||
|
||||
/** The point in world space the orbit camera rotates around. */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera|Orbit")
|
||||
FVector OrbitFocalPoint = FVector::ZeroVector;
|
||||
|
||||
private:
|
||||
// ---- Input Actions (created at runtime) ----
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInputMappingContext> EditorMappingContext;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInputAction> IA_Move;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInputAction> IA_Look;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInputAction> IA_Scroll;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInputAction> IA_LeftClick;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInputAction> IA_RightClick;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInputAction> IA_MiddleClick;
|
||||
|
||||
/** Is the Alt key currently held down. */
|
||||
bool bAltHeld = false;
|
||||
|
||||
// ---- Input Handlers ----
|
||||
void HandleMove(const FInputActionValue& Value);
|
||||
void HandleLook(const FInputActionValue& Value);
|
||||
void HandleScroll(const FInputActionValue& Value);
|
||||
void HandleLeftClickStarted(const FInputActionValue& Value);
|
||||
void HandleLeftClickCompleted(const FInputActionValue& Value);
|
||||
void HandleRightClickStarted(const FInputActionValue& Value);
|
||||
void HandleRightClickCompleted(const FInputActionValue& Value);
|
||||
void HandleMiddleClickStarted(const FInputActionValue& Value);
|
||||
void HandleMiddleClickCompleted(const FInputActionValue& Value);
|
||||
|
||||
// ---- Helpers ----
|
||||
void CreateInputActions();
|
||||
void SetCameraMode(EPS_Editor_CameraMode NewMode);
|
||||
void UpdateCursorVisibility();
|
||||
void ComputeOrbitFocalPoint();
|
||||
|
||||
/** Current pitch accumulated during orbit (clamped). */
|
||||
float OrbitPitch = 0.0f;
|
||||
/** Current yaw accumulated during orbit. */
|
||||
float OrbitYaw = 0.0f;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
|
||||
APS_Editor_PlayerController::APS_Editor_PlayerController()
|
||||
{
|
||||
bShowMouseCursor = true;
|
||||
DefaultMouseCursor = EMouseCursor::Default;
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Start in Game+UI mode so cursor is visible and clicks go to both UI and game
|
||||
SetInputMode(FInputModeGameAndUI().SetHideCursorDuringCapture(false));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "PS_Editor_PlayerController.generated.h"
|
||||
|
||||
/**
|
||||
* Player controller for PS_Editor runtime editing.
|
||||
* Shows mouse cursor by default and handles input mode switching.
|
||||
*/
|
||||
UCLASS()
|
||||
class PS_EDITOR_API APS_Editor_PlayerController : public APlayerController
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
APS_Editor_PlayerController();
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
};
|
||||
33
Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs
Normal file
33
Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using UnrealBuildTool;
|
||||
using System.IO;
|
||||
|
||||
public class PS_Editor : ModuleRules
|
||||
{
|
||||
public PS_Editor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
// Add all subdirectories to include paths so headers can be included by name
|
||||
string SourceDir = Path.Combine(ModuleDirectory);
|
||||
PublicIncludePaths.Add(SourceDir);
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "GameMode"));
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "UI"));
|
||||
PublicIncludePaths.Add(Path.Combine(SourceDir, "UI", "Widgets"));
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"InputCore",
|
||||
"EnhancedInput",
|
||||
"UMG"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Slate",
|
||||
"SlateCore"
|
||||
});
|
||||
}
|
||||
}
|
||||
17
Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.cpp
Normal file
17
Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "PS_Editor.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FPS_EditorModule"
|
||||
|
||||
void FPS_EditorModule::StartupModule()
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor module started."));
|
||||
}
|
||||
|
||||
void FPS_EditorModule::ShutdownModule()
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor module shut down."));
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FPS_EditorModule, PS_Editor)
|
||||
11
Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.h
Normal file
11
Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FPS_EditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "PS_Editor_HUD.h"
|
||||
#include "PS_Editor_MainWidget.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
|
||||
void APS_Editor_HUD::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
if (APlayerController* PC = GetOwningPlayerController())
|
||||
{
|
||||
MainWidget = CreateWidget<UPS_Editor_MainWidget>(PC, UPS_Editor_MainWidget::StaticClass());
|
||||
if (MainWidget)
|
||||
{
|
||||
MainWidget->AddToViewport(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.h
Normal file
29
Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/HUD.h"
|
||||
#include "PS_Editor_HUD.generated.h"
|
||||
|
||||
class UPS_Editor_MainWidget;
|
||||
|
||||
/**
|
||||
* HUD for the PS_Editor runtime editing mode.
|
||||
* Creates and manages the main editor widget.
|
||||
*/
|
||||
UCLASS()
|
||||
class PS_EDITOR_API APS_Editor_HUD : public AHUD
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Returns the main editor widget instance. */
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|UI")
|
||||
UPS_Editor_MainWidget* GetMainWidget() const { return MainWidget; }
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
TObjectPtr<UPS_Editor_MainWidget> MainWidget;
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "PS_Editor_MainWidget.h"
|
||||
#include "Components/TextBlock.h"
|
||||
#include "Components/VerticalBox.h"
|
||||
#include "Components/VerticalBoxSlot.h"
|
||||
#include "Components/Border.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "Widgets/Layout/SBorder.h"
|
||||
#include "Widgets/SOverlay.h"
|
||||
|
||||
TSharedRef<SWidget> UPS_Editor_MainWidget::RebuildWidget()
|
||||
{
|
||||
// Build the widget tree using Slate (C++ driven UMG)
|
||||
return SNew(SOverlay)
|
||||
// Top bar
|
||||
+ SOverlay::Slot()
|
||||
.HAlign(HAlign_Left)
|
||||
.VAlign(VAlign_Top)
|
||||
.Padding(16.0f)
|
||||
[
|
||||
SNew(SBorder)
|
||||
.BorderBackgroundColor(FLinearColor(0.05f, 0.05f, 0.05f, 0.85f))
|
||||
.Padding(FMargin(12.0f, 8.0f))
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("PS Editor")))
|
||||
.ColorAndOpacity(FLinearColor(0.2f, 0.7f, 1.0f, 1.0f))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 14))
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.VAlign(VAlign_Center)
|
||||
.Padding(12.0f, 0.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Edit Mode")))
|
||||
.ColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.7f, 1.0f))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 11))
|
||||
]
|
||||
]
|
||||
]
|
||||
// Bottom help bar
|
||||
+ SOverlay::Slot()
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Bottom)
|
||||
.Padding(16.0f)
|
||||
[
|
||||
SNew(SBorder)
|
||||
.BorderBackgroundColor(FLinearColor(0.05f, 0.05f, 0.05f, 0.75f))
|
||||
.Padding(FMargin(16.0f, 6.0f))
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("LMB: Pan | RMB: Fly (ZQSD) | Alt+LMB / MMB: Orbit | E/A: Up/Down | Scroll: Zoom")))
|
||||
.ColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f, 1.0f))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "PS_Editor_MainWidget.generated.h"
|
||||
|
||||
class UTextBlock;
|
||||
class UVerticalBox;
|
||||
class UBorder;
|
||||
|
||||
/**
|
||||
* Main editor widget for PS_Editor.
|
||||
* Serves as the root container for all editor UI panels.
|
||||
* Built entirely in C++ (no UMG blueprint).
|
||||
*/
|
||||
UCLASS()
|
||||
class PS_EDITOR_API UPS_Editor_MainWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
virtual TSharedRef<SWidget> RebuildWidget() override;
|
||||
|
||||
virtual void NativeConstruct() override;
|
||||
};
|
||||
Reference in New Issue
Block a user