using System;
using UnityEngine;
using UnityEngine.Events;
namespace OmiLAXR
{
///
/// Monitors a GameObject's transform for significant changes in position, rotation, and scale.
/// Triggers events when changes exceed defined thresholds.
///
[AddComponentMenu("OmiLAXR / Game Objects / Transform Watcher")]
[DisallowMultipleComponent]
public class TransformWatcher : MonoBehaviour
{
///
/// Data structure to hold information about a transform property change,
/// including both the previous and current values.
///
public struct TransformChange
{
///
/// The previous value before the change occurred.
///
public Vector3 OldValue;
///
/// The new value after the change occurred.
///
public Vector3 NewValue;
}
[Serializable]
public struct TransformIgnore
{
public bool position;
public bool rotation;
public bool scale;
}
public TransformIgnore ignore;
///
/// Minimum position change (in units) required to trigger the position change event.
///
[Tooltip("Minimum position change (in units) required to trigger events")]
public float positionThreshold = .5f;
///
/// Minimum rotation change (in degrees) required to trigger the rotation change event.
///
[Tooltip("Minimum rotation change (in degrees) required to trigger events")]
public float rotationThreshold = 1.0f;
///
/// Minimum scale change required to trigger the scale change event.
///
[Tooltip("Minimum scale change required to trigger events")]
public float scaleThreshold = 0.1f;
///
/// The most recent position value that exceeded the threshold.
///
private Vector3 _lastPosition;
///
/// The most recent scale value that exceeded the threshold.
///
private Vector3 _lastScale;
///
/// The most recent rotation value that exceeded the threshold.
///
private Vector3 _lastRotation;
///
/// Gets the last position that exceeded the threshold.
///
public Vector3 LastPosition => _lastPosition;
///
/// Gets the last scale that exceeded the threshold.
///
public Vector3 LastScale => _lastScale;
///
/// Gets the last rotation that exceeded the threshold.
///
public Vector3 LastRotation => _lastRotation;
///
/// Gets the current position of the transform.
///
public Vector3 CurrentPosition => transform.position;
///
/// Gets the current local scale of the transform.
///
public Vector3 CurrentScale => transform.localScale;
///
/// Gets the current rotation of the transform in Euler angles.
///
public Vector3 CurrentRotation => transform.eulerAngles;
///
/// Gets or sets the position from the previous frame.
///
public Vector3 PreviousPosition { get; protected set; } = Vector3.zero;
///
/// Gets or sets the scale from the previous frame.
///
public Vector3 PreviousScale { get; protected set; } = Vector3.zero;
///
/// Gets or sets the rotation from the previous frame in Euler angles.
///
public Vector3 PreviousRotation { get; protected set; } = Vector3.zero;
///
/// Event triggered when position changes exceed the defined threshold.
/// Provides the old and new position values.
///
[Tooltip("Event triggered when position changes exceed the threshold")]
public UnityEvent onChangedPosition = new UnityEvent();
///
/// Event triggered when scale changes exceed the defined threshold.
/// Provides the old and new scale values.
///
[Tooltip("Event triggered when scale changes exceed the threshold")]
public UnityEvent onChangedScale = new UnityEvent();
///
/// Event triggered when rotation changes exceed the defined threshold.
/// Provides the old and new rotation values in Euler angles.
///
[Tooltip("Event triggered when rotation changes exceed the threshold")]
public UnityEvent onChangedRotation = new UnityEvent();
///
/// Detects if a Vector3 value has changed beyond the specified threshold.
///
/// Reference to the current value being tracked.
/// The new value to compare against.
/// The minimum distance required to register a change.
/// True if the change exceeds the threshold, false otherwise.
private bool DetectChange(ref Vector3 curValue, Vector3 newValue, float threshold)
{
// Calculate the distance between the current and new values
var dis = Vector3.Distance(newValue, curValue);
// If the distance is less than or equal to the threshold, no significant change
if (dis <= threshold)
return false;
// Update the current value to the new value
curValue = newValue;
// Return true to indicate a significant change was detected
return true;
}
///
/// Monitors transform changes every frame and triggers events when changes exceed thresholds.
///
private void Update()
{
// Get the current transform values
var pos = transform.position;
var scale = transform.localScale;
var rotation = transform.eulerAngles;
// Check for position changes and trigger events if needed
if (!ignore.position && DetectChange(ref _lastPosition, pos, positionThreshold))
{
onChangedPosition?.Invoke(new TransformChange()
{
NewValue = pos,
OldValue = _lastPosition
});
}
// Check for rotation changes and trigger events if needed
if (!ignore.rotation && DetectChange(ref _lastRotation, rotation, rotationThreshold))
{
onChangedRotation?.Invoke(new TransformChange()
{
NewValue = rotation,
OldValue = _lastRotation
});
}
// Check for scale changes and trigger events if needed
if (!ignore.scale && DetectChange(ref _lastScale, scale, scaleThreshold))
{
onChangedScale?.Invoke(new TransformChange()
{
NewValue = scale,
OldValue = _lastScale
});
}
// Update the previous frame values
PreviousPosition = pos;
PreviousScale = scale;
PreviousRotation = rotation;
}
}
}