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:
@@ -122,29 +122,23 @@ void APS_Editor_Pawn::CreateInputActions()
|
|||||||
Mapping.Modifiers.Add(NewObject<UInputModifierNegate>(this));
|
Mapping.Modifiers.Add(NewObject<UInputModifierNegate>(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ZQSD (AZERTY)
|
// Arrow keys only (letter keys reserved for hotkeys)
|
||||||
MapForward(EKeys::Z);
|
|
||||||
MapBackward(EKeys::S);
|
|
||||||
MapRight(EKeys::D);
|
|
||||||
MapLeft(EKeys::Q);
|
|
||||||
|
|
||||||
// Arrow keys
|
|
||||||
MapForward(EKeys::Up);
|
MapForward(EKeys::Up);
|
||||||
MapBackward(EKeys::Down);
|
MapBackward(EKeys::Down);
|
||||||
MapRight(EKeys::Right);
|
MapRight(EKeys::Right);
|
||||||
MapLeft(EKeys::Left);
|
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);
|
UInputModifierSwizzleAxis* Swizzle = NewObject<UInputModifierSwizzleAxis>(this);
|
||||||
Swizzle->Order = EInputAxisSwizzle::ZYX;
|
Swizzle->Order = EInputAxisSwizzle::ZYX;
|
||||||
Mapping.Modifiers.Add(Swizzle);
|
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);
|
UInputModifierSwizzleAxis* Swizzle = NewObject<UInputModifierSwizzleAxis>(this);
|
||||||
Swizzle->Order = EInputAxisSwizzle::ZYX;
|
Swizzle->Order = EInputAxisSwizzle::ZYX;
|
||||||
Mapping.Modifiers.Add(Swizzle);
|
Mapping.Modifiers.Add(Swizzle);
|
||||||
@@ -157,6 +151,23 @@ void APS_Editor_Pawn::CreateInputActions()
|
|||||||
EditorMappingContext->MapKey(IA_LeftClick, EKeys::LeftMouseButton);
|
EditorMappingContext->MapKey(IA_LeftClick, EKeys::LeftMouseButton);
|
||||||
EditorMappingContext->MapKey(IA_RightClick, EKeys::RightMouseButton);
|
EditorMappingContext->MapKey(IA_RightClick, EKeys::RightMouseButton);
|
||||||
EditorMappingContext->MapKey(IA_MiddleClick, EKeys::MiddleMouseButton);
|
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)
|
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::Started, this, &APS_Editor_Pawn::HandleMiddleClickStarted);
|
||||||
EIC->BindAction(IA_MiddleClick, ETriggerEvent::Completed, this, &APS_Editor_Pawn::HandleMiddleClickCompleted);
|
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 ----
|
// ---- Input Handlers ----
|
||||||
@@ -295,10 +312,13 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
|||||||
|
|
||||||
const FVector AxisDir = Gizmo->GetAxisDirection(ActiveGizmoAxis);
|
const FVector AxisDir = Gizmo->GetAxisDirection(ActiveGizmoAxis);
|
||||||
const FVector TotalDelta = CurrentIntersection - GizmoDragInitialIntersection;
|
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();
|
const TArray<TWeakObjectPtr<AActor>>& Selected = SM->GetSelectedActors();
|
||||||
|
|
||||||
|
if (Mode == EPS_Editor_TransformMode::Translate)
|
||||||
|
{
|
||||||
|
const FVector ConstrainedDelta = FVector::DotProduct(TotalDelta, AxisDir) * AxisDir;
|
||||||
for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i)
|
for (int32 i = 0; i < Selected.Num() && i < GizmoDragInitialTransforms.Num(); ++i)
|
||||||
{
|
{
|
||||||
if (AActor* Actor = Selected[i].Get())
|
if (AActor* Actor = Selected[i].Get())
|
||||||
@@ -306,6 +326,62 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
|||||||
Actor->SetActorLocation(GizmoDragInitialTransforms[i].GetLocation() + ConstrainedDelta);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update gizmo position
|
// Update gizmo position
|
||||||
Gizmo->SetActorLocation(SM->GetSelectionPivot());
|
Gizmo->SetActorLocation(SM->GetSelectionPivot());
|
||||||
@@ -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 ----
|
// ---- Helpers ----
|
||||||
|
|
||||||
void APS_Editor_Pawn::SetCameraMode(EPS_Editor_CameraMode NewMode)
|
void APS_Editor_Pawn::SetCameraMode(EPS_Editor_CameraMode NewMode)
|
||||||
|
|||||||
@@ -146,6 +146,18 @@ private:
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<UInputAction> IA_MiddleClick;
|
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 ----
|
// ---- Modifier key state ----
|
||||||
bool bAltHeld = false;
|
bool bAltHeld = false;
|
||||||
bool bCtrlHeld = false;
|
bool bCtrlHeld = false;
|
||||||
@@ -170,6 +182,10 @@ private:
|
|||||||
void HandleRightClickCompleted(const FInputActionValue& Value);
|
void HandleRightClickCompleted(const FInputActionValue& Value);
|
||||||
void HandleMiddleClickStarted(const FInputActionValue& Value);
|
void HandleMiddleClickStarted(const FInputActionValue& Value);
|
||||||
void HandleMiddleClickCompleted(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 ----
|
// ---- Helpers ----
|
||||||
void CreateInputActions();
|
void CreateInputActions();
|
||||||
|
|||||||
Reference in New Issue
Block a user