Initial commit

This commit is contained in:
Thorbjoern
2025-05-26 00:46:28 +02:00
commit e5bca03433
3896 changed files with 1434297 additions and 0 deletions
@@ -0,0 +1,109 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
namespace XRMultiplayer
{
/// <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;
Vector3 m_InitialArrowScale;
void Start()
{
if (m_FadeAnimation != null)
{
var fadeAnim = m_FadeAnimation;
var clipName = m_FadeAnimation.clip.name;
fadeAnim[clipName].normalizedTime = 1f;
}
m_ArrowHeight = new Vector3TweenableVariable
{
animationCurve = m_AnimationCurve
};
m_InitialArrowScale = m_Arrow.localScale;
}
#pragma warning restore CS0618 // Type or member is obsolete
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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 31b457110c38f45909b40fd3abb1af16
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,36 @@
using UnityEngine;
namespace XRMultiplayer
{
public class Billboard : MonoBehaviour
{
[SerializeField] bool m_WorldUp;
[SerializeField] bool m_FlipForward;
protected Camera m_Camera;
private void Awake()
{
m_Camera = Camera.main;
}
private void Update()
{
Quaternion lookRot = Quaternion.LookRotation(m_Camera.transform.position - transform.position);
if (m_WorldUp)
{
Vector3 offset = lookRot.eulerAngles;
offset.x = 0;
offset.z = 0;
if (m_FlipForward)
offset.y += 180;
lookRot = Quaternion.Euler(offset);
}
transform.rotation = lookRot;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8f4f9a0c05e79af428c8c2ac20edde54
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:
@@ -0,0 +1,84 @@
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Teleportation;
namespace XRMultiplayer
{
public class CharacterResetter : MonoBehaviour
{
[SerializeField] Vector2 m_MinMaxHeight = new Vector2(-2.5f, 25.0f);
[SerializeField] float m_ResetDistance = 75.0f;
[SerializeField] Vector3 offlinePosition = new Vector3(0, .5f, -12.0f);
[SerializeField] Vector3 onlinePosition = new Vector3(0, .15f, 0);
TeleportationProvider m_TeleportationProvider;
Vector3 m_ResetPosition;
private void Start()
{
XRINetworkGameManager.Connected.Subscribe(UpdateResetPosition);
m_TeleportationProvider = GetComponentInChildren<TeleportationProvider>();
m_ResetPosition = offlinePosition;
ResetPlayer();
}
void UpdateResetPosition(bool connected)
{
if (connected)
{
m_ResetPosition = onlinePosition;
}
else
{
m_ResetPosition = offlinePosition;
ResetPlayer();
}
}
// Update is called once per frame
void Update()
{
if (transform.position.y < m_MinMaxHeight.x)
{
ResetPlayer();
}
else if (transform.position.y > m_MinMaxHeight.y)
{
ResetPlayer();
}
if (Mathf.Abs(transform.position.x) > m_ResetDistance || Mathf.Abs(transform.position.z) > m_ResetDistance)
{
ResetPlayer();
}
}
public void ResetPlayer()
{
ResetPlayer(m_ResetPosition);
}
void ResetPlayer(Vector3 destination)
{
TeleportRequest teleportRequest = new()
{
destinationPosition = destination,
destinationRotation = Quaternion.identity
};
if (!m_TeleportationProvider.QueueTeleportRequest(teleportRequest))
{
Utils.LogWarning("Failed to queue teleport request");
}
}
[ContextMenu("Set Player To Online Position")]
void SetPlayerToOnlinePosition()
{
ResetPlayer(onlinePosition);
}
[ContextMenu("Set Player To Offline Position")]
void SetPlayerToOfflinePosition()
{
ResetPlayer(offlinePosition);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c954b6e8024c0b248a278eab8c6828ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cd5b2479bf1334144a93423ea317ae20
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 72fb4b8d89bc26347a49177acaa93913
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,129 @@
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("Whether Gaze Callout is used.")]
bool m_UseGazeCallout = true;
[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_UseGazeCallout)
{
DisableCallout();
return;
}
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()
{
if (!m_UseGazeCallout)
{
DisableCallout();
return;
}
m_Gazing = true;
if (m_StartCo != null)
StopCoroutine(m_StartCo);
if (m_EndCo != null)
StopCoroutine(m_EndCo);
m_StartCo = StartCoroutine(StartDelay());
}
public void GazeHoverEnd()
{
if (!m_UseGazeCallout)
{
DisableCallout();
return;
}
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);
}
void DisableCallout()
{
if (m_StartCo != null)
StopCoroutine(m_StartCo);
if (m_EndCo != null)
StopCoroutine(m_EndCo);
TurnOffStuff();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16809ed3baa3d2341b75ec4c0aa874d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,74 @@
using UnityEngine;
namespace Unity.VRTemplate
{
/// <summary>
/// Makes this object face a target smoothly and along specific axes
/// </summary>
public class TurnToFace : MonoBehaviour
{
#pragma warning disable 649
public Transform faceTarget
{
get => m_FaceTarget;
set => m_FaceTarget = value;
}
[SerializeField]
[Tooltip("Target to face towards. If not set, this will default to the main camera")]
Transform m_FaceTarget;
[SerializeField]
[Tooltip("Speed to turn")]
float m_TurnToFaceSpeed = 5f;
[SerializeField]
[Tooltip("Local rotation offset")]
Vector3 m_RotationOffset = Vector3.zero;
[SerializeField]
[Tooltip("If enabled, ignore the x axis when rotating")]
bool m_IgnoreX;
[SerializeField]
[Tooltip("If enabled, ignore the y axis when rotating")]
bool m_IgnoreY;
[SerializeField]
[Tooltip("If enabled, ignore the z axis when rotating")]
bool m_IgnoreZ;
#pragma warning restore 649
void Awake()
{
// Default to main camera
if (m_FaceTarget == null)
if (Camera.main != null)
m_FaceTarget = Camera.main.transform;
}
void Update()
{
if (m_FaceTarget != null)
{
var facePosition = m_FaceTarget.position;
var forward = facePosition - transform.position;
var targetRotation = forward.sqrMagnitude > float.Epsilon ? Quaternion.LookRotation(forward, Vector3.up) : Quaternion.identity;
targetRotation *= Quaternion.Euler(m_RotationOffset);
if (m_IgnoreX || m_IgnoreY || m_IgnoreZ)
{
var targetEuler = targetRotation.eulerAngles;
var currentEuler = transform.rotation.eulerAngles;
targetRotation = Quaternion.Euler
(
m_IgnoreX ? currentEuler.x : targetEuler.x,
m_IgnoreY ? currentEuler.y : targetEuler.y,
m_IgnoreZ ? currentEuler.z : targetEuler.z
);
}
var ease = 1f - Mathf.Exp(-m_TurnToFaceSpeed * Time.unscaledDeltaTime);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, ease);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 35c58493ec2a8cb43ba320ce1af1adc6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
namespace XRMultiplayer
{
public class DelayedUnityEvent : MonoBehaviour
{
[SerializeField] float m_TimeToEnable = 4.0f;
[SerializeField] UnityEvent m_UnityEvent;
Coroutine m_EnablingRoutine;
private void OnEnable()
{
if (m_EnablingRoutine != null) StopCoroutine(m_EnablingRoutine);
m_EnablingRoutine = StartCoroutine(EnableAfterTime());
}
private void OnDisable()
{
if (m_EnablingRoutine != null) StopCoroutine(m_EnablingRoutine);
}
IEnumerator EnableAfterTime()
{
yield return new WaitForSeconds(m_TimeToEnable);
m_UnityEvent.Invoke();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d283a9893352d6e49b6b6db49148e069
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,17 @@
using UnityEngine;
namespace XRMultiplayer
{
public class GameObjectToggle : MonoBehaviour
{
[SerializeField] GameObject[] objectsToToggle;
public void ToggleObjects()
{
foreach (var obj in objectsToToggle)
{
obj.SetActive(!obj.activeSelf);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a9a7fb087d961449a55b0c20d051ffb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 59b8da3d55e3cb74c91ad743d542a9a2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
namespace UnityEngine.XR.Content.Interaction
{
/// <summary>
/// Interface to implement for objects that hold a set of <c>Key</c>s
/// </summary>
public interface IKeychain
{
/// <summary>
/// This callback is used to check if this keychain has a specific <c>Key</c>
/// <see cref="Lock"/>
/// </summary>
/// <param name="key">the key to be checked</param>
/// <returns>True if this keychain has the supplied key; false otherwise</returns>
bool Contains(Key key);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b3273498567e6fc4b944c2985269269d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
namespace UnityEngine.XR.Content.Interaction
{
/// <summary>
/// An asset that represents a key. Used to check if an object can perform some action
/// (<see cref="XRLockSocketInteractor"/> and <see cref="Keychain"/>)
/// </summary>
[CreateAssetMenuAttribute(menuName = "XR/Key Lock System/Key")]
public class Key : ScriptableObject
{ }
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4e629f4cfca91134e86ae027aaa5d4eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,73 @@
using System.Collections.Generic;
namespace UnityEngine.XR.Content.Interaction
{
/// <summary>
/// A generic Keychain component that holds the <see cref="Key"/>s to open a <see cref="Lock"/>.
/// Attach a Keychain component to an Interactable and assign to it the same Keys of an <see cref="XRLockSocketInteractor"/>
/// or an <see cref="XRLockGridSocketInteractor"/> to open (or interact with) them.
/// </summary>
[DisallowMultipleComponent]
public class Keychain : MonoBehaviour, IKeychain
{
[SerializeField]
[Tooltip("The keys on this keychain" +
"Create new keys by selecting \"Assets/Create/XR/Key Lock System/Key\"")]
List<Key> m_Keys;
HashSet<int> m_KeysHashSet = new HashSet<int>();
void Awake()
{
RepopulateHashSet();
}
void OnValidate()
{
// A key was added through the inspector while the game was running?
if (Application.isPlaying && m_Keys.Count != m_KeysHashSet.Count)
RepopulateHashSet();
}
void RepopulateHashSet()
{
m_KeysHashSet.Clear();
foreach (var key in m_Keys)
{
if (key != null)
m_KeysHashSet.Add(key.GetInstanceID());
}
}
/// <summary>
/// Adds the supplied key to this keychain
/// </summary>
/// <param name="key">The key to be added to the keychain</param>
public void AddKey(Key key)
{
if (key == null || Contains(key))
return;
m_Keys.Add(key);
m_KeysHashSet.Add(key.GetInstanceID());
}
/// <summary>
/// Adds the supplied key from this keychain
/// </summary>
/// <param name="key">The key to be removed from the keychain</param>
public void RemoveKey(Key key)
{
m_Keys.Remove(key);
if (key != null)
m_KeysHashSet.Remove(key.GetInstanceID());
}
/// <inheritdoc />
public bool Contains(Key key)
{
return key != null && m_KeysHashSet.Contains(key.GetInstanceID());
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 505599121cd7c2d4d87a596056b0142b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.XR.Content.Interaction
{
/// <summary>
/// Use this object as a generic way to validate if an object can perform some action.
/// The check is done in the <see cref="CanUnlock"/> method.
/// This class is used in combination with a <see cref="Keychain"/> component.
/// </summary>
/// <seealso cref="XRLockSocketInteractor"/>
/// <seealso cref="XRLockGridSocketInteractor"/>
[Serializable]
public class Lock
{
[SerializeField]
[Tooltip("The required keys to unlock this lock" +
"Create new keys by selecting \"Assets/Create/XR/Key Lock System/Key\"")]
List<Key> m_RequiredKeys;
/// <summary>
/// Returns the required keys to unlock this lock.
/// </summary>
public List<Key> requiredKeys => m_RequiredKeys;
/// <summary>
/// Checks if the supplied keychain has all the required keys to open this lock.
/// </summary>
/// <param name="keychain">The keychain to be checked.</param>
/// <returns>True if the supplied keychain has all the required keys; false otherwise.</returns>
public bool CanUnlock(IKeychain keychain)
{
if (keychain == null)
return m_RequiredKeys.Count == 0;
foreach (var requiredKey in m_RequiredKeys)
{
if (requiredKey == null)
continue;
if (!keychain.Contains(requiredKey))
return false;
}
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d479cceb8cf26842888dcaf56f46717
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,150 @@
using System.Collections;
using UnityEngine;
namespace Unity.VRTemplate
{
/// <summary>
/// Makes the object this is attached to follow a target with a slight delay
/// </summary>
public class LazyFollow : MonoBehaviour
{
#pragma warning disable 649
[SerializeField]
[Tooltip("The object being followed.")]
Transform m_Target;
#pragma warning restore 649
[SerializeField]
[Tooltip("Whether to always follow or only when in-view.")]
bool m_FOV = false;
[SerializeField]
[Tooltip("Whether rotation is locked to the z-axis for can move in any direction.")]
bool m_ZRot = true;
[SerializeField]
[Tooltip("Adjusts the follow point from the target by this amount.")]
Vector3 m_TargetOffset = Vector3.forward;
[SerializeField]
[Tooltip("Snap to target position when this component is enabled.")]
bool m_SnapOnEnable = true;
public bool followActive = true;
Vector3 m_TargetLastPos;
Camera m_Camera;
public float smoothTime = 0.3F;
private Vector3 velocity = Vector3.zero;
bool m_InFOV = false;
Vector3 targetPosition => m_Target.position + m_Target.TransformVector(m_TargetOffset);
Quaternion targetRotation
{
get
{
if (!m_ZRot)
{
var eulerAngles = m_Target.eulerAngles;
eulerAngles = new Vector3(eulerAngles.x, eulerAngles.y, 0f);
return Quaternion.Euler(eulerAngles);
}
return m_Target.rotation;
}
}
void Awake()
{
if (m_Camera == null)
m_Camera = Camera.main;
// Default to main camera
if (m_Target == null)
if (m_Camera != null)
m_Target = m_Camera.transform;
}
void Start()
{
var targetPos = targetPosition;
m_TargetLastPos = targetPos;
}
void OnEnable()
{
if (m_SnapOnEnable)
{
transform.position = targetPosition;
velocity = Vector3.zero;
}
}
void Update()
{
if (m_FOV)
{
Vector3 screenPoint = m_Camera.WorldToViewportPoint(this.gameObject.transform.position);
var inFov = screenPoint.z > 0f && screenPoint.x > 0f && screenPoint.x < 1f && screenPoint.y > 0f && screenPoint.y < 1f;
if (inFov)
return;
}
var targetPos = targetPosition;
if (m_TargetLastPos == targetPos)
return;
if (followActive)
{
transform.position = Vector3.SmoothDamp(transform.position, targetPos, ref velocity, smoothTime);
m_TargetLastPos = targetPos;
}
}
public void Summon()
{
m_InFOV = false;
if (!followActive)
StartCoroutine(OneTimeSummonPosition());
}
IEnumerator OneTimeSummonFOV()
{
while (!m_InFOV)
{
Vector3 screenPoint = m_Camera.WorldToViewportPoint(this.gameObject.transform.position);
var inFov = screenPoint.z > 0f && screenPoint.x > 0.3f && screenPoint.x < 0.7f && screenPoint.y > 0.3f && screenPoint.y < 0.7f;
if (inFov)
{
m_InFOV = true;
}
else
{
m_InFOV = false;
var targetPos = targetPosition;
if (m_TargetLastPos != targetPos)
{
transform.position = Vector3.SmoothDamp(transform.position, targetPos, ref velocity, smoothTime);
m_TargetLastPos = targetPos;
}
}
yield return null;
}
}
IEnumerator OneTimeSummonPosition()
{
while (Vector3.Distance(transform.position, targetPosition) > 0.1f)
{
var targetPos = targetPosition;
if (m_TargetLastPos != targetPos)
{
transform.position = Vector3.SmoothDamp(transform.position, targetPos, ref velocity, smoothTime);
m_TargetLastPos = targetPos;
}
yield return null;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 63be463a5616ad444a25ac2d0faf7074
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,78 @@
using UnityEngine;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace XRMultiplayer
{
/// <summary>
/// Simple Utility class that toggles on Shadow Casting for static renderers before a bake
/// and toggles off Shadow Casting upon bake completion.
/// </summary>
public class LightBakeUtility : MonoBehaviour
{
#if UNITY_EDITOR
[SerializeField, Tooltip("Renderers assigned here will enable shadows before light baking and disable shadows upon light bake completion.")]
Renderer[] m_StaticRenderers;
[SerializeField, Tooltip("Renderers assigned here will not have their shadow settings changed by this tool during the light baking process.")]
Renderer[] m_Filters;
[SerializeField, Tooltip("Transforms assigned here will gather all children Renderers and will enable and disable shadow during the light baking process.")]
Transform[] m_RendererParents;
[SerializeField] bool m_Log = false;
void OnValidate()
{
Log("Unsubsrcibing to Light Bake Events");
Lightmapping.bakeStarted -= BakeLight;
Lightmapping.bakeCompleted -= OnBakeCompleted;
Log("Subscribing to Light Bake Events");
Lightmapping.bakeStarted += BakeLight;
Lightmapping.bakeCompleted += OnBakeCompleted;
}
void BakeLight()
{
Log("Starting Light Bake");
ToggleShadowCasting(true);
}
private void OnBakeCompleted()
{
Log("Light Bake Completed");
ToggleShadowCasting(false);
}
void ToggleShadowCasting(bool toggle)
{
foreach (var renderer in m_StaticRenderers)
{
if(renderer == null || m_Filters.Contains(renderer)){ continue; }
renderer.shadowCastingMode = toggle ? UnityEngine.Rendering.ShadowCastingMode.On : UnityEngine.Rendering.ShadowCastingMode.Off;
}
foreach(Transform t in m_RendererParents)
{
foreach (var renderer in t.GetComponentsInChildren<Renderer>())
{
if(renderer == null || m_Filters.Contains(renderer)){ continue; }
renderer.shadowCastingMode = toggle ? UnityEngine.Rendering.ShadowCastingMode.On : UnityEngine.Rendering.ShadowCastingMode.Off;
}
}
}
void Log(string message)
{
if(m_Log)
Utils.Log(message);
}
#endif
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aff3e70e7b23cdc478231849b7482699
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using Unity.Netcode.Components;
using XRMultiplayer;
namespace UnityEngine.XR.Content.Interaction
{
/// <summary>
/// Provides the ability to reset objects
/// </summary>
public class ObjectReset : MonoBehaviour
{
[SerializeField] Transform m_ResetTransform;
List<NetworkPhysicsInteractable> m_Interactables = new List<NetworkPhysicsInteractable>();
void OnTriggerEnter(Collider collider)
{
NetworkPhysicsInteractable networkBaseInteractable = collider.GetComponentInParent<NetworkPhysicsInteractable>();
if (networkBaseInteractable != null && !networkBaseInteractable.isInteracting & !m_Interactables.Contains(networkBaseInteractable) && networkBaseInteractable.IsOwner)
{
m_Interactables.Add(networkBaseInteractable);
ResetTransform(networkBaseInteractable);
}
}
void ResetTransform(NetworkPhysicsInteractable networkBaseInteractable)
{
Transform currentTransform = networkBaseInteractable.transform;
networkBaseInteractable.GetComponent<NetworkTransform>().Teleport(m_ResetTransform.position, m_ResetTransform.rotation, networkBaseInteractable.transform.localScale);
var rigidBody = currentTransform.GetComponentInChildren<Rigidbody>();
if (rigidBody != null)
{
networkBaseInteractable.ResetObjectPhysics();
}
if (m_Interactables.Contains(networkBaseInteractable))
m_Interactables.Remove(networkBaseInteractable);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 78ec3e941589f6747b883dfa38151663
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+107
View File
@@ -0,0 +1,107 @@
using UnityEngine;
using UnityEngine.Pool;
namespace XRMultiplayer
{
public class Pooler : MonoBehaviour
{
/// <summary>
/// The Prefab to spawn and use for pooling.
/// </summary>
[SerializeField, Tooltip("The Prefab to spawn and use for pooling")]
GameObject m_SpawnPrefab;
/// <summary>
/// Collection checks are performed when an instance is returned back to the pool.
/// An exception will be thrown if the instance is already in the pool.
/// Collection checks are only performed in the Editor.
/// </summary>
[SerializeField, Tooltip("An exception will be thrown if the instance is already in the pool")]
bool m_UseCollectionChecks = true;
/// <summary>
/// The default capacity the pool will be created with.
/// </summary>
[SerializeField, Tooltip("he default capacity the pool will be created with")]
int m_DefaultCapacity = 30;
/// <summary>
/// The maximum size of the pool.
/// When the pool reaches the max size then any further instances returned to the pool will be ignored and can be garbage collected.
/// This can be used to prevent the pool growing to a very large size
/// </summary>
[SerializeField, Tooltip("The maximum size of the pool")]
int m_MaxCapacity = 1000;
/// <summary>
/// If true, the spawned object will be parented under the transform of the Pooler.
/// </summary>
[SerializeField, Tooltip("Spawned objects will be parented under this Transform")]
bool m_ParentUnderTransform = false;
IObjectPool<GameObject> m_Pool;
protected virtual void Start()
{
InitializePool();
}
protected void InitializePool()
{
m_Pool = new ObjectPool<GameObject>(CreateNewObject, OnTakeFromPool, OnReturnToPool,
OnDestroyPoolObject, m_UseCollectionChecks, m_DefaultCapacity, m_MaxCapacity);
}
protected GameObject CreateNewObject()
{
GameObject spawnedObject = Instantiate(m_SpawnPrefab);
if (m_ParentUnderTransform)
spawnedObject.transform.SetParent(transform);
return spawnedObject;
}
/// <summary>
/// Called when an instance is taken from the pool.
/// </summary>
protected void OnTakeFromPool(GameObject go)
{
go.SetActive(true);
}
/// <summary>
/// Called when returning an instance to the pool.
/// </summary>
protected void OnReturnToPool(GameObject go)
{
go.SetActive(false);
}
/// <summary>
/// Called when returning an instance to a pool that is full, or when called <see cref="ObjectPool.Dispose"/>, or <see cref="ObjectPool.Clear"/>
/// </summary>
/// <param name="go"></param>
protected void OnDestroyPoolObject(GameObject go)
{
Destroy(go);
}
/// <summary>
/// Get an instance from the pool. If the pool is empty then a new instance will be created.
/// </summary>
public GameObject GetItem()
{
return m_Pool.Get();
}
/// <summary>
/// Returns the instance back to the pool. Returning an instance to a pool that is full will cause the instance to be destroyed.
/// </summary>
/// <param name="item"></param>
public void ReturnItem(GameObject item)
{
m_Pool.Release(item);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dce48ae454aeed348839c2c3f91ab34f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,4 @@
namespace XRMultiplayer
{
public class PoolerProjectiles : Pooler { }
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 51e84aab007306d48bdf6774e8046b82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,27 @@
using UnityEngine;
namespace XRMultiplayer
{
[ExecuteInEditMode]
public class PositionalClampY : MonoBehaviour
{
[SerializeField] Vector2 m_minMaxHeight;
private void Update()
{
ClampBounds();
}
void ClampBounds()
{
if (transform.position.y < m_minMaxHeight.x)
{
transform.position = new Vector3(transform.position.x, m_minMaxHeight.x, transform.position.z);
}
else if (transform.position.y > m_minMaxHeight.y)
{
transform.position = new Vector3(transform.position.x, m_minMaxHeight.y, transform.position.z);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5d92ef1488c75c64da4a33d0379b73a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
using UnityEngine;
using UnityEngine.Video;
/// <summary>
/// This script Toggles on / off the video player after each loop to fix a bug where the video player freezes after time.
/// </summary>
public class ResetVideoOnLoop : MonoBehaviour
{
[SerializeField] VideoPlayer m_VideoPlayer;
// Start is called before the first frame update
void Start() => m_VideoPlayer.loopPointReached += OnLoopPointReached;
void OnDestroy() => m_VideoPlayer.loopPointReached -= OnLoopPointReached;
private void OnLoopPointReached(VideoPlayer source)
{
source.enabled = false;
source.enabled = true;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c41195ed3f3c0c042afc4aadf698fe04
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,32 @@
using UnityEngine;
namespace XRMultiplayer
{
[ExecuteInEditMode]
public class SnapToPlayerHeight : MonoBehaviour
{
[SerializeField] float m_heightOffset = -.25f;
[SerializeField] float m_ZOffset;
[SerializeField] Transform m_CameraTransform;
void Start() => SetupReferences();
void OnValidate() => SetupReferences();
void SetupReferences()
{
if (m_CameraTransform == null && Camera.main != null)
m_CameraTransform = Camera.main.transform;
}
// Update is called once per frame
void Update()
{
if (m_CameraTransform != null)
{
transform.position = new Vector3(transform.position.x, m_CameraTransform.position.y + m_heightOffset, transform.position.z);
transform.localPosition = new Vector3(transform.localPosition.x, transform.localPosition.y, m_ZOffset);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 28f3812bf17744341bcc863f74d26d65
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,31 @@
using System;
using UnityEngine;
namespace XRMultiplayer
{
/// <summary>
/// A simple class used for callbacks when OnTriggerEnter or OnTriggerExit is called.
/// </summary>
[RequireComponent(typeof(Collider))]
public class SubTrigger : MonoBehaviour
{
public Action<Collider, bool> OnTriggerAction;
public Collider subTriggerCollider;
private void Awake()
{
if (subTriggerCollider == null)
TryGetComponent(out subTriggerCollider);
}
private void OnTriggerEnter(Collider other)
{
OnTriggerAction?.Invoke(other, true);
}
private void OnTriggerExit(Collider other)
{
OnTriggerAction?.Invoke(other, false);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a05b87c216ca9e45a3773cd55311622
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,194 @@
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
namespace XRMultiplayer
{
public class UIComponentToggler : CalloutGazeController
{
[Header("Component Toggling")]
[SerializeField] CanvasGroup m_CanvasGroup;
[SerializeField] TooltipUI m_TooltipUI;
[SerializeField] float m_FadeDuration = .25f;
[SerializeField] Vector2 m_MinMaxThresholdDistance = new Vector2(2.0f, 5.0f);
[SerializeField] Vector2 m_MinMaxFacingThreshold = new Vector2(.8f, .995f);
[SerializeField] float m_MaxRenderingDistance = 15.0f;
[SerializeField] List<MonoBehaviour> m_ComponentsToToggle;
[SerializeField] GameObject[] m_ObjectsToToggle;
[SerializeField] bool m_StartHidden = true;
[SerializeField] bool m_DisableCanvasGroupObject = false;
#pragma warning disable CS0618 // Type or member is obsolete
FloatTweenableVariable m_FloatFadeTweenableVariable = new FloatTweenableVariable();
#pragma warning restore CS0618 // Type or member is obsolete
bool m_Hidden = false;
bool m_InRange = false;
Coroutine m_FadeRoutine;
// Start is called before the first frame update
void Start()
{
if (m_GazeTransform == null)
{
m_GazeTransform = Camera.main.transform;
}
if (m_CanvasGroup == null)
{
m_CanvasGroup = GetComponentInChildren<CanvasGroup>();
}
if (m_TooltipUI == null)
{
m_TooltipUI = GetComponentInChildren<TooltipUI>();
}
m_FacingThreshold = .98f;
m_FacingEntered.AddListener(delegate { ToggleFade(false); });
m_FacingExited.AddListener(delegate { ToggleFade(true); });
m_FloatFadeTweenableVariable.Subscribe(UpdateFade);
if (m_StartHidden)
{
ToggleFade(true);
}
}
protected override void Update()
{
base.Update();
float currentDistance = Vector3.Distance(transform.position, m_GazeTransform.position);
if (m_InRange)
{
float perc = (Mathf.Clamp(currentDistance, m_MinMaxThresholdDistance.x, m_MinMaxThresholdDistance.y) - m_MinMaxThresholdDistance.x) / (m_MinMaxThresholdDistance.y - m_MinMaxThresholdDistance.x);
m_FacingThreshold = Mathf.Lerp(m_MinMaxFacingThreshold.x, m_MinMaxFacingThreshold.y, perc);
if (currentDistance > m_MaxRenderingDistance)
{
m_InRange = false;
ToggleFade(true);
}
}
else
{
if (currentDistance <= m_MaxRenderingDistance)
{
m_InRange = true;
}
}
}
private void OnDestroy()
{
m_FacingEntered.RemoveListener(delegate { ToggleFade(false); });
m_FacingExited.RemoveListener(delegate { ToggleFade(true); });
}
[ContextMenu("Get References")]
void FindRendererReferences()
{
m_ComponentsToToggle = new List<MonoBehaviour>();
List<Image> images = new List<Image>(GetComponentsInChildren<Image>());
List<TMP_Text> texts = new List<TMP_Text>(GetComponentsInChildren<TMP_Text>());
foreach (Image image in images)
{
m_ComponentsToToggle.Add(image);
}
foreach (TMP_Text text in texts)
{
m_ComponentsToToggle.Add(text);
}
}
[ContextMenu("Toggle Components")]
void ToggleFade()
{
ToggleFade(!m_Hidden);
}
void ToggleFade(bool toggle)
{
m_Hidden = toggle;
if (!m_Hidden)
{
ToggleComponents(true);
}
if (m_FadeRoutine != null) StopCoroutine(m_FadeRoutine);
m_FadeRoutine = StartCoroutine(m_FloatFadeTweenableVariable.PlaySequence(m_FloatFadeTweenableVariable.Value, m_Hidden ? 0.0f : 1.0f, m_FadeDuration, CompleteFade));
}
void ToggleComponents(bool show)
{
foreach (var c in m_ComponentsToToggle)
{
if (c != null)
c.enabled = show;
else
Utils.Log("Component Toggler is missing references", 1);
}
foreach (GameObject go in m_ObjectsToToggle)
{
if (go != null)
go.SetActive(show);
else
Utils.Log("Component Toggler is missing references", 1);
}
if (m_DisableCanvasGroupObject)
{
if (m_CanvasGroup != null)
m_CanvasGroup.gameObject.SetActive(show);
else
Utils.Log("Component Toggler is missing references", 1);
}
if (m_TooltipUI != null)
{
if (!show)
{
if (m_TooltipUI != null)
m_TooltipUI.ResetTooltip();
else
Utils.Log("Component Toggler is missing references", 1);
}
}
}
void UpdateFade(float fadeAmount)
{
if (m_CanvasGroup != null)
{
m_CanvasGroup.alpha = fadeAmount;
}
}
void CompleteFade()
{
if (m_FloatFadeTweenableVariable.Value <= 0.0f)
{
ToggleComponents(false);
}
}
public void ToggleShow(bool show)
{
if (show)
{
m_FacingEntered.Invoke();
}
else
{
CheckPointerExit();
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba379b52851771f438e4580c5da84a31
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,93 @@
using System.Text;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace XRMultiplayer
{
public class Utils : MonoBehaviour
{
public const string k_LogPrefix = "<color=#33FF64>[XRMultiplayer]</color> ";
public static LogLevel s_LogLevel = LogLevel.Developer;
public static void LogError(string message) => Log(message, 2);
public static void LogWarning(string message) => Log(message, 1);
public static void Log(string message, int logLevel = 0)
{
if (s_LogLevel == LogLevel.Nothing) return;
StringBuilder sb = new(k_LogPrefix);
sb.Append(message);
switch (logLevel)
{
case 0:
if (s_LogLevel == 0)
Debug.Log(sb);
break;
case 1:
if ((int)s_LogLevel < 2)
Debug.LogWarning(sb);
break;
case 2:
Debug.LogError(sb);
break;
}
}
public static string GetOrdinal(int num)
{
if (num <= 0) return num.ToString();
switch (num % 100)
{
case 11:
case 12:
case 13:
return "th";
}
switch (num % 10)
{
case 1:
return "st";
case 2:
return "nd";
case 3:
return "rd";
default:
return "th";
}
}
public static int RealMod(int a, int b)
{
return (a % b + b) % b;
}
public static float GetPercentOfValueBetweenTwoValues(float min, float max, float input)
{
input = Mathf.Clamp(input, min, max);
return (input - min) / (max - min);
}
}
[System.Serializable]
public class TextButton
{
public Button button;
public TMP_Text buttonText;
public void UpdateButton(UnityAction clickFunction, string newText, bool removeAllListeners = true, bool isInteractable = true)
{
if (removeAllListeners)
button.onClick.RemoveAllListeners();
button.interactable = isInteractable;
button.onClick.AddListener(clickFunction);
buttonText.text = newText;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 50f56249bf6fe724c90534cda8b5d920
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
using TMPro;
using UnityEngine;
namespace XRMultiplayer
{
public class VersionText : MonoBehaviour
{
[SerializeField] TMP_Text[] m_VersionTextComponents;
[SerializeField] string m_Prefix = "v";
[SerializeField] string m_Suffix = "";
// Start is called before the first frame update
void Start()
{
SetText();
}
private void OnValidate()
{
SetText();
}
void SetText()
{
if (m_VersionTextComponents != null)
{
foreach (TMP_Text t in m_VersionTextComponents)
{
t.text = $"{m_Prefix}{Application.version}{m_Suffix}";
}
}
else
{
Utils.Log("Missing Text component on VersionText script", 2);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4f5a2bea27a2d584dbd76071985c5c5e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+435
View File
@@ -0,0 +1,435 @@
using System;
using UnityEngine.Events;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
namespace UnityEngine.XR.Content.Interaction
{
/// <summary>
/// An interactable knob that follows the rotation of the interactor
/// </summary>
public class XRKnob : UnityEngine.XR.Interaction.Toolkit.Interactables.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;
}
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]
public class ValueChangeEvent : UnityEvent<float> { }
[SerializeField]
[Tooltip("The object that is visually grabbed and manipulated")]
Transform m_Handle = null;
[SerializeField]
[Tooltip("The transform to snap the interactor to when holding the lever")]
Transform m_InteractorSnapTransform = 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();
UnityEngine.XR.Interaction.Toolkit.Interactors.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;
}
public override Transform GetAttachTransform(IXRInteractor interactor)
{
return m_InteractorSnapTransform;
}
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
{
base.ProcessInteractable(updatePhase);
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
{
if (isSelected)
{
UpdateRotation();
}
}
}
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 value)
{
if (m_ClampedMotion)
value = Mathf.Clamp01(value);
if (m_AngleIncrement > 0)
{
var angleRange = m_MaxAngle - m_MinAngle;
var angle = Mathf.Lerp(0.0f, angleRange, value);
angle = Mathf.Round(angle / m_AngleIncrement) * m_AngleIncrement;
value = Mathf.InverseLerp(0.0f, angleRange, angle);
}
m_Value = value;
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;
// Draw a circle from the handle point at size of position tracked radius
var circleCenter = transform.position;
if (m_Handle != null)
circleCenter = m_Handle.position;
var circleX = transform.right;
var circleY = transform.forward;
Gizmos.color = Color.green;
var segmentCounter = 0;
while (segmentCounter < k_CircleSegments)
{
var startAngle = (float)segmentCounter * k_SegmentRatio * 2.0f * Mathf.PI;
segmentCounter++;
var endAngle = (float)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());
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65a8500fa86faa04596fb5f9d40efeae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,242 @@
using UnityEngine.Events;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
namespace UnityEngine.XR.Content.Interaction
{
/// <summary>
/// An interactable lever that snaps into an on or off position by a direct interactor
/// </summary>
public class XRLever : XR.Interaction.Toolkit.Interactables.XRBaseInteractable
{
const float k_LeverDeadZone = 0.1f; // Prevents rapid switching between on and off states when right in the middle
[SerializeField]
[Tooltip("The object that is visually grabbed and manipulated")]
Transform m_Handle = null;
[SerializeField]
[Tooltip("The transform to snap the interactor to when holding the lever")]
Transform m_InteractorSnapTransform = null;
[SerializeField]
[Tooltip("The value of the lever")]
bool m_Value = false;
[SerializeField]
[Tooltip("If enabled, the lever will snap to the value position when released")]
bool m_LockToValue;
[SerializeField]
[Tooltip("Angle of the lever in the 'on' position")]
[Range(-90.0f, 90.0f)]
float m_MaxAngle = 90.0f;
[SerializeField]
[Tooltip("Angle of the lever in the 'off' position")]
[Range(-90.0f, 90.0f)]
float m_MinAngle = -90.0f;
[SerializeField]
[Tooltip("Events to trigger when the lever activates")]
UnityEvent m_OnLeverActivate = new UnityEvent();
[SerializeField]
[Tooltip("Events to trigger when the lever deactivates")]
UnityEvent m_OnLeverDeactivate = new UnityEvent();
UnityEngine.XR.Interaction.Toolkit.Interactors.IXRSelectInteractor m_Interactor;
/// <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 lever
/// </summary>
public bool value
{
get => m_Value;
set => SetValue(value, true);
}
/// <summary>
/// If enabled, the lever will snap to the value position when released
/// </summary>
public bool lockToValue { get; set; }
/// <summary>
/// Angle of the lever in the 'on' position
/// </summary>
public float maxAngle
{
get => m_MaxAngle;
set => m_MaxAngle = value;
}
/// <summary>
/// Angle of the lever in the 'off' position
/// </summary>
public float minAngle
{
get => m_MinAngle;
set => m_MinAngle = value;
}
/// <summary>
/// Events to trigger when the lever activates
/// </summary>
public UnityEvent onLeverActivate => m_OnLeverActivate;
/// <summary>
/// Events to trigger when the lever deactivates
/// </summary>
public UnityEvent onLeverDeactivate => m_OnLeverDeactivate;
void Start()
{
SetValue(m_Value, true);
}
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;
}
void EndGrab(SelectExitEventArgs args)
{
SetValue(m_Value, true);
m_Interactor = null;
}
public override Transform GetAttachTransform(IXRInteractor interactor)
{
return m_InteractorSnapTransform;
}
// public override Transform GetAttachTransform(IXRInteractor interactor)
// {
// return base.GetAttachTransform(interactor);
// }
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
{
base.ProcessInteractable(updatePhase);
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
{
if (isSelected)
{
UpdateValue();
}
}
}
Vector3 GetLookDirection()
{
Vector3 direction = m_Interactor.GetAttachTransform(this).position - m_Handle.position;
direction = transform.InverseTransformDirection(direction);
direction.x = 0;
return direction.normalized;
}
void UpdateValue()
{
var lookDirection = GetLookDirection();
var lookAngle = Mathf.Atan2(lookDirection.z, lookDirection.y) * Mathf.Rad2Deg;
if (m_MinAngle < m_MaxAngle)
lookAngle = Mathf.Clamp(lookAngle, m_MinAngle, m_MaxAngle);
else
lookAngle = Mathf.Clamp(lookAngle, m_MaxAngle, m_MinAngle);
var maxAngleDistance = Mathf.Abs(m_MaxAngle - lookAngle);
var minAngleDistance = Mathf.Abs(m_MinAngle - lookAngle);
if (m_Value)
maxAngleDistance *= (1.0f - k_LeverDeadZone);
else
minAngleDistance *= (1.0f - k_LeverDeadZone);
var newValue = (maxAngleDistance < minAngleDistance);
SetHandleAngle(lookAngle);
SetValue(newValue);
}
void SetValue(bool isOn, bool forceRotation = false)
{
if (m_Value == isOn)
{
if (forceRotation)
SetHandleAngle(m_Value ? m_MaxAngle : m_MinAngle);
return;
}
m_Value = isOn;
if (m_Value)
{
m_OnLeverActivate.Invoke();
}
else
{
m_OnLeverDeactivate.Invoke();
}
if (!isSelected && (m_LockToValue || forceRotation))
SetHandleAngle(m_Value ? m_MaxAngle : m_MinAngle);
}
void SetHandleAngle(float angle)
{
if (m_Handle != null)
m_Handle.localRotation = Quaternion.Euler(angle, 0.0f, 0.0f);
}
void OnDrawGizmosSelected()
{
var angleStartPoint = transform.position;
if (m_Handle != null)
angleStartPoint = m_Handle.position;
const float k_AngleLength = 0.25f;
var angleMaxPoint = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_MaxAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
var angleMinPoint = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_MinAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
Gizmos.color = Color.green;
Gizmos.DrawLine(angleStartPoint, angleMaxPoint);
Gizmos.color = Color.red;
Gizmos.DrawLine(angleStartPoint, angleMinPoint);
}
void OnValidate()
{
SetHandleAngle(m_Value ? m_MaxAngle : m_MinAngle);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e601cf28e9702c945abe3b90e7f51974
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,44 @@
namespace UnityEngine.XR.Content.Interaction
{
/// <summary>
/// Socket interactor that only selects and hovers interactables with a keychain component containing specific keys.
/// </summary>
public class XRLockSocketInteractor : UnityEngine.XR.Interaction.Toolkit.Interactors.XRSocketInteractor
{
[Space]
[SerializeField]
[Tooltip("The required keys to interact with this socket.")]
Lock m_Lock;
/// <summary>
/// The required keys to interact with this socket.
/// </summary>
public Lock keychainLock
{
get => m_Lock;
set => m_Lock = value;
}
/// <inheritdoc />
public override bool CanHover(UnityEngine.XR.Interaction.Toolkit.Interactables.IXRHoverInteractable interactable)
{
if (!base.CanHover(interactable))
return false;
var keyChain = interactable.transform.GetComponent<IKeychain>();
return m_Lock.CanUnlock(keyChain);
}
/// <inheritdoc />
public override bool CanSelect(UnityEngine.XR.Interaction.Toolkit.Interactables.IXRSelectInteractable interactable)
{
if (!base.CanSelect(interactable))
return false;
var keyChain = interactable.transform.GetComponent<IKeychain>();
return m_Lock.CanUnlock(keyChain);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eecc085bf63270540b2d9a418fb5f149
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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 XRMultiplayer
{
/// <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: