Add rotate/scale gizmo modes, hotkeys, and delete

- E/R/T hotkeys to switch Translate/Rotate/Scale modes
- Rotate: drag gizmo axis to rotate selection around that axis
- Scale: drag gizmo axis to scale selection on that axis
- Delete key to destroy selected actors
- Movement keys: arrows only + A (up) / Q (down)
- Removed ZQSD movement to free letter keys for hotkeys

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 15:47:20 +02:00
parent 7884d1de59
commit 30c0078a1d
2 changed files with 168 additions and 16 deletions

View File

@@ -122,29 +122,23 @@ void APS_Editor_Pawn::CreateInputActions()
Mapping.Modifiers.Add(NewObject<UInputModifierNegate>(this));
};
// ZQSD (AZERTY)
MapForward(EKeys::Z);
MapBackward(EKeys::S);
MapRight(EKeys::D);
MapLeft(EKeys::Q);
// Arrow keys
// Arrow keys only (letter keys reserved for hotkeys)
MapForward(EKeys::Up);
MapBackward(EKeys::Down);
MapRight(EKeys::Right);
MapLeft(EKeys::Left);
// E - Up (+Z)
// A - Up (+Z)
{
FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::E);
FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::A);
UInputModifierSwizzleAxis* Swizzle = NewObject<UInputModifierSwizzleAxis>(this);
Swizzle->Order = EInputAxisSwizzle::ZYX;
Mapping.Modifiers.Add(Swizzle);
}
// A - Down (-Z)
// Q - Down (-Z)
{
FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::A);
FEnhancedActionKeyMapping& Mapping = EditorMappingContext->MapKey(IA_Move, EKeys::Q);
UInputModifierSwizzleAxis* Swizzle = NewObject<UInputModifierSwizzleAxis>(this);
Swizzle->Order = EInputAxisSwizzle::ZYX;
Mapping.Modifiers.Add(Swizzle);
@@ -157,6 +151,23 @@ void APS_Editor_Pawn::CreateInputActions()
EditorMappingContext->MapKey(IA_LeftClick, EKeys::LeftMouseButton);
EditorMappingContext->MapKey(IA_RightClick, EKeys::RightMouseButton);
EditorMappingContext->MapKey(IA_MiddleClick, EKeys::MiddleMouseButton);
// Transform mode hotkeys (E/R/T)
IA_TranslateMode = NewObject<UInputAction>(this, TEXT("IA_TranslateMode"));
IA_TranslateMode->ValueType = EInputActionValueType::Boolean;
EditorMappingContext->MapKey(IA_TranslateMode, EKeys::E);
IA_RotateMode = NewObject<UInputAction>(this, TEXT("IA_RotateMode"));
IA_RotateMode->ValueType = EInputActionValueType::Boolean;
EditorMappingContext->MapKey(IA_RotateMode, EKeys::R);
IA_ScaleMode = NewObject<UInputAction>(this, TEXT("IA_ScaleMode"));
IA_ScaleMode->ValueType = EInputActionValueType::Boolean;
EditorMappingContext->MapKey(IA_ScaleMode, EKeys::T);
IA_Delete = NewObject<UInputAction>(this, TEXT("IA_Delete"));
IA_Delete->ValueType = EInputActionValueType::Boolean;
EditorMappingContext->MapKey(IA_Delete, EKeys::Delete);
}
void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
@@ -188,6 +199,12 @@ void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComp
EIC->BindAction(IA_MiddleClick, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleMiddleClickStarted);
EIC->BindAction(IA_MiddleClick, ETriggerEvent::Completed, this, &APS_Editor_Pawn::HandleMiddleClickCompleted);
// Transform mode hotkeys (only trigger on press, not hold)
EIC->BindAction(IA_TranslateMode, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleTranslateMode);
EIC->BindAction(IA_RotateMode, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleRotateMode);
EIC->BindAction(IA_ScaleMode, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleScaleMode);
EIC->BindAction(IA_Delete, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleDelete);
}
// ---- Input Handlers ----
@@ -295,15 +312,74 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
const FVector AxisDir = Gizmo->GetAxisDirection(ActiveGizmoAxis);
const FVector TotalDelta = CurrentIntersection - GizmoDragInitialIntersection;
const FVector ConstrainedDelta = FVector::DotProduct(TotalDelta, AxisDir) * AxisDir;
const EPS_Editor_TransformMode Mode = SM->GetTransformMode();
// Apply delta to all selected actors from their initial transforms
const TArray<TWeakObjectPtr<AActor>>& Selected = SM->GetSelectedActors();
for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i)
if (Mode == EPS_Editor_TransformMode::Translate)
{
if (AActor* Actor = Selected[i].Get())
const FVector ConstrainedDelta = FVector::DotProduct(TotalDelta, AxisDir) * AxisDir;
for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i)
{
Actor->SetActorLocation(GizmoDragInitialTransforms[i].GetLocation() + ConstrainedDelta);
if (AActor* Actor = Selected[i].Get())
{
Actor->SetActorLocation(GizmoDragInitialTransforms[i].GetLocation() + ConstrainedDelta);
}
}
}
else if (Mode == EPS_Editor_TransformMode::Rotate)
{
// Compute rotation angle from initial to current intersection around the axis
const FVector GizmoLoc = GizmoDragPlaneOrigin;
const FVector InitDir = (GizmoDragInitialIntersection - GizmoLoc).GetSafeNormal();
const FVector CurrDir = (CurrentIntersection - GizmoLoc).GetSafeNormal();
// Signed angle via cross product
float Angle = FMath::Atan2(
FVector::DotProduct(FVector::CrossProduct(InitDir, CurrDir), AxisDir),
FVector::DotProduct(InitDir, CurrDir));
Angle = FMath::RadiansToDegrees(Angle);
const FQuat RotationQuat(AxisDir, FMath::DegreesToRadians(Angle));
for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i)
{
if (AActor* Actor = Selected[i].Get())
{
// Rotate around gizmo pivot
const FVector Offset = GizmoDragInitialTransforms[i].GetLocation() - GizmoLoc;
const FVector RotatedOffset = RotationQuat.RotateVector(Offset);
Actor->SetActorLocation(GizmoLoc + RotatedOffset);
// Also rotate the actor itself
const FQuat OrigRot = GizmoDragInitialTransforms[i].GetRotation();
Actor->SetActorRotation((RotationQuat * OrigRot).Rotator());
}
}
}
else if (Mode == EPS_Editor_TransformMode::Scale)
{
// Scale factor from mouse movement along the axis
const float InitialDist = FVector::DotProduct(GizmoDragInitialIntersection - GizmoDragPlaneOrigin, AxisDir);
const float CurrentDist = FVector::DotProduct(CurrentIntersection - GizmoDragPlaneOrigin, AxisDir);
float ScaleFactor = 1.0f;
if (FMath::Abs(InitialDist) > KINDA_SMALL_NUMBER)
{
ScaleFactor = FMath::Clamp(CurrentDist / InitialDist, 0.01f, 100.0f);
}
for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i)
{
if (AActor* Actor = Selected[i].Get())
{
FVector NewScale = GizmoDragInitialTransforms[i].GetScale3D();
// Scale only on the dragged axis
if (ActiveGizmoAxis == EPS_Editor_GizmoAxis::X) NewScale.X *= ScaleFactor;
else if (ActiveGizmoAxis == EPS_Editor_GizmoAxis::Y) NewScale.Y *= ScaleFactor;
else if (ActiveGizmoAxis == EPS_Editor_GizmoAxis::Z) NewScale.Z *= ScaleFactor;
Actor->SetActorScale3D(NewScale);
}
}
}
@@ -412,6 +488,66 @@ void APS_Editor_Pawn::HandleMiddleClickCompleted(const FInputActionValue& Value)
}
}
void APS_Editor_Pawn::HandleTranslateMode(const FInputActionValue& Value)
{
if (CameraMode != EPS_Editor_CameraMode::Idle) return;
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController()))
{
if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager())
{
SM->SetTransformMode(EPS_Editor_TransformMode::Translate);
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Translate mode"));
}
}
}
void APS_Editor_Pawn::HandleRotateMode(const FInputActionValue& Value)
{
if (CameraMode != EPS_Editor_CameraMode::Idle) return;
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController()))
{
if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager())
{
SM->SetTransformMode(EPS_Editor_TransformMode::Rotate);
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Rotate mode"));
}
}
}
void APS_Editor_Pawn::HandleScaleMode(const FInputActionValue& Value)
{
if (CameraMode != EPS_Editor_CameraMode::Idle) return;
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController()))
{
if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager())
{
SM->SetTransformMode(EPS_Editor_TransformMode::Scale);
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Scale mode"));
}
}
}
void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value)
{
if (CameraMode != EPS_Editor_CameraMode::Idle) return;
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController()))
{
if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager())
{
TArray<TWeakObjectPtr<AActor>> ToDelete = SM->GetSelectedActors();
SM->ClearSelection();
for (const TWeakObjectPtr<AActor>& Weak : ToDelete)
{
if (AActor* Actor = Weak.Get())
{
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Deleted %s"), *Actor->GetName());
Actor->Destroy();
}
}
}
}
}
// ---- Helpers ----
void APS_Editor_Pawn::SetCameraMode(EPS_Editor_CameraMode NewMode)

View File

@@ -146,6 +146,18 @@ private:
UPROPERTY()
TObjectPtr<UInputAction> IA_MiddleClick;
UPROPERTY()
TObjectPtr<UInputAction> IA_TranslateMode;
UPROPERTY()
TObjectPtr<UInputAction> IA_RotateMode;
UPROPERTY()
TObjectPtr<UInputAction> IA_ScaleMode;
UPROPERTY()
TObjectPtr<UInputAction> IA_Delete;
// ---- Modifier key state ----
bool bAltHeld = false;
bool bCtrlHeld = false;
@@ -170,6 +182,10 @@ private:
void HandleRightClickCompleted(const FInputActionValue& Value);
void HandleMiddleClickStarted(const FInputActionValue& Value);
void HandleMiddleClickCompleted(const FInputActionValue& Value);
void HandleTranslateMode(const FInputActionValue& Value);
void HandleRotateMode(const FInputActionValue& Value);
void HandleScaleMode(const FInputActionValue& Value);
void HandleDelete(const FInputActionValue& Value);
// ---- Helpers ----
void CreateInputActions();