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:
2026-04-10 12:33:55 +02:00
parent 17e8d5b194
commit b089a20512
121 changed files with 4434 additions and 1 deletions

View File

@@ -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();
}

View File

@@ -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;
};

View File

@@ -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;
}
}

View File

@@ -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;
};

View File

@@ -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));
}

View File

@@ -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;
};

View 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"
});
}
}

View 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)

View 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;
};

View File

@@ -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);
}
}
}

View 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;
};

View File

@@ -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();
}

View File

@@ -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;
};