using UnityEngine; using UnityEngine.Events; using Lean.Common; using FSA = UnityEngine.Serialization.FormerlySerializedAsAttribute; namespace Lean.Gui { /// This component will automatically snap RectTransform.anchoredPosition to the specified interval. [ExecuteInEditMode] [RequireComponent(typeof(RectTransform))] [HelpURL(LeanGui.HelpUrlPrefix + "LeanSnap")] [AddComponentMenu(LeanGui.ComponentMenuPrefix + "Snap")] public class LeanSnap : MonoBehaviour { [System.Serializable] public class Vector2IntEvent : UnityEvent {} /// Snap horizontally? public bool Horizontal { set { horizontal = value; } get { return horizontal; } } [SerializeField] private bool horizontal; /// The snap points will be offset by this many pixels. public float HorizontalOffset { set { horizontalOffset = value; } get { return horizontalOffset; } } [SerializeField] private float horizontalOffset; /// The spacing between each snap point in pixels. public float HorizontalIntervalPixel { set { horizontalIntervalPixel = value; } get { return horizontalIntervalPixel; } } [FSA("horizontalInterval")] [SerializeField] private float horizontalIntervalPixel = 10.0f; /// The spacing between each snap point in 0..1 percent of the current RectTransform size. public float HorizontalIntervalRect { set { horizontalIntervalRect = value; } get { return horizontalIntervalRect; } } [SerializeField] private float horizontalIntervalRect; /// The spacing between each snap point in 0..1 percent of the parent. public float HorizontalIntervalParent { set { horizontalIntervalParent = value; } get { return horizontalIntervalParent; } } [SerializeField] private float horizontalIntervalParent; /// The snap speed. /// -1 = Instant. /// 1 = Slow. /// 10 = Fast. public float HorizontalSpeed { set { horizontalSpeed = value; } get { return horizontalSpeed; } } [SerializeField] private float horizontalSpeed = -1.0f; /// Snap vertically? public bool Vertical { set { vertical = value; } get { return vertical; } } [SerializeField] private bool vertical; /// The snap points will be offset by this many pixels. public float VerticalOffset { set { verticalOffset = value; } get { return verticalOffset; } } [SerializeField] private float verticalOffset; /// The spacing between each snap point in pixels. public float VerticalIntervalPixel { set { verticalIntervalPixel = value; } get { return verticalIntervalPixel; } } [FSA("verticalInterval")] [SerializeField] private float verticalIntervalPixel = 10.0f; /// The spacing between each snap point in 0..1 percent of the current RectTransform size. public float VerticalIntervalRect { set { verticalIntervalRect = value; } get { return verticalIntervalRect; } } [SerializeField] private float verticalIntervalRect; /// The spacing between each snap point in 0..1 percent of the parent. public float VerticalIntervalParent { set { verticalIntervalParent = value; } get { return verticalIntervalParent; } } [SerializeField] private float verticalIntervalParent; /// The snap speed. /// -1 = Instant. /// 1 = Slow. /// 10 = Fast. public float VerticalSpeed { set { verticalSpeed = value; } get { return verticalSpeed; } } [SerializeField] private float verticalSpeed = -1.0f; /// To prevent UI element dragging from conflicting with snapping, you can specify the drag component here. public LeanDrag DisableWith { set { disableWith = value; } get { return disableWith; } } [SerializeField] private LeanDrag disableWith; /// This tells you the snap position as integers. public Vector2Int Position { get { return position; } } [SerializeField] private Vector2Int position; /// This event will be invoked when the snap position changes. public Vector2IntEvent OnPositionChanged { get { if (onPositionChanged == null) onPositionChanged = new Vector2IntEvent(); return onPositionChanged; } } [SerializeField] private Vector2IntEvent onPositionChanged; [System.NonSerialized] private RectTransform cachedRectTransform; protected virtual void OnEnable() { cachedRectTransform = GetComponent(); } protected virtual void LateUpdate() { if (disableWith != null && disableWith.Dragging == true) { return; } var anchoredPosition = cachedRectTransform.anchoredPosition; var rect = cachedRectTransform.rect; var parentSize = ParentSize; var intervalX = horizontalIntervalPixel + horizontalIntervalRect * rect.width + horizontalIntervalParent * parentSize.x; var intervalY = verticalIntervalPixel + verticalIntervalRect * rect.width + verticalIntervalParent * parentSize.y; var oldPosition = position; if (intervalX != 0.0f) { position.x = Mathf.RoundToInt((anchoredPosition.x - horizontalOffset) / intervalX); } if (intervalY != 0.0f) { position.y = Mathf.RoundToInt((anchoredPosition.y - verticalOffset) / intervalY); } if (horizontal == true) { var target = position.x * intervalX + horizontalOffset; var factor = LeanHelper.GetDampenFactor(horizontalSpeed, Time.deltaTime); anchoredPosition.x = Mathf.Lerp(anchoredPosition.x, target, factor); } if (vertical == true) { var target = position.y * intervalY + verticalOffset; var factor = LeanHelper.GetDampenFactor(verticalSpeed, Time.deltaTime); anchoredPosition.y = Mathf.Lerp(anchoredPosition.y, target, factor); } cachedRectTransform.anchoredPosition = anchoredPosition; if (position != oldPosition) { if (onPositionChanged != null) { onPositionChanged.Invoke(position); } } } private Vector2 ParentSize { get { var parent = cachedRectTransform.parent as RectTransform; return parent != null ? parent.rect.size : Vector2.zero; } } } } #if UNITY_EDITOR namespace Lean.Gui.Editor { using TARGET = LeanSnap; [UnityEditor.CanEditMultipleObjects] [UnityEditor.CustomEditor(typeof(TARGET))] public class LeanSnap_Editor : LeanEditor { protected override void OnInspector() { TARGET tgt; TARGET[] tgts; GetTargets(out tgt, out tgts); Draw("horizontal", "Snap horizontally?"); if (Any(tgts, t => t.Horizontal == true)) { BeginIndent(); Draw("horizontalOffset", "The snap points will be offset by this many pixels.", "Offset"); BeginError(Any(tgts, t => t.HorizontalIntervalPixel == 0.0f && t.HorizontalIntervalRect == 0.0f && t.HorizontalIntervalParent == 0.0f)); Draw("horizontalIntervalPixel", "The spacing between each snap point in pixels.", "Interval Pixel"); Draw("horizontalIntervalRect", "The spacing between each snap point in 0..1 percent of the current RectTransform size.", "Interval Rect"); Draw("horizontalIntervalParent", "The spacing between each snap point in 0..1 percent of the parent.", "Interval Parent"); EndError(); Draw("horizontalSpeed", "The snap speed.\n\n-1 = Instant.\n\n1 = Slow.\n\n10 = Fast.", "Speed"); EndIndent(); } Separator(); Draw("vertical", "Snap vertically?"); if (Any(tgts, t => t.Vertical == true)) { BeginIndent(); Draw("verticalOffset", "The snap points will be offset by this many pixels.", "Offset"); BeginError(Any(tgts, t => t.VerticalIntervalPixel == 0.0f && t.VerticalIntervalRect == 0.0f && t.VerticalIntervalParent == 0.0f)); Draw("verticalIntervalPixel", "The spacing between each snap point in pixels.", "Interval Pixel"); Draw("verticalIntervalRect", "The spacing between each snap point in 0..1 percent of the current RectTransform size.", "Interval Rect"); Draw("verticalIntervalParent", "The spacing between each snap point in 0..1 percent of the parent.", "Interval Parent"); EndError(); Draw("verticalSpeed", "The snap speed.\n\n-1 = Instant.\n\n1 = Slow.\n\n10 = Fast.", "Speed"); EndIndent(); } Separator(); Draw("disableWith", "To prevent UI element dragging from conflicting with snapping, you can specify the drag component here."); BeginDisabled(true); Draw("position", "This tells you the snap position as integers."); EndDisabled(); Draw("onPositionChanged"); } } } #endif