Initial commit

This commit is contained in:
HussienX72u
2026-07-21 08:56:10 +03:00
commit 4017028111
9410 changed files with 2150500 additions and 0 deletions
@@ -0,0 +1,164 @@
using UnityEngine;
/// <summary>
/// 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).
/// </summary>
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;
/// <param name="lockedRotation">Ghost rotation frozen for the entire interaction.</param>
/// <param name="clampMin">Min distance from drag origin along the axis (negative = behind).</param>
/// <param name="clampMax">Max distance from drag origin along the axis.</param>
public AxisMoveStrategy(Quaternion lockedRotation, float clampMin = -1000f, float clampMax = 1000f)
{
_lockedRotation = lockedRotation;
_clampMin = clampMin;
_clampMax = clampMax;
_camera = Camera.main;
}
private float _dragOffsetT;
/// <summary>
/// Call from your UI when the user begins dragging an axis arrow.
/// </summary>
/// <param name="axisDirection">World-space direction of the axis (e.g. ghost.transform.right).</param>
/// <param name="ghostPosition">Ghost world position at the moment dragging begins.</param>
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;
}
/// <summary>Call from your UI when the pointer is released.</summary>
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<SocketPoint>();
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<SocketPoint>();
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;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4d40b6e6599b5054b8a3f72a417bfa87
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,186 @@
using UnityEngine;
public class AxisRotateStrategy : IGhostMovementStrategy
{
public bool OverridesSurfaceTracking => true;
public enum LocalAxis { Up, Forward, Right }
private readonly LocalAxis _localAxis;
private readonly Vector3 _lockedPosition;
private bool _isRotating;
private float _accumulatedDegrees;
private Vector2 _lastMousePos;
private Quaternion _unsnappedRot;
private Vector3 _unsnappedPos;
private bool _wasSnapped;
public float Sensitivity = 0.4f;
public float SnapAngle = 15f;
public bool EnableSnapping = false;
/// <param name="localAxis">Local axis of the ghost to rotate around.</param>
/// <param name="lockedPosition">Ghost world position frozen for the entire interaction.</param>
public AxisRotateStrategy(LocalAxis localAxis, Vector3 lockedPosition)
{
_localAxis = localAxis;
_lockedPosition = lockedPosition;
}
public void BeginRotate()
{
_lastMousePos = Input.mousePosition;
_accumulatedDegrees = 0f;
_isRotating = true;
_wasSnapped = false;
}
public void EndRotate() => _isRotating = false;
public void UpdateMovement(ref GhostMovementContext ctx)
{
// 1. Revert previous frame's snap so we rotate from the true axial position
if (_wasSnapped)
{
ctx.GhostTransform.position = _unsnappedPos;
ctx.GhostTransform.rotation = _unsnappedRot;
_wasSnapped = false;
}
// Read the axis from the ghost's current orientation every frame
// so it stays local regardless of how the ghost has been rotated
Vector3 worldAxis = GetWorldAxis(ctx.GhostTransform);
if (_isRotating)
{
Vector2 mouseDelta = (Vector2)Input.mousePosition - _lastMousePos;
_lastMousePos = Input.mousePosition;
if (mouseDelta.sqrMagnitude > 0.001f)
{
// Project the world axis into screen space to get its 2D direction
Vector3 axisScreenStart = Camera.main.WorldToScreenPoint(_lockedPosition);
Vector3 axisScreenEnd = Camera.main.WorldToScreenPoint(_lockedPosition + worldAxis);
Vector2 axisScreen = (axisScreenEnd - axisScreenStart).normalized;
// The rotation direction is the perpendicular to the screen-space axis
// Dot the mouse delta against that perpendicular to get signed rotation
Vector2 perpendicular = new Vector2(-axisScreen.y, axisScreen.x);
float signedDelta = Vector2.Dot(mouseDelta, perpendicular);
if (ctx.SnapRotation || EnableSnapping)
{
_accumulatedDegrees += signedDelta * Sensitivity;
float sign = Mathf.Sign(_accumulatedDegrees);
while (Mathf.Abs(_accumulatedDegrees) >= SnapAngle)
{
ctx.GhostTransform.RotateAround(_lockedPosition, worldAxis, SnapAngle * sign);
_accumulatedDegrees -= SnapAngle * sign;
}
}
else
{
float pendingDegrees = signedDelta * Sensitivity;
if (pendingDegrees != 0f)
{
ctx.GhostTransform.RotateAround(_lockedPosition, worldAxis, pendingDegrees);
}
}
}
}
// Save the pure axial rotation state
_unsnappedPos = ctx.GhostTransform.position;
_unsnappedRot = ctx.GhostTransform.rotation;
// Attempt to snap to nearby sockets visually
if (TrySocketSnap(ref ctx))
{
_wasSnapped = true;
}
ctx.RotationAxis = worldAxis;
}
private bool TrySocketSnap(ref GhostMovementContext ctx)
{
SocketPoint[] mySockets = ctx.GhostTransform.GetComponentsInChildren<SocketPoint>();
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<SocketPoint>();
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)
{
// Align the rotations exactly like FreeMoveStrategy
Vector3 myNormal = bestMySocket.GetNormal().normalized;
Vector3 myTargetNormal = -bestTargetSocket.GetNormal().normalized;
Quaternion primaryRot = Quaternion.FromToRotation(myNormal, myTargetNormal);
ctx.GhostTransform.rotation = primaryRot * ctx.GhostTransform.rotation;
Vector3 mySecondary = Mathf.Abs(Vector3.Dot(bestMySocket.transform.up, myTargetNormal)) < 0.9f
? bestMySocket.transform.up
: bestMySocket.transform.forward;
Vector3 targetSecondary = Mathf.Abs(Vector3.Dot(bestTargetSocket.transform.up, -myTargetNormal)) < 0.9f
? bestTargetSocket.transform.up
: bestTargetSocket.transform.forward;
Vector3 projectedMySecondary = Vector3.ProjectOnPlane(mySecondary, myTargetNormal).normalized;
Vector3 projectedTargetSecondary = Vector3.ProjectOnPlane(targetSecondary, myTargetNormal).normalized;
if (projectedMySecondary != Vector3.zero && projectedTargetSecondary != Vector3.zero)
{
float angle = Vector3.SignedAngle(projectedMySecondary, projectedTargetSecondary, myTargetNormal);
float correction = angle - Mathf.Round(angle / 45f) * 45f;
ctx.GhostTransform.rotation = Quaternion.AngleAxis(correction, myTargetNormal) * ctx.GhostTransform.rotation;
}
// Translate to connect the sockets perfectly
Vector3 shift = bestTargetSocket.transform.position - bestMySocket.transform.position;
ctx.GhostTransform.position += shift;
return true;
}
return false;
}
// Re-evaluated every frame from the ghost's current transform
private Vector3 GetWorldAxis(Transform t) => _localAxis switch
{
LocalAxis.Up => t.up,
LocalAxis.Forward => t.forward,
LocalAxis.Right => t.right,
_ => t.up,
};
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6129be9a4c7dac4bb0a5c345336cbcb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,131 @@
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);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5046d2417e8edc54f981d84ddaa61413
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,82 @@
using System.Collections;
using System.Collections.Generic;
using NaughtyAttributes;
using UnityEngine;
public class GhostStrategyHandler : MonoBehaviour
{
private static GhostStrategyHandler instance;
public static GhostStrategyHandler getInstance() => instance;
private GhostManagerV2 ghostManager;
private void Awake()
{
if (instance == null) instance = this;
else Destroy(gameObject);
}
void Start()
{
ghostManager = GhostManagerV2.getInstance();
}
// MoveAxis strategy
public void StartDraggingRight()
{
var MoveStategy = new AxisMoveStrategy(Quaternion.identity);
MoveStategy.BeginDrag(Vector3.right, Vector3.zero);
ghostManager.SetMovementStrategy(MoveStategy);
}
public void StartDraggingUp()
{
var MoveStategy = new AxisMoveStrategy(Quaternion.identity);
MoveStategy.BeginDrag(Vector3.up, Vector3.zero);
ghostManager.SetMovementStrategy(MoveStategy);
}
public void StartDraggingForward()
{
var MoveStategy = new AxisMoveStrategy(Quaternion.identity);
MoveStategy.BeginDrag(Vector3.forward, Vector3.zero);
ghostManager.SetMovementStrategy(MoveStategy);
}
public void EndDragging()
{
ghostManager.SetMovementStrategy(new FreeMoveStrategy());
}
// RotateAxis strategy
public AxisRotateStrategy StartRotatingRight()
{
var RotateStrategy = new AxisRotateStrategy(AxisRotateStrategy.LocalAxis.Right, Vector3.zero);
ghostManager.SetMovementStrategy(RotateStrategy);
return RotateStrategy;
}
public AxisRotateStrategy StartRotatingUp()
{
var RotateStrategy = new AxisRotateStrategy(AxisRotateStrategy.LocalAxis.Up, Vector3.zero);
ghostManager.SetMovementStrategy(RotateStrategy);
return RotateStrategy;
}
public AxisRotateStrategy StartRotatingForward()
{
var RotateStrategy = new AxisRotateStrategy(AxisRotateStrategy.LocalAxis.Forward, Vector3.zero);
ghostManager.SetMovementStrategy(RotateStrategy);
return RotateStrategy;
}
public void StartRotation()
{
var RotateStrategy = new AxisRotateStrategy(AxisRotateStrategy.LocalAxis.Right, Vector3.zero);
ghostManager.SetMovementStrategy(RotateStrategy);
return;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 033a7779738092141b18bb78e68e0c21
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,237 @@
using UnityEngine;
public class GhostStrategyTester : MonoBehaviour
{
[Header("Assign the scene object to edit")]
public GameObject TestTarget;
[Header("Move settings")]
public float ClampMin = -5f;
public float ClampMax = 5f;
[Header("Rotation settings")]
public float RotateSensitivity = 0.4f;
// -------------------------------------------------------------------------
private enum Mode { None, AxisMove, AxisRotate }
private Mode _mode = Mode.None;
private AxisMoveStrategy _moveStrat;
private AxisRotateStrategy _rotStrat;
private bool _isEditing;
private bool _buttonHeld;
private int _activeAxisIndex = -1;
private float _lastMouseX;
private Vector3 _editingOrigin;
private static readonly Color[] AxisColors =
{
new Color(0.9f, 0.25f, 0.25f),
new Color(0.25f, 0.85f, 0.25f),
new Color(0.25f, 0.45f, 0.95f),
};
private static readonly string[] AxisLabels = { "X", "Y", "Z" };
private static readonly Vector3[] LocalMoveAxes =
{
Vector3.right,
Vector3.up,
Vector3.forward,
};
private static readonly AxisRotateStrategy.LocalAxis[] RotateAxes =
{
AxisRotateStrategy.LocalAxis.Right,
AxisRotateStrategy.LocalAxis.Up,
AxisRotateStrategy.LocalAxis.Forward,
};
private const float BtnW = 96f;
private const float BtnH = 36f;
private const float PadX = 12f;
private const float PadY = 8f;
// -------------------------------------------------------------------------
private void Start()
{
GhostManagerV2.getInstance().onClearAddListener(() =>
{
_isEditing = false;
_buttonHeld = false;
_activeAxisIndex = -1;
_moveStrat = null;
_rotStrat = null;
});
}
// private void Update()
// {
// if (!_buttonHeld || _activeAxisIndex < 0) return;
// if (_mode == Mode.AxisRotate && _rotStrat != null)
// {
// float delta = Input.mousePosition.x - _lastMouseX;
// if (Mathf.Abs(delta) > 0.01f)
// _rotStrat.Rotate(delta * RotateSensitivity);
// }
// _lastMouseX = Input.mousePosition.x;
// }
// -------------------------------------------------------------------------
// GUI
// -------------------------------------------------------------------------
private void OnGUI()
{
if (TestTarget == null)
{
GUI.Label(new Rect(PadX, PadX, 300, 24), "Assign TestTarget in the Inspector.");
return;
}
float y = PadX;
// ── Mode buttons ───────────────────────────────────────────────
DrawModeButton("Move", Mode.AxisMove, new Rect(PadX, y, BtnW, BtnH));
DrawModeButton("Rotate", Mode.AxisRotate, new Rect(PadX + BtnW + PadY, y, BtnW, BtnH));
y += BtnH + PadY;
if (_mode == Mode.None)
{
GUI.Label(new Rect(PadX, y, 300, 24), "Select a mode above.");
return;
}
// ── Axis buttons ───────────────────────────────────────────────
string hint = _mode == Mode.AxisMove
? "Hold to drag along axis:"
: "Hold and drag left/right to rotate:";
GUI.Label(new Rect(PadX, y, 350, 20), hint);
y += 22f;
for (int i = 0; i < 3; i++)
{
Rect btnRect = new Rect(PadX + i * (BtnW + PadY), y, BtnW, BtnH);
DrawAxisButton(i, btnRect);
}
y += BtnH + PadY * 2;
// ── Commit / Cancel ────────────────────────────────────────────
if (GUI.Button(new Rect(PadX, y, BtnW, BtnH), "Commit"))
CommitEdit();
if (GUI.Button(new Rect(PadX + BtnW + PadY, y, BtnW, BtnH), "Cancel"))
StopEditing();
}
// -------------------------------------------------------------------------
private void DrawAxisButton(int i, Rect rect)
{
bool held = _buttonHeld && _activeAxisIndex == i;
GUI.backgroundColor = held ? AxisColors[i] : AxisColors[i] * 0.55f;
GUI.contentColor = Color.white;
bool pressed = GUI.RepeatButton(rect, $"{AxisLabels[i]} axis");
if (pressed && (!_buttonHeld || _activeAxisIndex != i))
OnAxisButtonDown(i);
if (!pressed && _buttonHeld && _activeAxisIndex == i)
OnAxisButtonUp();
GUI.backgroundColor = Color.white;
GUI.contentColor = Color.white;
}
// -------------------------------------------------------------------------
private void OnAxisButtonDown(int axisIndex)
{
EnsureEditing();
_activeAxisIndex = axisIndex;
_buttonHeld = true;
_lastMouseX = Input.mousePosition.x;
if (_mode == Mode.AxisMove)
{
Transform ghost = GhostManagerV2.getInstance().GhostTransform;
Vector3 worldAxis = ghost.TransformDirection(LocalMoveAxes[axisIndex]);
_moveStrat = new AxisMoveStrategy(ghost.rotation, ClampMin, ClampMax);
_moveStrat.BeginDrag(worldAxis, ghost.position);
GhostManagerV2.getInstance().SetMovementStrategy(_moveStrat);
}
else
{
_rotStrat = new AxisRotateStrategy(
RotateAxes[axisIndex],
GhostManagerV2.getInstance().GhostTransform.position);
GhostManagerV2.getInstance().SetMovementStrategy(_rotStrat);
}
}
private void OnAxisButtonUp()
{
_buttonHeld = false;
_activeAxisIndex = -1;
if (_mode == Mode.AxisMove)
_moveStrat?.EndDrag();
}
// -------------------------------------------------------------------------
private void EnsureEditing()
{
if (_isEditing) return;
_editingOrigin = TestTarget.transform.position;
GhostManagerV2.getInstance().SetupExistingBlock(TestTarget, new FreeMoveStrategy());
_isEditing = true;
}
private void CommitEdit()
{
GhostManagerV2.getInstance().ForceCommit();
ResetState();
}
private void StopEditing()
{
GhostManagerV2.getInstance().ClearGhostData();
ResetState();
}
private void ResetState()
{
_mode = Mode.None;
_activeAxisIndex = -1;
_buttonHeld = false;
_isEditing = false;
_moveStrat = null;
_rotStrat = null;
}
private void DrawModeButton(string label, Mode target, Rect rect)
{
bool active = _mode == target;
GUI.backgroundColor = active ? Color.white : new Color(0.55f, 0.55f, 0.55f);
GUI.contentColor = active ? Color.black : Color.white;
if (GUI.Button(rect, label) && !active)
{
StopEditing();
_mode = target;
}
GUI.backgroundColor = Color.white;
GUI.contentColor = Color.white;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e03daa9e654f8664f912d8ce018c0112
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: