Files
JuegoSim/Assets/_Project/Scripts/Code/InteractionLayer/Managers/GhostExtensions/FreeMoveStrategy.cs
T
2026-07-21 08:56:10 +03:00

131 lines
5.2 KiB
C#

using UnityEngine;
/// <summary>
/// Strategy — free surface tracking.
/// Ghost follows the ray hit point, aligns to the surface normal,
/// and socket-snaps when a compatible socket is nearby.
/// Rotation input rotates around the surface normal (or socket forward when snapped).
/// </summary>
public class FreeMoveStrategy : IGhostMovementStrategy
{
public void UpdateMovement(ref GhostMovementContext ctx)
{
bool snapped = TrySocketSnap(ref ctx);
if (!snapped)
{
if (ctx.AlignToSurfaceNormal)
AlignToSurfaceNormal(ref ctx);
MoveAlongSurface(ref ctx);
}
// Rotation axis: socket forward when snapped, surface normal otherwise
ctx.RotationAxis = (snapped && ctx.MySocket != null)
? ctx.MySocket.transform.forward
: ctx.SurfaceNormal;
}
// -------------------------------------------------------------------------
private static bool TrySocketSnap(ref GhostMovementContext ctx)
{
if (ctx.TargetSocket == null) return false;
bool compatible = ctx.MySocket == null || ctx.TargetSocket.CanAccept(ctx.MySocket);
if (!compatible) return false;
PerformSocketSnap(ref ctx);
return true;
}
private static void PerformSocketSnap(ref GhostMovementContext ctx)
{
Vector3 targetNormal = ctx.TargetSocket.GetNormal().normalized;
if (ctx.MySocket != null)
{
// 1. Primary Alignment: Align the exact socket normals
Vector3 myNormal = ctx.MySocket.GetNormal().normalized;
Vector3 myTargetNormal = -ctx.TargetSocket.GetNormal().normalized;
Quaternion primaryRot = Quaternion.FromToRotation(myNormal, myTargetNormal);
ctx.GhostTransform.rotation = primaryRot * ctx.GhostTransform.rotation;
// 2. Secondary Alignment: Align the 'Up' (or Forward) vectors to prevent random twisting/rolling
Vector3 mySecondary = Mathf.Abs(Vector3.Dot(ctx.MySocket.transform.up, myTargetNormal)) < 0.9f
? ctx.MySocket.transform.up
: ctx.MySocket.transform.forward;
Vector3 targetSecondary = Mathf.Abs(Vector3.Dot(ctx.TargetSocket.transform.up, -myTargetNormal)) < 0.9f
? ctx.TargetSocket.transform.up
: ctx.TargetSocket.transform.forward;
// Project secondary vectors onto the flat plane between the sockets
Vector3 projectedMySecondary = Vector3.ProjectOnPlane(mySecondary, myTargetNormal).normalized;
Vector3 projectedTargetSecondary = Vector3.ProjectOnPlane(targetSecondary, myTargetNormal).normalized;
if (projectedMySecondary != Vector3.zero && projectedTargetSecondary != Vector3.zero)
{
// Find the twist angle needed to align the secondary axes
float angle = Vector3.SignedAngle(projectedMySecondary, projectedTargetSecondary, myTargetNormal);
// Snap to the nearest 45 degrees to allow manual 'R' rotations to persist!
float correction = angle - Mathf.Round(angle / 45f) * 45f;
// Rotate the ghost around its own pivot
ctx.GhostTransform.rotation = Quaternion.AngleAxis(correction, myTargetNormal) * ctx.GhostTransform.rotation;
}
// 3. Translate the ghost so the sockets physically touch
Vector3 shift = ctx.TargetSocket.transform.position - ctx.MySocket.transform.position;
ctx.GhostTransform.position += shift;
}
else
{
Transform pivot = ctx.AttachPoint != null
? ctx.AttachPoint
: ctx.GhostTransform;
Vector3 pivotOffset = pivot.position - ctx.GhostTransform.position;
ctx.GhostTransform.position = ctx.TargetSocket.transform.position - pivotOffset;
}
}
private static void AlignToSurfaceNormal(ref GhostMovementContext ctx)
{
Transform pivot = GetCurrentPivot(ref ctx);
Quaternion target = Quaternion.FromToRotation(pivot.up, ctx.SurfaceNormal)
* ctx.GhostTransform.rotation;
if (ctx.SnapRotation)
target = SnapToNearestRightAngle(target);
ctx.GhostTransform.rotation = target;
}
private static void MoveAlongSurface(ref GhostMovementContext ctx)
{
Transform pivot = GetCurrentPivot(ref ctx);
Vector3 pivotOffset = pivot.position - ctx.GhostTransform.position;
Vector3 targetPos = ctx.SurfacePoint - pivotOffset;
if (ctx.HeightOffset != 0f)
targetPos += ctx.SurfaceNormal * ctx.HeightOffset;
ctx.GhostTransform.position = Vector3.Lerp(
ctx.GhostTransform.position,
targetPos,
Time.deltaTime * ctx.FollowSpeed);
}
private static Transform GetCurrentPivot(ref GhostMovementContext ctx)
{
return ctx.AttachPoint != null ? ctx.AttachPoint : ctx.GhostTransform;
}
private static Quaternion SnapToNearestRightAngle(Quaternion q)
{
Vector3 e = q.eulerAngles;
e.x = Mathf.Round(e.x / 90f) * 90f;
e.y = Mathf.Round(e.y / 90f) * 90f;
e.z = Mathf.Round(e.z / 90f) * 90f;
return Quaternion.Euler(e);
}
}