XRPokeFollowAffordanceFill.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. using Unity.Mathematics;
  2. using Unity.XR.CoreUtils.Bindings;
  3. using UnityEngine;
  4. using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.State;
  5. using UnityEngine.XR.Interaction.Toolkit.Filtering;
  6. using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
  7. namespace Unity.VRTemplate
  8. {
  9. /// <summary>
  10. /// Follow animation affordance for <see cref="IPokeStateDataProvider"/>, such as <see cref="XRPokeFilter"/>.
  11. /// Used to animate a pressed transform, such as a button to follow the poke position.
  12. /// </summary>
  13. [AddComponentMenu("XR/XR Poke Follow Affordance Fill", 22)]
  14. public class XRPokeFollowAffordanceFill : MonoBehaviour
  15. {
  16. [SerializeField]
  17. [Tooltip("Transform that will move in the poke direction when this or a parent GameObject is poked." +
  18. "\nNote: Should be a direct child GameObject.")]
  19. Transform m_PokeFollowTransform;
  20. [SerializeField]
  21. [Tooltip("Transform that will scale the mask when this interactable is poked.")]
  22. RectTransform m_PokeFill;
  23. [SerializeField]
  24. [Tooltip("The max width size for the poke fill image when pressed")]
  25. float m_PokeFillMaxSizeX;
  26. [SerializeField]
  27. [Tooltip("The max height size for the poke fill image when pressed")]
  28. float m_PokeFillMaxSizeY;
  29. /// <summary>
  30. /// Transform that will animate along the axis of interaction when this interactable is poked.
  31. /// Note: Must be a direct child GameObject as it moves in local space relative to the poke target's transform.
  32. /// </summary>
  33. public Transform pokeFollowTransform
  34. {
  35. get => m_PokeFollowTransform;
  36. set => m_PokeFollowTransform = value;
  37. }
  38. [SerializeField]
  39. [Range(0f, 20f)]
  40. [Tooltip("Multiplies transform position interpolation as a factor of Time.deltaTime. If 0, no smoothing will be applied.")]
  41. float m_SmoothingSpeed = 8f;
  42. /// <summary>
  43. /// Multiplies transform position interpolation as a factor of <see cref="Time.deltaTime"/>. If <c>0</c>, no smoothing will be applied.
  44. /// </summary>
  45. public float smoothingSpeed
  46. {
  47. get => m_SmoothingSpeed;
  48. set => m_SmoothingSpeed = value;
  49. }
  50. [SerializeField]
  51. [Tooltip("When this component is no longer the target of the poke, the Poke Follow Transform returns to the original position.")]
  52. bool m_ReturnToInitialPosition = true;
  53. /// <summary>
  54. /// When this component is no longer the target of the poke, the <see cref="pokeFollowTransform"/> returns to the original position.
  55. /// </summary>
  56. public bool returnToInitialPosition
  57. {
  58. get => m_ReturnToInitialPosition;
  59. set => m_ReturnToInitialPosition = value;
  60. }
  61. [SerializeField]
  62. [Tooltip("Whether to apply the follow animation if the target of the poke is a child of this transform. " +
  63. "This is useful for UI objects that may have child graphics.")]
  64. bool m_ApplyIfChildIsTarget = true;
  65. /// <summary>
  66. /// Whether to apply the follow animation if the target of the poke is a child of this transform.
  67. /// This is useful for UI objects that may have child graphics.
  68. /// </summary>
  69. public bool applyIfChildIsTarget
  70. {
  71. get => m_ApplyIfChildIsTarget;
  72. set => m_ApplyIfChildIsTarget = value;
  73. }
  74. [Header("Distance Clamping")]
  75. [SerializeField]
  76. [Tooltip("Whether to keep the Poke Follow Transform from moving past a minimum distance from the poke target.")]
  77. bool m_ClampToMinDistance;
  78. /// <summary>
  79. /// Whether to keep the <see cref="pokeFollowTransform"/> from moving past <see cref="minDistance"/> from the poke target.
  80. /// </summary>
  81. public bool clampToMinDistance
  82. {
  83. get => m_ClampToMinDistance;
  84. set => m_ClampToMinDistance = value;
  85. }
  86. [SerializeField]
  87. [Tooltip("The minimum distance from this transform that the Poke Follow Transform can move.")]
  88. float m_MinDistance;
  89. /// <summary>
  90. /// The minimum distance from this transform that the <see cref="pokeFollowTransform"/> can move when
  91. /// <see cref="clampToMinDistance"/> is <see langword="true"/>.
  92. /// </summary>
  93. public float minDistance
  94. {
  95. get => m_MinDistance;
  96. set => m_MinDistance = value;
  97. }
  98. [Space]
  99. [SerializeField]
  100. [Tooltip("Whether to keep the Poke Follow Transform from moving past a maximum distance from the poke target.")]
  101. bool m_ClampToMaxDistance;
  102. /// <summary>
  103. /// Whether to keep the <see cref="pokeFollowTransform"/> from moving past <see cref="maxDistance"/> from the poke target.
  104. /// </summary>
  105. public bool clampToMaxDistance
  106. {
  107. get => m_ClampToMaxDistance;
  108. set => m_ClampToMaxDistance = value;
  109. }
  110. [SerializeField]
  111. [Tooltip("The maximum distance from this transform that the Poke Follow Transform can move. Will shrink to the distance of initial position if that is smaller, or if this is 0.")]
  112. float m_MaxDistance;
  113. /// <summary>
  114. /// The maximum distance from this transform that the <see cref="pokeFollowTransform"/> can move when
  115. /// <see cref="clampToMaxDistance"/> is <see langword="true"/>.
  116. /// </summary>
  117. public float maxDistance
  118. {
  119. get => m_MaxDistance;
  120. set => m_MaxDistance = value;
  121. }
  122. IPokeStateDataProvider m_PokeDataProvider;
  123. #pragma warning disable CS0618 // Type or member is obsolete
  124. readonly Vector3TweenableVariable m_TransformTweenableVariable = new Vector3TweenableVariable();
  125. readonly FloatTweenableVariable m_PokeStrengthTweenableVariable = new FloatTweenableVariable();
  126. #pragma warning restore CS0618 // Type or member is obsolete
  127. readonly BindingsGroup m_BindingsGroup = new BindingsGroup();
  128. Vector3 m_InitialPosition;
  129. bool m_IsFirstFrame;
  130. /// <summary>
  131. /// See <see cref="MonoBehaviour"/>.
  132. /// </summary>
  133. protected void Awake()
  134. {
  135. m_PokeDataProvider = GetComponentInParent<IPokeStateDataProvider>();
  136. }
  137. /// <summary>
  138. /// See <see cref="MonoBehaviour"/>.
  139. /// </summary>
  140. protected void Start()
  141. {
  142. if (m_PokeFollowTransform != null)
  143. {
  144. m_InitialPosition = m_PokeFollowTransform.localPosition;
  145. m_MaxDistance = m_MaxDistance > 0f ? Mathf.Min(m_InitialPosition.magnitude, m_MaxDistance) : m_InitialPosition.magnitude;
  146. m_BindingsGroup.AddBinding(m_TransformTweenableVariable.Subscribe(OnTransformTweenableVariableUpdated));
  147. m_BindingsGroup.AddBinding(m_PokeStrengthTweenableVariable.Subscribe(OnPokeStrengthChanged));
  148. m_BindingsGroup.AddBinding(m_PokeDataProvider.pokeStateData.SubscribeAndUpdate(OnPokeStateDataUpdated));
  149. }
  150. else
  151. {
  152. enabled = false;
  153. Debug.LogWarning($"Missing Poke Follow Transform assignment on {this}. Disabling component.", this);
  154. }
  155. }
  156. /// <summary>
  157. /// See <see cref="MonoBehaviour"/>.
  158. /// </summary>
  159. protected void OnDestroy()
  160. {
  161. m_BindingsGroup.Clear();
  162. m_TransformTweenableVariable?.Dispose();
  163. }
  164. /// <summary>
  165. /// See <see cref="MonoBehaviour"/>.
  166. /// </summary>
  167. protected void LateUpdate()
  168. {
  169. if (m_IsFirstFrame)
  170. {
  171. m_TransformTweenableVariable.HandleTween(1f);
  172. m_PokeStrengthTweenableVariable.target = 0f;
  173. m_PokeStrengthTweenableVariable.HandleTween(1f);
  174. m_IsFirstFrame = false;
  175. return;
  176. }
  177. float tweenAmt = m_SmoothingSpeed > 0f ? Time.deltaTime * m_SmoothingSpeed : 1f;
  178. m_TransformTweenableVariable.HandleTween(tweenAmt);
  179. m_PokeStrengthTweenableVariable.HandleTween(tweenAmt);
  180. }
  181. void OnTransformTweenableVariableUpdated(float3 position)
  182. {
  183. m_PokeFollowTransform.localPosition = position;
  184. }
  185. void OnPokeStrengthChanged(float newStrength)
  186. {
  187. var newX = m_PokeFillMaxSizeX * newStrength;
  188. var newY = m_PokeFillMaxSizeY * newStrength;
  189. m_PokeFill.sizeDelta = new Vector2(newX, newY);
  190. }
  191. void OnPokeStateDataUpdated(PokeStateData data)
  192. {
  193. var pokeTarget = data.target;
  194. var applyFollow = m_ApplyIfChildIsTarget
  195. ? pokeTarget != null && pokeTarget.IsChildOf(transform)
  196. : pokeTarget == transform;
  197. if (applyFollow)
  198. {
  199. var targetPosition = pokeTarget.InverseTransformPoint(data.axisAlignedPokeInteractionPoint);
  200. if (m_ClampToMinDistance && targetPosition.sqrMagnitude < m_MinDistance * m_MinDistance)
  201. targetPosition = Vector3.ClampMagnitude(targetPosition, m_MinDistance);
  202. if (m_ClampToMaxDistance && targetPosition.sqrMagnitude > m_MaxDistance * m_MaxDistance)
  203. targetPosition = Vector3.ClampMagnitude(targetPosition, m_MaxDistance);
  204. m_TransformTweenableVariable.target = targetPosition;
  205. m_PokeStrengthTweenableVariable.target = Mathf.Clamp01(data.interactionStrength);
  206. }
  207. else if (m_ReturnToInitialPosition)
  208. {
  209. m_TransformTweenableVariable.target = m_InitialPosition;
  210. m_PokeStrengthTweenableVariable.target = 0f;
  211. }
  212. }
  213. }
  214. }