init
This commit is contained in:
108
Assets/VRTemplateAssets/Scripts/AnchorVisuals.cs
Normal file
108
Assets/VRTemplateAssets/Scripts/AnchorVisuals.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper script used to control the Teleport Anchor visuals animations.
|
||||
/// </summary>
|
||||
public class AnchorVisuals : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("The animation for the vertical glow element on the platform.")]
|
||||
Animation m_FadeAnimation;
|
||||
|
||||
[SerializeField, Tooltip("The arrow transform, at the center of the platform.")]
|
||||
Transform m_Arrow;
|
||||
|
||||
[SerializeField, Tooltip("Height of the arrow transform when teleport ray hovers the teleport pad.")]
|
||||
float m_TargetArrowHeight = 1.0f;
|
||||
|
||||
[SerializeField, Tooltip("Animation duration of the arrow transform to and from the target arrow height.")]
|
||||
float m_ArrowAnimationDuration = 0.2f;
|
||||
|
||||
[SerializeField, Tooltip("Animation curve of hte arrow transform to and from the target arrow height.")]
|
||||
AnimationCurve m_AnimationCurve;
|
||||
|
||||
Coroutine m_ArrowCoroutine;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Vector3TweenableVariable m_ArrowHeight;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
Vector3 m_InitialArrowScale;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (m_FadeAnimation != null)
|
||||
{
|
||||
var fadeAnim = m_FadeAnimation;
|
||||
var clipName = m_FadeAnimation.clip.name;
|
||||
fadeAnim[clipName].normalizedTime = 1f;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
m_ArrowHeight = new Vector3TweenableVariable();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
m_ArrowHeight.animationCurve = m_AnimationCurve;
|
||||
m_InitialArrowScale = m_Arrow.localScale;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
m_Arrow.localPosition = m_ArrowHeight.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs animations when teleport interactor enters the teleport anchor selection.
|
||||
/// </summary>
|
||||
public void OnAnchorEnter()
|
||||
{
|
||||
m_Arrow.localScale = m_InitialArrowScale;
|
||||
|
||||
if (m_FadeAnimation != null)
|
||||
{
|
||||
var fadeAnim = m_FadeAnimation;
|
||||
var clipName = m_FadeAnimation.clip.name;
|
||||
fadeAnim[clipName].normalizedTime = 0f;
|
||||
fadeAnim[clipName].speed = 1f;
|
||||
fadeAnim.Play();
|
||||
}
|
||||
|
||||
if (m_ArrowCoroutine != null)
|
||||
StopCoroutine(m_ArrowCoroutine);
|
||||
|
||||
var arrowPosition = m_Arrow.localPosition;
|
||||
m_ArrowCoroutine = StartCoroutine(m_ArrowHeight.PlaySequence(arrowPosition, new float3(arrowPosition.x, m_TargetArrowHeight, arrowPosition.z), m_ArrowAnimationDuration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs animations when teleport interactor exits the teleport anchor selection.
|
||||
/// </summary>
|
||||
public void OnAnchorExit()
|
||||
{
|
||||
if (m_FadeAnimation != null)
|
||||
{
|
||||
// Set time to 1, at the end of the animation, play at 1.5x speed
|
||||
var fadeAnim = m_FadeAnimation;
|
||||
var clipName = m_FadeAnimation.clip.name;
|
||||
fadeAnim[clipName].normalizedTime = 1f;
|
||||
fadeAnim[clipName].speed = -1.5f;
|
||||
fadeAnim.Play();
|
||||
}
|
||||
|
||||
if (m_ArrowCoroutine != null)
|
||||
StopCoroutine(m_ArrowCoroutine);
|
||||
|
||||
var arrowPosition = m_Arrow.localPosition;
|
||||
m_ArrowCoroutine = StartCoroutine(m_ArrowHeight.PlaySequence(arrowPosition, new float3(arrowPosition.x, 0, arrowPosition.z), m_ArrowAnimationDuration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the arrow visual when teleporting
|
||||
/// </summary>
|
||||
public void HideArrowOnTeleport()
|
||||
{
|
||||
m_Arrow.localScale = Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRTemplateAssets/Scripts/AnchorVisuals.cs.meta
Normal file
11
Assets/VRTemplateAssets/Scripts/AnchorVisuals.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2dd7fabdb14fca498bd9e0def11ab19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
202
Assets/VRTemplateAssets/Scripts/BezierCurve.cs
Normal file
202
Assets/VRTemplateAssets/Scripts/BezierCurve.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws a bezier curve from a starting point transform to an end point transform
|
||||
/// </summary>
|
||||
public class BezierCurve : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// If the view scale changes more than this amount, then the line width will be updated causing the line to be rebuilt.
|
||||
/// </summary>
|
||||
const float k_ViewerScaleChangeThreshold = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// The time within the frame that the curve will be updated.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnityEngine.XR.Interaction.Toolkit.XRBaseController.UpdateType"/>
|
||||
public enum UpdateType
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample at both update and directly before rendering. For smooth tracking,
|
||||
/// we recommend using this value as it will provide the lowest input latency for the device.
|
||||
/// </summary>
|
||||
UpdateAndBeforeRender,
|
||||
|
||||
/// <summary>
|
||||
/// Only sample input during the update phase of the frame.
|
||||
/// </summary>
|
||||
Update,
|
||||
|
||||
/// <summary>
|
||||
/// Only sample input directly before rendering.
|
||||
/// </summary>
|
||||
BeforeRender,
|
||||
}
|
||||
|
||||
#pragma warning disable 649
|
||||
[SerializeField, Tooltip("The time within the frame that the curve will be updated. If this Bezier Curve is attached to a transform that is updating before render, then enabling updates in Before Render will keep the line connected without delay.")]
|
||||
UpdateType m_UpdateTrackingType = UpdateType.Update;
|
||||
|
||||
[SerializeField, Tooltip("The transform that determines the position, handle rotation, and handle scale of the start point of the bezier curve.")]
|
||||
Transform m_StartPoint;
|
||||
|
||||
[SerializeField, Tooltip("The transform that determines the position, handle rotation, and handle scale of the end point of the bezier curve.")]
|
||||
Transform m_EndPoint;
|
||||
|
||||
[SerializeField, Tooltip("Controls the scale factor of the curve's start bezier handle.")]
|
||||
float m_CurveFactorStart = 1.0f;
|
||||
|
||||
[SerializeField, Tooltip("Controls the scale factor of the curve's end bezier handle.")]
|
||||
float m_CurveFactorEnd = 1.0f;
|
||||
|
||||
[SerializeField, Tooltip("Controls the number of segments used to draw the curve.")]
|
||||
int m_SegmentCount = 50;
|
||||
|
||||
[SerializeField, Tooltip("When enabled, the line color gradient will be animated so that an opaque part travels along the line.")]
|
||||
bool m_Animate;
|
||||
|
||||
[SerializeField, Tooltip("If animated, this controls the speed that the animation of the line.")]
|
||||
float m_AnimSpeed = 0.25f;
|
||||
|
||||
[SerializeField, Tooltip("If animated, this color will be the main opaque color of the gradient")]
|
||||
Color m_GradientKeyColor = new Color(0.1254902f, 0.5882353f, 0.9529412f);
|
||||
|
||||
[SerializeField, Tooltip("The line renderer that will draw the curve. If not set it will find a line renderer on this GameObject.")]
|
||||
LineRenderer m_LineRenderer;
|
||||
#pragma warning restore 649
|
||||
|
||||
Vector3[] m_ControlPoints = new Vector3[4];
|
||||
float m_Time;
|
||||
float m_LineWidth;
|
||||
float m_LastViewerScale;
|
||||
|
||||
Vector3 m_LastStartPosition;
|
||||
Vector3 m_LastEndPosition;
|
||||
//IProvidesViewerScale IFunctionalitySubscriber<IProvidesViewerScale>.provider { get; set; }
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (m_LineRenderer == null)
|
||||
m_LineRenderer = GetComponent<LineRenderer>();
|
||||
|
||||
m_LineWidth = m_LineRenderer.startWidth;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
DrawCurve();
|
||||
Application.onBeforeRender += OnBeforeRender;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Application.onBeforeRender -= OnBeforeRender;
|
||||
|
||||
}
|
||||
|
||||
void OnBeforeRender()
|
||||
{
|
||||
if (m_UpdateTrackingType == UpdateType.BeforeRender || m_UpdateTrackingType == UpdateType.UpdateAndBeforeRender)
|
||||
DrawCurve();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (m_UpdateTrackingType == UpdateType.Update || m_UpdateTrackingType == UpdateType.UpdateAndBeforeRender)
|
||||
DrawCurve();
|
||||
|
||||
if (m_Animate)
|
||||
{
|
||||
AnimateCurve();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the line points to draw the bezier curve.
|
||||
/// </summary>
|
||||
[ContextMenu("Draw")]
|
||||
public void DrawCurve()
|
||||
{
|
||||
var startPointPosition = m_StartPoint.position;
|
||||
var endPointPosition = m_EndPoint.position;
|
||||
|
||||
if (startPointPosition == m_LastStartPosition &&
|
||||
endPointPosition == m_LastEndPosition)
|
||||
return; // Return early if the start and end have not changed to avoid recalculating the curve
|
||||
|
||||
var dist = Vector3.Distance(startPointPosition, endPointPosition);
|
||||
|
||||
m_ControlPoints[0] = startPointPosition;
|
||||
m_ControlPoints[1] = startPointPosition + (m_StartPoint.right * (dist * m_CurveFactorStart));
|
||||
m_ControlPoints[2] = endPointPosition - (m_EndPoint.right * (dist * m_CurveFactorEnd));
|
||||
m_ControlPoints[3] = endPointPosition;
|
||||
|
||||
int segmentCount;
|
||||
const float smallestCurveLength = 0.0125f;
|
||||
if (Vector3.Distance(startPointPosition, endPointPosition) < (smallestCurveLength * m_LastViewerScale))
|
||||
{
|
||||
segmentCount = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
segmentCount = m_SegmentCount;
|
||||
}
|
||||
|
||||
m_LineRenderer.positionCount = segmentCount + 1;
|
||||
m_LineRenderer.SetPosition(0, m_ControlPoints[0]);
|
||||
for (var i = 1; i <= segmentCount; i++)
|
||||
{
|
||||
var t = i / (float)segmentCount;
|
||||
var pixel = CalculateCubicBezierPoint(t, m_ControlPoints[0], m_ControlPoints[1], m_ControlPoints[2], m_ControlPoints[3]);
|
||||
m_LineRenderer.SetPosition(i, pixel);
|
||||
}
|
||||
|
||||
m_LastStartPosition = startPointPosition;
|
||||
m_LastEndPosition = endPointPosition;
|
||||
}
|
||||
|
||||
static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
|
||||
{
|
||||
var u = 1 - t;
|
||||
var tt = t * t;
|
||||
var uu = u * u;
|
||||
var uuu = uu * u;
|
||||
var ttt = tt * t;
|
||||
|
||||
var p = uuu * p0;
|
||||
p += 3 * uu * t * p1;
|
||||
p += 3 * u * tt * p2;
|
||||
p += ttt * p3;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void AnimateCurve()
|
||||
{
|
||||
var newGrad = new Gradient();
|
||||
|
||||
var colorKeys = new GradientColorKey[1];
|
||||
var alphaKeys = new GradientAlphaKey[2];
|
||||
|
||||
var colorKey = new GradientColorKey(m_GradientKeyColor, 0f);
|
||||
colorKeys[0] = colorKey;
|
||||
|
||||
var alphaKeyStart = new GradientAlphaKey(.25f, m_Time);
|
||||
var alphaKeyEnd = new GradientAlphaKey(1f, 1f);
|
||||
alphaKeys[0] = alphaKeyStart;
|
||||
alphaKeys[1] = alphaKeyEnd;
|
||||
|
||||
newGrad.SetKeys(colorKeys, alphaKeys);
|
||||
newGrad.mode = GradientMode.Blend;
|
||||
|
||||
m_LineRenderer.colorGradient = newGrad;
|
||||
m_Time += (Time.unscaledDeltaTime * m_AnimSpeed);
|
||||
|
||||
if (m_Time >= 1f)
|
||||
m_Time = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRTemplateAssets/Scripts/BezierCurve.cs.meta
Normal file
11
Assets/VRTemplateAssets/Scripts/BezierCurve.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72fb4b8d89bc26347a49177acaa93913
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the visual states of a boolean toggle switch UI
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Toggle))]
|
||||
public class BooleanToggleVisualsController : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
const float k_TargetPositionX = 17f;
|
||||
|
||||
#pragma warning disable 649
|
||||
[SerializeField, Tooltip("The boolean toggle knob.")]
|
||||
RectTransform m_Knob;
|
||||
|
||||
[SerializeField, Tooltip("How much to translate the button imagery on the z on hover.")]
|
||||
float m_ZTranslation = 5f;
|
||||
#pragma warning restore 649
|
||||
|
||||
Toggle m_Toggle;
|
||||
float m_InitialBackground;
|
||||
Coroutine m_ColorFade;
|
||||
Coroutine m_LocalMove;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_Toggle = gameObject.GetComponent<Toggle>();
|
||||
|
||||
//Add listener for when the state of the Toggle changes, to take action
|
||||
m_Toggle.onValueChanged.AddListener(ToggleValueChanged);
|
||||
|
||||
if (m_Knob != null)
|
||||
{
|
||||
m_InitialBackground = m_Knob.localPosition.z;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
ToggleValueChanged(m_Toggle.isOn);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
PerformEntranceActions();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IPointerExitHandler.OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
PerformExitActions();
|
||||
}
|
||||
|
||||
void ToggleValueChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
m_Knob.localPosition = new Vector3(k_TargetPositionX, m_Knob.localPosition.y, m_Knob.localPosition.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Knob.localPosition = new Vector3(-k_TargetPositionX, m_Knob.localPosition.y, m_Knob.localPosition.z);
|
||||
}
|
||||
}
|
||||
|
||||
void PerformEntranceActions()
|
||||
{
|
||||
if (m_Knob != null)
|
||||
{
|
||||
var backgroundLocalPosition = m_Knob.localPosition;
|
||||
backgroundLocalPosition.z = m_InitialBackground - m_ZTranslation;
|
||||
m_Knob.localPosition = backgroundLocalPosition;
|
||||
}
|
||||
}
|
||||
|
||||
void PerformExitActions()
|
||||
{
|
||||
if (m_Knob != null)
|
||||
{
|
||||
var backgroundLocalPosition = m_Knob.localPosition;
|
||||
backgroundLocalPosition.z = m_InitialBackground;
|
||||
m_Knob.localPosition = backgroundLocalPosition;
|
||||
m_Knob.localScale = Vector3.one;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f390e213230ce1d42a51aed871ab74ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
99
Assets/VRTemplateAssets/Scripts/Callout.cs
Normal file
99
Assets/VRTemplateAssets/Scripts/Callout.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Callout used to display information like world and controller tooltips.
|
||||
/// </summary>
|
||||
public class Callout : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The tooltip Transform associated with this Callout.")]
|
||||
Transform m_LazyTooltip;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The line curve GameObject associated with this Callout.")]
|
||||
GameObject m_Curve;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The required time to dwell on this callout before the tooltip and curve are enabled.")]
|
||||
float m_DwellTime = 1f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether the associated tooltip will be unparented on Start.")]
|
||||
bool m_Unparent = true;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether the associated tooltip and curve will be disabled on Start.")]
|
||||
bool m_TurnOffAtStart = true;
|
||||
|
||||
bool m_Gazing = false;
|
||||
|
||||
Coroutine m_StartCo;
|
||||
Coroutine m_EndCo;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (m_Unparent)
|
||||
{
|
||||
if (m_LazyTooltip != null)
|
||||
m_LazyTooltip.SetParent(null);
|
||||
}
|
||||
|
||||
if (m_TurnOffAtStart)
|
||||
{
|
||||
if (m_LazyTooltip != null)
|
||||
m_LazyTooltip.gameObject.SetActive(false);
|
||||
if (m_Curve != null)
|
||||
m_Curve.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void GazeHoverStart()
|
||||
{
|
||||
m_Gazing = true;
|
||||
if (m_StartCo != null)
|
||||
StopCoroutine(m_StartCo);
|
||||
if (m_EndCo != null)
|
||||
StopCoroutine(m_EndCo);
|
||||
m_StartCo = StartCoroutine(StartDelay());
|
||||
}
|
||||
|
||||
public void GazeHoverEnd()
|
||||
{
|
||||
m_Gazing = false;
|
||||
m_EndCo = StartCoroutine(EndDelay());
|
||||
}
|
||||
|
||||
IEnumerator StartDelay()
|
||||
{
|
||||
yield return new WaitForSeconds(m_DwellTime);
|
||||
if (m_Gazing)
|
||||
TurnOnStuff();
|
||||
}
|
||||
|
||||
IEnumerator EndDelay()
|
||||
{
|
||||
if (!m_Gazing)
|
||||
TurnOffStuff();
|
||||
yield return null;
|
||||
}
|
||||
|
||||
void TurnOnStuff()
|
||||
{
|
||||
if (m_LazyTooltip != null)
|
||||
m_LazyTooltip.gameObject.SetActive(true);
|
||||
if (m_Curve != null)
|
||||
m_Curve.SetActive(true);
|
||||
}
|
||||
|
||||
void TurnOffStuff()
|
||||
{
|
||||
if (m_LazyTooltip != null)
|
||||
m_LazyTooltip.gameObject.SetActive(false);
|
||||
if (m_Curve != null)
|
||||
m_Curve.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRTemplateAssets/Scripts/Callout.cs.meta
Normal file
11
Assets/VRTemplateAssets/Scripts/Callout.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16809ed3baa3d2341b75ec4c0aa874d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
80
Assets/VRTemplateAssets/Scripts/CalloutGazeController.cs
Normal file
80
Assets/VRTemplateAssets/Scripts/CalloutGazeController.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using Unity.XR.CoreUtils;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Fires events when this object is is within the field of view of the gaze transform. This is currently used to
|
||||
/// hide and show tooltip callouts on the controllers when the controllers are within the field of view.
|
||||
/// </summary>
|
||||
public class CalloutGazeController : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("The transform which the forward direction will be used to evaluate as the gaze direction.")]
|
||||
Transform m_GazeTransform;
|
||||
|
||||
[SerializeField, Tooltip("Threshold for the dot product when determining if the Gaze Transform is facing this object. The lower the threshold, the wider the field of view."), Range(0.0f, 1.0f)]
|
||||
float m_FacingThreshold = 0.85f;
|
||||
|
||||
[SerializeField, Tooltip("Events fired when the Gaze Transform begins facing this game object")]
|
||||
UnityEvent m_FacingEntered;
|
||||
|
||||
[SerializeField, Tooltip("Events fired when the Gaze Transform stops facing this game object")]
|
||||
UnityEvent m_FacingExited;
|
||||
|
||||
[SerializeField, Tooltip("Distance threshold for movement in a single frame that determines a large movement that will trigger Facing Exited events.")]
|
||||
float m_LargeMovementDistanceThreshold = 0.05f;
|
||||
|
||||
[SerializeField, Tooltip("Cool down time after a large movement for Facing Entered events to fire again.")]
|
||||
float m_LargeMovementCoolDownTime = 0.25f;
|
||||
|
||||
bool m_IsFacing;
|
||||
float m_LargeMovementCoolDown;
|
||||
Vector3 m_LastPosition;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!m_GazeTransform)
|
||||
return;
|
||||
|
||||
CheckLargeMovement();
|
||||
|
||||
if (m_LargeMovementCoolDown < m_LargeMovementCoolDownTime)
|
||||
return;
|
||||
|
||||
var dotProduct = Vector3.Dot(m_GazeTransform.forward, (transform.position - m_GazeTransform.position).normalized);
|
||||
if (dotProduct > m_FacingThreshold && !m_IsFacing)
|
||||
FacingEntered();
|
||||
else if (dotProduct < m_FacingThreshold && m_IsFacing)
|
||||
FacingExited();
|
||||
}
|
||||
|
||||
void CheckLargeMovement()
|
||||
{
|
||||
// Check if there is large movement
|
||||
var currentPosition = transform.position;
|
||||
var positionDelta = Mathf.Abs(Vector3.Distance(m_LastPosition, currentPosition));
|
||||
if (positionDelta > m_LargeMovementDistanceThreshold)
|
||||
{
|
||||
m_LargeMovementCoolDown = 0.0f;
|
||||
FacingExited();
|
||||
}
|
||||
m_LargeMovementCoolDown += Time.deltaTime;
|
||||
m_LastPosition = currentPosition;
|
||||
}
|
||||
|
||||
void FacingEntered()
|
||||
{
|
||||
m_IsFacing = true;
|
||||
m_FacingEntered.Invoke();
|
||||
}
|
||||
|
||||
void FacingExited()
|
||||
{
|
||||
m_IsFacing = false;
|
||||
m_FacingExited.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84f6509f3c7fa7b4899c9c767f49e622
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
19
Assets/VRTemplateAssets/Scripts/DestroyObject.cs
Normal file
19
Assets/VRTemplateAssets/Scripts/DestroyObject.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Destroys GameObject after a few seconds.
|
||||
/// </summary>
|
||||
public class DestroyObject : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("Time before destroying in seconds.")]
|
||||
float m_Lifetime = 5f;
|
||||
|
||||
void Start()
|
||||
{
|
||||
Destroy(gameObject, m_Lifetime);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRTemplateAssets/Scripts/DestroyObject.cs.meta
Normal file
11
Assets/VRTemplateAssets/Scripts/DestroyObject.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 210883af668c0fc4aab890d9ad3cfd6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
75
Assets/VRTemplateAssets/Scripts/HandSubsystemManager.cs
Normal file
75
Assets/VRTemplateAssets/Scripts/HandSubsystemManager.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Hands;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is a convenience wrapper to handle external start/stop
|
||||
/// of a currently running XR Hand Subsystem.
|
||||
/// </summary>
|
||||
/// <seealso cref="XRHandSubsystem"/>
|
||||
public class HandSubsystemManager : MonoBehaviour
|
||||
{
|
||||
static List<XRHandSubsystem> s_HandSubsystems;
|
||||
XRHandSubsystem m_HandSubsystem;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (m_HandSubsystem == null)
|
||||
{
|
||||
TryGetHandSubsystem(out m_HandSubsystem);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function will attempt to find a currently running hand tracking subsystem and stop it.
|
||||
/// </summary>
|
||||
/// <seealso cref="XRHandSubsystem"/>
|
||||
public void DisableHandTracking()
|
||||
{
|
||||
if (m_HandSubsystem != null || TryGetHandSubsystem(out m_HandSubsystem))
|
||||
{
|
||||
m_HandSubsystem.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function will attempt to find a current hand tracking subsystem and start it up.
|
||||
/// </summary>
|
||||
/// <seealso cref="XRHandSubsystem"/>
|
||||
public void EnableHandTracking()
|
||||
{
|
||||
if (m_HandSubsystem != null || TryGetHandSubsystem(out m_HandSubsystem))
|
||||
{
|
||||
m_HandSubsystem.Start();
|
||||
}
|
||||
}
|
||||
|
||||
// This is taken from XRInputTrackingAggregator and should be removed once the internal version
|
||||
// has been made publicly available.
|
||||
static bool TryGetHandSubsystem(out XRHandSubsystem handSubsystem)
|
||||
{
|
||||
s_HandSubsystems ??= new List<XRHandSubsystem>();
|
||||
SubsystemManager.GetSubsystems(s_HandSubsystems);
|
||||
if (s_HandSubsystems.Count == 0)
|
||||
{
|
||||
handSubsystem = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s_HandSubsystems.Count > 1)
|
||||
{
|
||||
for (var i = 0; i < s_HandSubsystems.Count; ++i)
|
||||
{
|
||||
handSubsystem = s_HandSubsystems[i];
|
||||
if (handSubsystem.running)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
handSubsystem = s_HandSubsystems[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d52987a218afbc4c81f20e47e2cb295
|
||||
36
Assets/VRTemplateAssets/Scripts/LaunchProjectile.cs
Normal file
36
Assets/VRTemplateAssets/Scripts/LaunchProjectile.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Apply forward force to instantiated prefab
|
||||
/// </summary>
|
||||
public class LaunchProjectile : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The projectile that's created")]
|
||||
GameObject m_ProjectilePrefab = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The point that the project is created")]
|
||||
Transform m_StartPoint = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The speed at which the projectile is launched")]
|
||||
float m_LaunchSpeed = 1.0f;
|
||||
|
||||
public void Fire()
|
||||
{
|
||||
GameObject newObject = Instantiate(m_ProjectilePrefab, m_StartPoint.position, m_StartPoint.rotation, null);
|
||||
|
||||
if (newObject.TryGetComponent(out Rigidbody rigidBody))
|
||||
ApplyForce(rigidBody);
|
||||
}
|
||||
|
||||
void ApplyForce(Rigidbody rigidBody)
|
||||
{
|
||||
Vector3 force = m_StartPoint.forward * m_LaunchSpeed;
|
||||
rigidBody.AddForce(force);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRTemplateAssets/Scripts/LaunchProjectile.cs.meta
Normal file
11
Assets/VRTemplateAssets/Scripts/LaunchProjectile.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f369c769e8961844a3c7ccacd4c3cd5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Assets/VRTemplateAssets/Scripts/RayAttachModifier.cs
Normal file
45
Assets/VRTemplateAssets/Scripts/RayAttachModifier.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Unity.XR.CoreUtils;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactables;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactors;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Add this to your interactable to make it snap to the source of the XR Ray Interactor
|
||||
/// instead of staying at a distance. Has a similar outcome as enabling Force Grab.
|
||||
/// </summary>
|
||||
public class RayAttachModifier : MonoBehaviour
|
||||
{
|
||||
IXRSelectInteractable m_SelectInteractable;
|
||||
|
||||
protected void OnEnable()
|
||||
{
|
||||
m_SelectInteractable = GetComponent<IXRSelectInteractable>();
|
||||
if (m_SelectInteractable as Object == null)
|
||||
{
|
||||
Debug.LogError($"Ray Attach Modifier missing required Select Interactable on {name}", this);
|
||||
return;
|
||||
}
|
||||
|
||||
m_SelectInteractable.selectEntered.AddListener(OnSelectEntered);
|
||||
}
|
||||
|
||||
protected void OnDisable()
|
||||
{
|
||||
if (m_SelectInteractable as Object != null)
|
||||
m_SelectInteractable.selectEntered.RemoveListener(OnSelectEntered);
|
||||
}
|
||||
|
||||
void OnSelectEntered(SelectEnterEventArgs args)
|
||||
{
|
||||
if (!(args.interactorObject is XRRayInteractor))
|
||||
return;
|
||||
|
||||
var attachTransform = args.interactorObject.GetAttachTransform(m_SelectInteractable);
|
||||
var originalAttachPose = args.interactorObject.GetLocalAttachPoseOnSelect(m_SelectInteractable);
|
||||
attachTransform.SetLocalPose(originalAttachPose);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRTemplateAssets/Scripts/RayAttachModifier.cs.meta
Normal file
11
Assets/VRTemplateAssets/Scripts/RayAttachModifier.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4f2f658073e4974d800c553fe797db1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/VRTemplateAssets/Scripts/Rotator.cs
Normal file
18
Assets/VRTemplateAssets/Scripts/Rotator.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Rotates this object at a user defined speed
|
||||
/// </summary>
|
||||
public class Rotator : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("Angular velocity in degrees per second")]
|
||||
Vector3 m_Velocity;
|
||||
|
||||
void Update()
|
||||
{
|
||||
transform.Rotate(m_Velocity * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRTemplateAssets/Scripts/Rotator.cs.meta
Normal file
11
Assets/VRTemplateAssets/Scripts/Rotator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bb83e3cfa27c6d4487384ed695c76bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
39
Assets/VRTemplateAssets/Scripts/StepManager.cs
Normal file
39
Assets/VRTemplateAssets/Scripts/StepManager.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the steps in the in coaching card.
|
||||
/// </summary>
|
||||
public class StepManager : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
class Step
|
||||
{
|
||||
[SerializeField]
|
||||
public GameObject stepObject;
|
||||
|
||||
[SerializeField]
|
||||
public string buttonText;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
public TextMeshProUGUI m_StepButtonTextField;
|
||||
|
||||
[SerializeField]
|
||||
List<Step> m_StepList = new List<Step>();
|
||||
|
||||
int m_CurrentStepIndex = 0;
|
||||
|
||||
public void Next()
|
||||
{
|
||||
m_StepList[m_CurrentStepIndex].stepObject.SetActive(false);
|
||||
m_CurrentStepIndex = (m_CurrentStepIndex + 1) % m_StepList.Count;
|
||||
m_StepList[m_CurrentStepIndex].stepObject.SetActive(true);
|
||||
m_StepButtonTextField.text = m_StepList[m_CurrentStepIndex].buttonText;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRTemplateAssets/Scripts/StepManager.cs.meta
Normal file
11
Assets/VRTemplateAssets/Scripts/StepManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fc0d34940dd44129c17bc6d4396b866
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Assets/VRTemplateAssets/Scripts/VideoPlayerRenderTexture.cs
Normal file
40
Assets/VRTemplateAssets/Scripts/VideoPlayerRenderTexture.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a RenderTexture for rendering video to a target renderer.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(VideoPlayer))]
|
||||
public class VideoPlayerRenderTexture : MonoBehaviour
|
||||
{
|
||||
const string k_ShaderName = "Unlit/Texture";
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The target Renderer which will display the video.")]
|
||||
Renderer m_Renderer;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The width of the RenderTexture which will be created.")]
|
||||
int m_RenderTextureWidth = 1920;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The height of the RenderTexture which will be created.")]
|
||||
int m_RenderTextureHeight = 1080;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The bit depth of the depth channel for the RenderTexture which will be created.")]
|
||||
int m_RenderTextureDepth;
|
||||
|
||||
void Start()
|
||||
{
|
||||
var renderTexture = new RenderTexture(m_RenderTextureWidth, m_RenderTextureHeight, m_RenderTextureDepth);
|
||||
renderTexture.Create();
|
||||
var material = new Material(Shader.Find(k_ShaderName));
|
||||
material.mainTexture = renderTexture;
|
||||
GetComponent<VideoPlayer>().targetTexture = renderTexture;
|
||||
m_Renderer.material = material;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9f926e9bbc6b3149869d0f7ecdf53a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
192
Assets/VRTemplateAssets/Scripts/VideoTimeScrubControl.cs
Normal file
192
Assets/VRTemplateAssets/Scripts/VideoTimeScrubControl.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects a UI slider control to a video player, allowing users to scrub to a particular time in th video.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(VideoPlayer))]
|
||||
public class VideoTimeScrubControl : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("Video play/pause button GameObject")]
|
||||
GameObject m_ButtonPlayOrPause;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Slider that controls the video")]
|
||||
Slider m_Slider;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Play icon sprite")]
|
||||
Sprite m_IconPlay;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Pause icon sprite")]
|
||||
Sprite m_IconPause;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Play or pause button image.")]
|
||||
Image m_ButtonPlayOrPauseIcon;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Text that displays the current time of the video.")]
|
||||
TextMeshProUGUI m_VideoTimeText;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("If checked, the slider will fade off after a few seconds. If unchecked, the slider will remain on.")]
|
||||
bool m_HideSliderAfterFewSeconds;
|
||||
|
||||
bool m_IsDragging;
|
||||
bool m_VideoIsPlaying;
|
||||
bool m_VideoJumpPending;
|
||||
long m_LastFrameBeforeScrub;
|
||||
VideoPlayer m_VideoPlayer;
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_VideoPlayer = GetComponent<VideoPlayer>();
|
||||
if (!m_VideoPlayer.playOnAwake)
|
||||
{
|
||||
m_VideoPlayer.playOnAwake = true; // Set play on awake for next enable.
|
||||
m_VideoPlayer.Play(); // Play video to load first frame.
|
||||
VideoStop(); // Stop the video to set correct state and pause frame.
|
||||
}
|
||||
else
|
||||
{
|
||||
VideoPlay(); // Play to ensure correct state.
|
||||
}
|
||||
|
||||
if (m_ButtonPlayOrPause != null)
|
||||
m_ButtonPlayOrPause.SetActive(false);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (m_VideoPlayer != null)
|
||||
{
|
||||
m_VideoPlayer.frame = 0;
|
||||
VideoPlay(); // Ensures correct UI state update if paused.
|
||||
}
|
||||
|
||||
m_Slider.value = 0.0f;
|
||||
m_Slider.onValueChanged.AddListener(OnSliderValueChange);
|
||||
m_Slider.gameObject.SetActive(true);
|
||||
if (m_HideSliderAfterFewSeconds)
|
||||
StartCoroutine(HideSliderAfterSeconds());
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (m_VideoJumpPending)
|
||||
{
|
||||
// We're trying to jump to a new position, but we're checking to make sure the video player is updated to our new jump frame.
|
||||
if (m_LastFrameBeforeScrub == m_VideoPlayer.frame)
|
||||
return;
|
||||
|
||||
// If the video player has been updated with desired jump frame, reset these values.
|
||||
m_LastFrameBeforeScrub = long.MinValue;
|
||||
m_VideoJumpPending = false;
|
||||
}
|
||||
|
||||
if (!m_IsDragging && !m_VideoJumpPending)
|
||||
{
|
||||
if (m_VideoPlayer.frameCount > 0)
|
||||
{
|
||||
var progress = (float)m_VideoPlayer.frame / m_VideoPlayer.frameCount;
|
||||
m_Slider.value = progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerDown()
|
||||
{
|
||||
m_VideoJumpPending = true;
|
||||
VideoStop();
|
||||
VideoJump();
|
||||
}
|
||||
|
||||
public void OnRelease()
|
||||
{
|
||||
m_IsDragging = false;
|
||||
VideoPlay();
|
||||
VideoJump();
|
||||
}
|
||||
|
||||
void OnSliderValueChange(float sliderValue)
|
||||
{
|
||||
UpdateVideoTimeText();
|
||||
}
|
||||
|
||||
IEnumerator HideSliderAfterSeconds(float duration = 1f)
|
||||
{
|
||||
yield return new WaitForSeconds(duration);
|
||||
m_Slider.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void OnDrag()
|
||||
{
|
||||
m_IsDragging = true;
|
||||
m_VideoJumpPending = true;
|
||||
}
|
||||
|
||||
void VideoJump()
|
||||
{
|
||||
m_VideoJumpPending = true;
|
||||
var frame = m_VideoPlayer.frameCount * m_Slider.value;
|
||||
m_LastFrameBeforeScrub = m_VideoPlayer.frame;
|
||||
m_VideoPlayer.frame = (long)frame;
|
||||
}
|
||||
|
||||
public void PlayOrPauseVideo()
|
||||
{
|
||||
if (m_VideoIsPlaying)
|
||||
{
|
||||
VideoStop();
|
||||
}
|
||||
else
|
||||
{
|
||||
VideoPlay();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateVideoTimeText()
|
||||
{
|
||||
if (m_VideoPlayer != null && m_VideoTimeText != null)
|
||||
{
|
||||
var currentTimeTimeSpan = TimeSpan.FromSeconds(m_VideoPlayer.time);
|
||||
var totalTimeTimeSpan = TimeSpan.FromSeconds(m_VideoPlayer.length);
|
||||
var currentTimeString = string.Format("{0:D2}:{1:D2}",
|
||||
currentTimeTimeSpan.Minutes,
|
||||
currentTimeTimeSpan.Seconds
|
||||
);
|
||||
|
||||
var totalTimeString = string.Format("{0:D2}:{1:D2}",
|
||||
totalTimeTimeSpan.Minutes,
|
||||
totalTimeTimeSpan.Seconds
|
||||
);
|
||||
m_VideoTimeText.SetText(currentTimeString + " / " + totalTimeString);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoStop()
|
||||
{
|
||||
m_VideoIsPlaying = false;
|
||||
m_VideoPlayer.Pause();
|
||||
m_ButtonPlayOrPauseIcon.sprite = m_IconPlay;
|
||||
m_ButtonPlayOrPause.SetActive(true);
|
||||
}
|
||||
|
||||
void VideoPlay()
|
||||
{
|
||||
m_VideoIsPlaying = true;
|
||||
m_VideoPlayer.Play();
|
||||
m_ButtonPlayOrPauseIcon.sprite = m_IconPause;
|
||||
m_ButtonPlayOrPause.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ca75f292f7449044807f6ba9e6f954c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
441
Assets/VRTemplateAssets/Scripts/XRKnob.cs
Normal file
441
Assets/VRTemplateAssets/Scripts/XRKnob.cs
Normal file
@@ -0,0 +1,441 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactables;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactors;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// An interactable knob that follows the rotation of the interactor
|
||||
/// </summary>
|
||||
public class XRKnob : XRBaseInteractable
|
||||
{
|
||||
const float k_ModeSwitchDeadZone = 0.1f; // Prevents rapid switching between the different rotation tracking modes
|
||||
|
||||
/// <summary>
|
||||
/// Helper class used to track rotations that can go beyond 180 degrees while minimizing accumulation error
|
||||
/// </summary>
|
||||
struct TrackedRotation
|
||||
{
|
||||
/// <summary>
|
||||
/// The anchor rotation we calculate an offset from
|
||||
/// </summary>
|
||||
float m_BaseAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The target rotate we calculate the offset to
|
||||
/// </summary>
|
||||
float m_CurrentOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Any previous offsets we've added in
|
||||
/// </summary>
|
||||
float m_AccumulatedAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The total rotation that occurred from when this rotation started being tracked
|
||||
/// </summary>
|
||||
public float totalOffset => m_AccumulatedAngle + m_CurrentOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Resets the tracked rotation so that total offset returns 0
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
m_BaseAngle = 0.0f;
|
||||
m_CurrentOffset = 0.0f;
|
||||
m_AccumulatedAngle = 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a new anchor rotation while maintaining any previously accumulated offset
|
||||
/// </summary>
|
||||
/// <param name="direction">The XZ vector used to calculate a rotation angle</param>
|
||||
public void SetBaseFromVector(Vector3 direction)
|
||||
{
|
||||
// Update any accumulated angle
|
||||
m_AccumulatedAngle += m_CurrentOffset;
|
||||
|
||||
// Now set a new base angle
|
||||
m_BaseAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg;
|
||||
m_CurrentOffset = 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates current offset and base angle based on target direction.
|
||||
/// </summary>
|
||||
/// <param name="direction">The XZ vector used to calculate a rotation angle</param>
|
||||
public void SetTargetFromVector(Vector3 direction)
|
||||
{
|
||||
// Set the target angle
|
||||
var targetAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg;
|
||||
|
||||
// Return the offset
|
||||
m_CurrentOffset = ShortestAngleDistance(m_BaseAngle, targetAngle, 360.0f);
|
||||
|
||||
// If the offset is greater than 90 degrees, we update the base so we can rotate beyond 180 degrees
|
||||
if (Mathf.Abs(m_CurrentOffset) > 90.0f)
|
||||
{
|
||||
m_BaseAngle = targetAngle;
|
||||
m_AccumulatedAngle += m_CurrentOffset;
|
||||
m_CurrentOffset = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[Tooltip("Event called when the value of the knob is changed")]
|
||||
public class ValueChangeEvent : UnityEvent<float> { }
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The object that is visually grabbed and manipulated")]
|
||||
Transform m_Handle = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The value of the knob")]
|
||||
[Range(0.0f, 1.0f)]
|
||||
float m_Value = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether this knob's rotation should be clamped by the angle limits")]
|
||||
bool m_ClampedMotion = true;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Rotation of the knob at value '1'")]
|
||||
float m_MaxAngle = 90.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Rotation of the knob at value '0'")]
|
||||
float m_MinAngle = -90.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Angle increments to support, if greater than '0'")]
|
||||
float m_AngleIncrement = 0.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The position of the interactor controls rotation when outside this radius")]
|
||||
float m_PositionTrackedRadius = 0.1f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("How much controller rotation")]
|
||||
float m_TwistSensitivity = 1.5f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the knob is rotated")]
|
||||
ValueChangeEvent m_OnValueChange = new ValueChangeEvent();
|
||||
|
||||
IXRSelectInteractor m_Interactor;
|
||||
|
||||
bool m_PositionDriven = false;
|
||||
bool m_UpVectorDriven = false;
|
||||
|
||||
TrackedRotation m_PositionAngles = new TrackedRotation();
|
||||
TrackedRotation m_UpVectorAngles = new TrackedRotation();
|
||||
TrackedRotation m_ForwardVectorAngles = new TrackedRotation();
|
||||
|
||||
float m_BaseKnobRotation = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The object that is visually grabbed and manipulated
|
||||
/// </summary>
|
||||
public Transform handle
|
||||
{
|
||||
get => m_Handle;
|
||||
set => m_Handle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of the knob
|
||||
/// </summary>
|
||||
public float value
|
||||
{
|
||||
get => m_Value;
|
||||
set
|
||||
{
|
||||
SetValue(value);
|
||||
SetKnobRotation(ValueToRotation());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this knob's rotation should be clamped by the angle limits
|
||||
/// </summary>
|
||||
public bool clampedMotion
|
||||
{
|
||||
get => m_ClampedMotion;
|
||||
set => m_ClampedMotion = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation of the knob at value '1'
|
||||
/// </summary>
|
||||
public float maxAngle
|
||||
{
|
||||
get => m_MaxAngle;
|
||||
set => m_MaxAngle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation of the knob at value '0'
|
||||
/// </summary>
|
||||
public float minAngle
|
||||
{
|
||||
get => m_MinAngle;
|
||||
set => m_MinAngle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The position of the interactor controls rotation when outside this radius
|
||||
/// </summary>
|
||||
public float positionTrackedRadius
|
||||
{
|
||||
get => m_PositionTrackedRadius;
|
||||
set => m_PositionTrackedRadius = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the knob is rotated
|
||||
/// </summary>
|
||||
public ValueChangeEvent onValueChange => m_OnValueChange;
|
||||
|
||||
void Start()
|
||||
{
|
||||
SetValue(m_Value);
|
||||
SetKnobRotation(ValueToRotation());
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
selectEntered.AddListener(StartGrab);
|
||||
selectExited.AddListener(EndGrab);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
selectEntered.RemoveListener(StartGrab);
|
||||
selectExited.RemoveListener(EndGrab);
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
void StartGrab(SelectEnterEventArgs args)
|
||||
{
|
||||
m_Interactor = args.interactorObject;
|
||||
|
||||
m_PositionAngles.Reset();
|
||||
m_UpVectorAngles.Reset();
|
||||
m_ForwardVectorAngles.Reset();
|
||||
|
||||
UpdateBaseKnobRotation();
|
||||
UpdateRotation(true);
|
||||
}
|
||||
|
||||
void EndGrab(SelectExitEventArgs args)
|
||||
{
|
||||
m_Interactor = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
||||
{
|
||||
base.ProcessInteractable(updatePhase);
|
||||
|
||||
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
|
||||
{
|
||||
if (isSelected)
|
||||
{
|
||||
UpdateRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Transform GetAttachTransform(IXRInteractor interactor)
|
||||
{
|
||||
return m_Handle;
|
||||
}
|
||||
|
||||
void UpdateRotation(bool freshCheck = false)
|
||||
{
|
||||
// Are we in position offset or direction rotation mode?
|
||||
var interactorTransform = m_Interactor.GetAttachTransform(this);
|
||||
|
||||
// We cache the three potential sources of rotation - the position offset, the forward vector of the controller, and up vector of the controller
|
||||
// We store any data used for determining which rotation to use, then flatten the vectors to the local xz plane
|
||||
var localOffset = transform.InverseTransformVector(interactorTransform.position - m_Handle.position);
|
||||
localOffset.y = 0.0f;
|
||||
var radiusOffset = transform.TransformVector(localOffset).magnitude;
|
||||
localOffset.Normalize();
|
||||
|
||||
var localForward = transform.InverseTransformDirection(interactorTransform.forward);
|
||||
var localY = Math.Abs(localForward.y);
|
||||
localForward.y = 0.0f;
|
||||
localForward.Normalize();
|
||||
|
||||
var localUp = transform.InverseTransformDirection(interactorTransform.up);
|
||||
localUp.y = 0.0f;
|
||||
localUp.Normalize();
|
||||
|
||||
if (m_PositionDriven && !freshCheck)
|
||||
radiusOffset *= (1.0f + k_ModeSwitchDeadZone);
|
||||
|
||||
// Determine when a certain source of rotation won't contribute - in that case we bake in the offset it has applied
|
||||
// and set a new anchor when they can contribute again
|
||||
if (radiusOffset >= m_PositionTrackedRadius)
|
||||
{
|
||||
if (!m_PositionDriven || freshCheck)
|
||||
{
|
||||
m_PositionAngles.SetBaseFromVector(localOffset);
|
||||
m_PositionDriven = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_PositionDriven = false;
|
||||
|
||||
// If it's not a fresh check, then we weight the local Y up or down to keep it from flickering back and forth at boundaries
|
||||
if (!freshCheck)
|
||||
{
|
||||
if (!m_UpVectorDriven)
|
||||
localY *= (1.0f - (k_ModeSwitchDeadZone * 0.5f));
|
||||
else
|
||||
localY *= (1.0f + (k_ModeSwitchDeadZone * 0.5f));
|
||||
}
|
||||
|
||||
if (localY > 0.707f)
|
||||
{
|
||||
if (!m_UpVectorDriven || freshCheck)
|
||||
{
|
||||
m_UpVectorAngles.SetBaseFromVector(localUp);
|
||||
m_UpVectorDriven = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_UpVectorDriven || freshCheck)
|
||||
{
|
||||
m_ForwardVectorAngles.SetBaseFromVector(localForward);
|
||||
m_UpVectorDriven = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get angle from position
|
||||
if (m_PositionDriven)
|
||||
m_PositionAngles.SetTargetFromVector(localOffset);
|
||||
|
||||
if (m_UpVectorDriven)
|
||||
m_UpVectorAngles.SetTargetFromVector(localUp);
|
||||
else
|
||||
m_ForwardVectorAngles.SetTargetFromVector(localForward);
|
||||
|
||||
// Apply offset to base knob rotation to get new knob rotation
|
||||
var knobRotation = m_BaseKnobRotation - ((m_UpVectorAngles.totalOffset + m_ForwardVectorAngles.totalOffset) * m_TwistSensitivity) - m_PositionAngles.totalOffset;
|
||||
|
||||
// Clamp to range
|
||||
if (m_ClampedMotion)
|
||||
knobRotation = Mathf.Clamp(knobRotation, m_MinAngle, m_MaxAngle);
|
||||
|
||||
SetKnobRotation(knobRotation);
|
||||
|
||||
// Reverse to get value
|
||||
var knobValue = (knobRotation - m_MinAngle) / (m_MaxAngle - m_MinAngle);
|
||||
SetValue(knobValue);
|
||||
}
|
||||
|
||||
void SetKnobRotation(float angle)
|
||||
{
|
||||
if (m_AngleIncrement > 0)
|
||||
{
|
||||
var normalizeAngle = angle - m_MinAngle;
|
||||
angle = (Mathf.Round(normalizeAngle / m_AngleIncrement) * m_AngleIncrement) + m_MinAngle;
|
||||
}
|
||||
|
||||
if (m_Handle != null)
|
||||
m_Handle.localEulerAngles = new Vector3(0.0f, angle, 0.0f);
|
||||
}
|
||||
|
||||
void SetValue(float newValue)
|
||||
{
|
||||
if (m_ClampedMotion)
|
||||
newValue = Mathf.Clamp01(newValue);
|
||||
|
||||
if (m_AngleIncrement > 0)
|
||||
{
|
||||
var angleRange = m_MaxAngle - m_MinAngle;
|
||||
var angle = Mathf.Lerp(0.0f, angleRange, newValue);
|
||||
angle = Mathf.Round(angle / m_AngleIncrement) * m_AngleIncrement;
|
||||
newValue = Mathf.InverseLerp(0.0f, angleRange, angle);
|
||||
}
|
||||
|
||||
m_Value = newValue;
|
||||
m_OnValueChange.Invoke(m_Value);
|
||||
}
|
||||
|
||||
float ValueToRotation()
|
||||
{
|
||||
return m_ClampedMotion ? Mathf.Lerp(m_MinAngle, m_MaxAngle, m_Value) : Mathf.LerpUnclamped(m_MinAngle, m_MaxAngle, m_Value);
|
||||
}
|
||||
|
||||
void UpdateBaseKnobRotation()
|
||||
{
|
||||
m_BaseKnobRotation = Mathf.LerpUnclamped(m_MinAngle, m_MaxAngle, m_Value);
|
||||
}
|
||||
|
||||
static float ShortestAngleDistance(float start, float end, float max)
|
||||
{
|
||||
var angleDelta = end - start;
|
||||
var angleSign = Mathf.Sign(angleDelta);
|
||||
|
||||
angleDelta = Math.Abs(angleDelta) % max;
|
||||
if (angleDelta > (max * 0.5f))
|
||||
angleDelta = -(max - angleDelta);
|
||||
|
||||
return angleDelta * angleSign;
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
const int k_CircleSegments = 16;
|
||||
const float k_SegmentRatio = 1.0f / k_CircleSegments;
|
||||
|
||||
// Nothing to do if position radius is too small
|
||||
if (m_PositionTrackedRadius <= Mathf.Epsilon)
|
||||
return;
|
||||
|
||||
var knobTransform = transform;
|
||||
|
||||
// Draw a circle from the handle point at size of position tracked radius
|
||||
var circleCenter = knobTransform.position;
|
||||
|
||||
if (m_Handle != null)
|
||||
circleCenter = m_Handle.position;
|
||||
|
||||
var circleX = knobTransform.right;
|
||||
var circleY = knobTransform.forward;
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
var segmentCounter = 0;
|
||||
while (segmentCounter < k_CircleSegments)
|
||||
{
|
||||
var startAngle = segmentCounter * k_SegmentRatio * 2.0f * Mathf.PI;
|
||||
segmentCounter++;
|
||||
var endAngle = segmentCounter * k_SegmentRatio * 2.0f * Mathf.PI;
|
||||
|
||||
Gizmos.DrawLine(circleCenter + (Mathf.Cos(startAngle) * circleX + Mathf.Sin(startAngle) * circleY) * m_PositionTrackedRadius,
|
||||
circleCenter + (Mathf.Cos(endAngle) * circleX + Mathf.Sin(endAngle) * circleY) * m_PositionTrackedRadius);
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (m_ClampedMotion)
|
||||
m_Value = Mathf.Clamp01(m_Value);
|
||||
|
||||
if (m_MinAngle > m_MaxAngle)
|
||||
m_MinAngle = m_MaxAngle;
|
||||
|
||||
SetKnobRotation(ValueToRotation());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/VRTemplateAssets/Scripts/XRKnob.cs.meta
Normal file
11
Assets/VRTemplateAssets/Scripts/XRKnob.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e888dc0064d84d041818fa202b52d6be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
246
Assets/VRTemplateAssets/Scripts/XRPokeFollowAffordanceFill.cs
Normal file
246
Assets/VRTemplateAssets/Scripts/XRPokeFollowAffordanceFill.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using Unity.Mathematics;
|
||||
using Unity.XR.CoreUtils.Bindings;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.State;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Filtering;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
|
||||
|
||||
namespace Unity.VRTemplate
|
||||
{
|
||||
/// <summary>
|
||||
/// Follow animation affordance for <see cref="IPokeStateDataProvider"/>, such as <see cref="XRPokeFilter"/>.
|
||||
/// Used to animate a pressed transform, such as a button to follow the poke position.
|
||||
/// </summary>
|
||||
[AddComponentMenu("XR/XR Poke Follow Affordance Fill", 22)]
|
||||
public class XRPokeFollowAffordanceFill : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("Transform that will move in the poke direction when this or a parent GameObject is poked." +
|
||||
"\nNote: Should be a direct child GameObject.")]
|
||||
Transform m_PokeFollowTransform;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Transform that will scale the mask when this interactable is poked.")]
|
||||
RectTransform m_PokeFill;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The max width size for the poke fill image when pressed")]
|
||||
float m_PokeFillMaxSizeX;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The max height size for the poke fill image when pressed")]
|
||||
float m_PokeFillMaxSizeY;
|
||||
|
||||
/// <summary>
|
||||
/// Transform that will animate along the axis of interaction when this interactable is poked.
|
||||
/// Note: Must be a direct child GameObject as it moves in local space relative to the poke target's transform.
|
||||
/// </summary>
|
||||
public Transform pokeFollowTransform
|
||||
{
|
||||
get => m_PokeFollowTransform;
|
||||
set => m_PokeFollowTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Range(0f, 20f)]
|
||||
[Tooltip("Multiplies transform position interpolation as a factor of Time.deltaTime. If 0, no smoothing will be applied.")]
|
||||
float m_SmoothingSpeed = 8f;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies transform position interpolation as a factor of <see cref="Time.deltaTime"/>. If <c>0</c>, no smoothing will be applied.
|
||||
/// </summary>
|
||||
public float smoothingSpeed
|
||||
{
|
||||
get => m_SmoothingSpeed;
|
||||
set => m_SmoothingSpeed = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("When this component is no longer the target of the poke, the Poke Follow Transform returns to the original position.")]
|
||||
bool m_ReturnToInitialPosition = true;
|
||||
|
||||
/// <summary>
|
||||
/// When this component is no longer the target of the poke, the <see cref="pokeFollowTransform"/> returns to the original position.
|
||||
/// </summary>
|
||||
public bool returnToInitialPosition
|
||||
{
|
||||
get => m_ReturnToInitialPosition;
|
||||
set => m_ReturnToInitialPosition = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to apply the follow animation if the target of the poke is a child of this transform. " +
|
||||
"This is useful for UI objects that may have child graphics.")]
|
||||
bool m_ApplyIfChildIsTarget = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to apply the follow animation if the target of the poke is a child of this transform.
|
||||
/// This is useful for UI objects that may have child graphics.
|
||||
/// </summary>
|
||||
public bool applyIfChildIsTarget
|
||||
{
|
||||
get => m_ApplyIfChildIsTarget;
|
||||
set => m_ApplyIfChildIsTarget = value;
|
||||
}
|
||||
|
||||
[Header("Distance Clamping")]
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to keep the Poke Follow Transform from moving past a minimum distance from the poke target.")]
|
||||
bool m_ClampToMinDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to keep the <see cref="pokeFollowTransform"/> from moving past <see cref="minDistance"/> from the poke target.
|
||||
/// </summary>
|
||||
public bool clampToMinDistance
|
||||
{
|
||||
get => m_ClampToMinDistance;
|
||||
set => m_ClampToMinDistance = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The minimum distance from this transform that the Poke Follow Transform can move.")]
|
||||
float m_MinDistance;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum distance from this transform that the <see cref="pokeFollowTransform"/> can move when
|
||||
/// <see cref="clampToMinDistance"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public float minDistance
|
||||
{
|
||||
get => m_MinDistance;
|
||||
set => m_MinDistance = value;
|
||||
}
|
||||
[Space]
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to keep the Poke Follow Transform from moving past a maximum distance from the poke target.")]
|
||||
bool m_ClampToMaxDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to keep the <see cref="pokeFollowTransform"/> from moving past <see cref="maxDistance"/> from the poke target.
|
||||
/// </summary>
|
||||
public bool clampToMaxDistance
|
||||
{
|
||||
get => m_ClampToMaxDistance;
|
||||
set => m_ClampToMaxDistance = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[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.")]
|
||||
float m_MaxDistance;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum distance from this transform that the <see cref="pokeFollowTransform"/> can move when
|
||||
/// <see cref="clampToMaxDistance"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public float maxDistance
|
||||
{
|
||||
get => m_MaxDistance;
|
||||
set => m_MaxDistance = value;
|
||||
}
|
||||
|
||||
IPokeStateDataProvider m_PokeDataProvider;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
readonly Vector3TweenableVariable m_TransformTweenableVariable = new Vector3TweenableVariable();
|
||||
readonly FloatTweenableVariable m_PokeStrengthTweenableVariable = new FloatTweenableVariable();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
readonly BindingsGroup m_BindingsGroup = new BindingsGroup();
|
||||
Vector3 m_InitialPosition;
|
||||
bool m_IsFirstFrame;
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Awake()
|
||||
{
|
||||
m_PokeDataProvider = GetComponentInParent<IPokeStateDataProvider>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Start()
|
||||
{
|
||||
if (m_PokeFollowTransform != null)
|
||||
{
|
||||
m_InitialPosition = m_PokeFollowTransform.localPosition;
|
||||
m_MaxDistance = m_MaxDistance > 0f ? Mathf.Min(m_InitialPosition.magnitude, m_MaxDistance) : m_InitialPosition.magnitude;
|
||||
m_BindingsGroup.AddBinding(m_TransformTweenableVariable.Subscribe(OnTransformTweenableVariableUpdated));
|
||||
m_BindingsGroup.AddBinding(m_PokeStrengthTweenableVariable.Subscribe(OnPokeStrengthChanged));
|
||||
m_BindingsGroup.AddBinding(m_PokeDataProvider.pokeStateData.SubscribeAndUpdate(OnPokeStateDataUpdated));
|
||||
}
|
||||
else
|
||||
{
|
||||
enabled = false;
|
||||
Debug.LogWarning($"Missing Poke Follow Transform assignment on {this}. Disabling component.", this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void OnDestroy()
|
||||
{
|
||||
m_BindingsGroup.Clear();
|
||||
m_TransformTweenableVariable?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void LateUpdate()
|
||||
{
|
||||
if (m_IsFirstFrame)
|
||||
{
|
||||
m_TransformTweenableVariable.HandleTween(1f);
|
||||
m_PokeStrengthTweenableVariable.target = 0f;
|
||||
m_PokeStrengthTweenableVariable.HandleTween(1f);
|
||||
m_IsFirstFrame = false;
|
||||
return;
|
||||
}
|
||||
|
||||
float tweenAmt = m_SmoothingSpeed > 0f ? Time.deltaTime * m_SmoothingSpeed : 1f;
|
||||
m_TransformTweenableVariable.HandleTween(tweenAmt);
|
||||
m_PokeStrengthTweenableVariable.HandleTween(tweenAmt);
|
||||
}
|
||||
|
||||
void OnTransformTweenableVariableUpdated(float3 position)
|
||||
{
|
||||
m_PokeFollowTransform.localPosition = position;
|
||||
}
|
||||
|
||||
void OnPokeStrengthChanged(float newStrength)
|
||||
{
|
||||
var newX = m_PokeFillMaxSizeX * newStrength;
|
||||
var newY = m_PokeFillMaxSizeY * newStrength;
|
||||
m_PokeFill.sizeDelta = new Vector2(newX, newY);
|
||||
}
|
||||
|
||||
void OnPokeStateDataUpdated(PokeStateData data)
|
||||
{
|
||||
var pokeTarget = data.target;
|
||||
var applyFollow = m_ApplyIfChildIsTarget
|
||||
? pokeTarget != null && pokeTarget.IsChildOf(transform)
|
||||
: pokeTarget == transform;
|
||||
|
||||
if (applyFollow)
|
||||
{
|
||||
var targetPosition = pokeTarget.InverseTransformPoint(data.axisAlignedPokeInteractionPoint);
|
||||
|
||||
if (m_ClampToMinDistance && targetPosition.sqrMagnitude < m_MinDistance * m_MinDistance)
|
||||
targetPosition = Vector3.ClampMagnitude(targetPosition, m_MinDistance);
|
||||
|
||||
if (m_ClampToMaxDistance && targetPosition.sqrMagnitude > m_MaxDistance * m_MaxDistance)
|
||||
targetPosition = Vector3.ClampMagnitude(targetPosition, m_MaxDistance);
|
||||
|
||||
m_TransformTweenableVariable.target = targetPosition;
|
||||
m_PokeStrengthTweenableVariable.target = Mathf.Clamp01(data.interactionStrength);
|
||||
}
|
||||
else if (m_ReturnToInitialPosition)
|
||||
{
|
||||
m_TransformTweenableVariable.target = m_InitialPosition;
|
||||
m_PokeStrengthTweenableVariable.target = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0908100b30fe0ab4191734ae3261431f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user