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));
|
||||
};
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user