using UnityEngine; /// /// Strategy 2 — axis-constrained translation via UI handle. /// Call BeginDrag() when the user grabs an axis arrow, passing the world-space /// axis direction and the hit point on that arrow. /// Call EndDrag() when the pointer is released. /// The ghost slides along the axis; rotation is frozen. /// Snapping still applies (position only). /// public class AxisMoveStrategy : IGhostMovementStrategy { private readonly Quaternion _lockedRotation; // Drag state private bool _isDragging; private Vector3 _axisDirection; // world-space unit vector of the active axis private Vector3 _axisOrigin; // point on the axis at drag-begin (ghost position) private float _clampMin; private float _clampMax; private Camera _camera; private Vector3 _frozenPosition; private Vector2 _lastMousePos; private float _accumulated; /// Ghost rotation frozen for the entire interaction. /// Min distance from drag origin along the axis (negative = behind). /// Max distance from drag origin along the axis. public AxisMoveStrategy(Quaternion lockedRotation, float clampMin = -1000f, float clampMax = 1000f) { _lockedRotation = lockedRotation; _clampMin = clampMin; _clampMax = clampMax; _camera = Camera.main; } private float _dragOffsetT; /// /// Call from your UI when the user begins dragging an axis arrow. /// /// World-space direction of the axis (e.g. ghost.transform.right). /// Ghost world position at the moment dragging begins. public void BeginDrag(Vector3 axisDirection, Vector3 ghostPosition) { _camera = Camera.main; // Ensure camera reference is up-to-date _axisDirection = axisDirection.normalized; _axisOrigin = ghostPosition; _frozenPosition = ghostPosition; _accumulated = 0f; _isDragging = true; if (_camera != null) { Ray mouseRay = _camera.ScreenPointToRay(Input.mousePosition); _dragOffsetT = CalculateT(mouseRay); } } private float CalculateT(Ray mouseRay) { Vector3 w = mouseRay.origin - _axisOrigin; float b = Vector3.Dot(_axisDirection, mouseRay.direction); float d = Vector3.Dot(_axisDirection, w); float e = Vector3.Dot(mouseRay.direction, w); float denominator = 1f - b * b; if (Mathf.Abs(denominator) < 0.0001f) { return 0f; } return (d - b * e) / denominator; } /// Call from your UI when the pointer is released. public void EndDrag() => _isDragging = false; public void UpdateMovement(ref GhostMovementContext ctx) { // Rotation always frozen ctx.GhostTransform.rotation = _lockedRotation; if (!_isDragging) { ctx.RotationAxis = _axisDirection != Vector3.zero ? _axisDirection : Vector3.up; TrySocketSnap(ref ctx); return; } if (_camera != null) { Ray mouseRay = _camera.ScreenPointToRay(Input.mousePosition); float currentT = CalculateT(mouseRay); float targetAccumulated = currentT - _dragOffsetT; _accumulated = Mathf.Clamp(targetAccumulated, _clampMin, _clampMax); } // Only the axis component changes — all other axes stay frozen ctx.GhostTransform.position = _frozenPosition + _axisDirection * _accumulated; ctx.RotationAxis = _axisDirection; TrySocketSnap(ref ctx); } // ------------------------------------------------------------------------- private bool TrySocketSnap(ref GhostMovementContext ctx) { SocketPoint[] mySockets = ctx.GhostTransform.GetComponentsInChildren(); if (mySockets.Length == 0) return false; SocketPoint bestMySocket = null; SocketPoint bestTargetSocket = null; float bestDist = 0.5f; foreach (var mySocket in mySockets) { Collider[] hits = Physics.OverlapSphere(mySocket.transform.position, bestDist, Physics.AllLayers, QueryTriggerInteraction.Collide); foreach (var hit in hits) { if (hit.transform.root == ctx.GhostTransform.root) continue; SocketPoint[] targetSockets = hit.transform.root.GetComponentsInChildren(); foreach (var target in targetSockets) { if (target.IsOccupied()) continue; if (!target.CanAccept(mySocket)) continue; float dist = Vector3.Distance(mySocket.transform.position, target.transform.position); if (dist < bestDist) { bestDist = dist; bestMySocket = mySocket; bestTargetSocket = target; } } } } if (bestMySocket != null && bestTargetSocket != null) { Vector3 desiredPosition = bestTargetSocket.transform.position - (bestMySocket.transform.position - ctx.GhostTransform.position); if (_isDragging && _axisDirection != Vector3.zero) { Vector3 toDesired = desiredPosition - _axisOrigin; float scalar = Vector3.Dot(toDesired, _axisDirection); scalar = Mathf.Clamp(scalar, _clampMin, _clampMax); ctx.GhostTransform.position = _axisOrigin + _axisDirection * scalar; } else { ctx.GhostTransform.position = desiredPosition; } return true; } return false; } }