Pivot-anchor fallback for no-collision spawns + always-snap on catalog
Ground snap: - When an actor has no colliding components at all (e.g. a VR start marker, placement helper, or any BP whose collision is only enabled on entering editor mode), SnapActorToGround now anchors the PIVOT directly on the ground instead of falling back to the full-bounds bbox bottom. The full-bounds bottom could be far below the pivot (helper meshes, arrows, visual markers) and was lifting objects hundreds of units into the air. - Actors with collision keep the original behavior (collision-bbox bottom rests on the ground). Catalog spawn: - Always snap to ground (drop the bAutoSnapToGround gate) — a user clicking in the catalog expects the object to land on the ground every time; the flag is kept for gameplay-side spawning via SceneLoader. - SpawnActor now uses AlwaysSpawn instead of AdjustIfPossibleButAlwaysSpawn so UE never pushes the actor vertically to resolve overlaps — SnapActorToGround is the authoritative placement step. CLAUDE.md workflow rule: - Document that behavior / logic changes require explaining the plan first and waiting for go-ahead; only diagnostic-only additions (temporary UE_LOG) are fine without approval. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -120,22 +120,33 @@ namespace PS_Editor_GroundSnap
|
||||
return false;
|
||||
}
|
||||
|
||||
// Measure the bbox bottom relative to the pivot. Use COLLIDING components only so
|
||||
// a Character's capsule drives the calculation — not its skeletal mesh (often with
|
||||
// collision disabled) or attached weapons / child actors whose meshes may extend
|
||||
// below the capsule and artificially lift the pawn by that overflow.
|
||||
// Falls back to the full actor bounds if no colliding component is found (e.g. a
|
||||
// pure-mesh prop without collision — rare but possible).
|
||||
// Measure the bbox bottom relative to the pivot.
|
||||
//
|
||||
// Primary path — colliding components only: a Character's capsule drives the
|
||||
// calculation, ignoring the skeletal mesh (often with collision disabled) or any
|
||||
// attached weapons / child actors whose meshes might extend below the capsule and
|
||||
// artificially lift the pawn by that overflow. Formula: pivot is placed so the
|
||||
// collision-bbox bottom rests on the ground.
|
||||
//
|
||||
// Fallback — no colliding components found: the actor is treated as a pure visual
|
||||
// marker (VR start zone, placement helper, or a BP that enables collision only
|
||||
// when entering editor mode and hasn't done so yet). For these, we don't trust
|
||||
// the full-bounds bbox (it may wrap helper meshes far below the pivot and push
|
||||
// the object absurdly high). Instead we anchor the PIVOT directly on the ground —
|
||||
// which is what the designer authored the pivot for on these markers.
|
||||
FVector BoundsOrigin, BoundsExtent;
|
||||
Actor->GetActorBounds(/*bOnlyCollidingComponents*/ true, BoundsOrigin, BoundsExtent);
|
||||
if (BoundsExtent.IsNearlyZero())
|
||||
const bool bUsedFallback = BoundsExtent.IsNearlyZero();
|
||||
if (bUsedFallback)
|
||||
{
|
||||
// Read the full bounds too — only to know where "above the actor" is for the
|
||||
// ray start position, not to anchor the snap.
|
||||
Actor->GetActorBounds(false, BoundsOrigin, BoundsExtent);
|
||||
}
|
||||
const float ActorZ = Actor->GetActorLocation().Z;
|
||||
const float BBoxBottomZ = BoundsOrigin.Z - BoundsExtent.Z;
|
||||
const float BBoxTopZ = BoundsOrigin.Z + BoundsExtent.Z;
|
||||
const float PivotAboveBottom = ActorZ - BBoxBottomZ;
|
||||
const float PivotAboveBottom = bUsedFallback ? 0.0f : (ActorZ - BBoxBottomZ);
|
||||
|
||||
float ExtraOffset = 0.0f;
|
||||
if (UPS_Editor_SpawnableComponent* SpawnComp = Actor->FindComponentByClass<UPS_Editor_SpawnableComponent>())
|
||||
|
||||
@@ -130,7 +130,13 @@ AActor* UPS_Editor_SpawnManager::SpawnFromCatalogAtLocation(int32 Index, FVector
|
||||
}
|
||||
|
||||
FActorSpawnParameters SpawnParams;
|
||||
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
|
||||
// AlwaysSpawn (instead of AdjustIfPossibleButAlwaysSpawn) so UE doesn't push the actor
|
||||
// upward when its bounds overlap the ground at spawn time — our SnapActorToGround call
|
||||
// below is the authoritative ground placement step, and it uses collision-only bounds
|
||||
// which correctly ignore non-collidable attached meshes (weapons, child actors).
|
||||
// With the old override, a character with a weapon dangling below the capsule could
|
||||
// be shoved high by the auto-adjust, leaving it stuck in the air.
|
||||
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
|
||||
AActor* SpawnedActor = PC->GetWorld()->SpawnActor<AActor>(Entry.ActorClass, WorldLocation, FRotator::ZeroRotator, SpawnParams);
|
||||
if (!SpawnedActor)
|
||||
@@ -160,10 +166,13 @@ AActor* UPS_Editor_SpawnManager::SpawnFromCatalogAtLocation(int32 Index, FVector
|
||||
SpawnedActors.Add(SpawnedActor);
|
||||
RegisterSpawnedActorId(PC->GetWorld(), SpawnedActor);
|
||||
|
||||
// Auto-snap to ground if configured — uses the shared helper so it handles pivot
|
||||
// positions (characters with pivot at feet) + ignores the fresh actor itself.
|
||||
// Catalogue spawns are always a deliberate user placement, so always snap to ground.
|
||||
// Previously gated on SpawnableComponent::bAutoSnapToGround — that flag is preserved
|
||||
// as the gameplay-spawn hint (for SceneLoader / runtime spawning) but here we want
|
||||
// consistent placement regardless of BP configuration. If the actor genuinely shouldn't
|
||||
// touch the ground, the user can move it with the gizmo after spawning.
|
||||
UPS_Editor_SpawnableComponent* SpawnComp = SpawnedActor->FindComponentByClass<UPS_Editor_SpawnableComponent>();
|
||||
if (SpawnComp && SpawnComp->bAutoSnapToGround && PC->GetWorld())
|
||||
if (PC->GetWorld())
|
||||
{
|
||||
TArray<AActor*> ExtraIgnored;
|
||||
if (APawn* Pwn = PC->GetPawn()) ExtraIgnored.Add(Pwn);
|
||||
|
||||
Reference in New Issue
Block a user