Initialer Upload neues Unity-Projekt

This commit is contained in:
Daniel Ocks
2025-07-21 09:11:14 +02:00
commit eeca72985b
14558 changed files with 1508140 additions and 0 deletions

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEngine.Events;
namespace Oculus.Interaction
{
public class ActiveStateUnityEventWrapper : MonoBehaviour
{
[Tooltip("Events will fire based on the state of this IActiveState.")]
[SerializeField, Interface(typeof(IActiveState))]
private UnityEngine.Object _activeState;
private IActiveState ActiveState;
[Tooltip("This event will be fired when the provided IActiveState becomes active.")]
[SerializeField]
private UnityEvent _whenActivated;
[Tooltip("This event will be fired when the provided IActiveState becomes inactive.")]
[SerializeField]
private UnityEvent _whenDeactivated;
public UnityEvent WhenActivated => _whenActivated;
public UnityEvent WhenDeactivated => _whenDeactivated;
[SerializeField]
[Tooltip("If true, the corresponding event will be fired at the beginning of Update")]
private bool _emitOnFirstUpdate = true;
private bool _emittedOnFirstUpdate = false;
private bool _savedState;
protected virtual void Awake()
{
ActiveState = _activeState as IActiveState;
}
protected virtual void Start()
{
this.AssertField(ActiveState, nameof(ActiveState));
_savedState = false;
}
protected virtual void Update()
{
if (_emitOnFirstUpdate && !_emittedOnFirstUpdate)
{
InvokeEvent();
_emittedOnFirstUpdate = true;
}
if (_savedState != ActiveState.Active)
{
_savedState = ActiveState.Active;
InvokeEvent();
}
}
private void InvokeEvent()
{
if (_savedState)
{
_whenActivated.Invoke();
}
else
{
_whenDeactivated.Invoke();
}
}
#region Inject
public void InjectAllActiveStateUnityEventWrapper(IActiveState activeState)
{
InjectActiveState(activeState);
}
public void InjectActiveState(IActiveState activeState)
{
_activeState = activeState as UnityEngine.Object;
ActiveState = activeState;
}
public void InjectOptionalEmitOnFirstUpdate(bool emitOnFirstUpdate)
{
_emitOnFirstUpdate = emitOnFirstUpdate;
}
public void InjectOptionalWhenActivated(UnityEvent whenActivated)
{
_whenActivated = whenActivated;
}
public void InjectOptionalWhenDeactivated(UnityEvent whenDeactivated)
{
_whenDeactivated = whenDeactivated;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d2705674196dfc4da9c25fbacda72d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 62d408212f3d68b45b2bb2deb2d33239
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Oculus.Interaction.Input;
using UnityEngine;
namespace Oculus.Interaction.Unity.Input
{
public class InputAxis : MonoBehaviour, IAxis1D
{
[SerializeField]
private string _axisName;
public float Value()
{
return UnityEngine.Input.GetAxis(_axisName);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 62ac6de3f9c5a0a4c98563d8b26fc962
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Oculus.Interaction.Input;
using UnityEngine;
namespace Oculus.Interaction.Unity.Input
{
public class InputButton : MonoBehaviour, IButton
{
[SerializeField]
private string _buttonName;
public bool Value()
{
return UnityEngine.Input.GetButton(_buttonName);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 17cbfd92dddfbac42becf0ad22b3d80d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Oculus.Interaction.Input;
using UnityEngine;
namespace Oculus.Interaction.Unity.Input
{
public class InputKey : MonoBehaviour, IButton
{
[SerializeField]
private KeyCode _key;
public bool Value()
{
return UnityEngine.Input.GetKey(_key);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9c623d571545a954eb981f14cf5e3eef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Oculus.Interaction.Input;
using UnityEngine;
namespace Oculus.Interaction.Unity.Input
{
public class InputMouseButton : MonoBehaviour, IButton
{
[SerializeField]
private int _button;
public bool Value()
{
return UnityEngine.Input.GetMouseButton(_button);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d388cb9865e6fb34c83ff0b41a218bde
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,172 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Events;
namespace Oculus.Interaction
{
/// <summary>
/// This component makes it possible to connect Interactables in the
/// inspector to Unity Events that are broadcast on state changes.
/// </summary>
public class InteractableUnityEventWrapper : MonoBehaviour
{
[SerializeField, Interface(typeof(IInteractableView))]
private UnityEngine.Object _interactableView;
private IInteractableView InteractableView;
[SerializeField]
private UnityEvent _whenHover;
[SerializeField]
private UnityEvent _whenUnhover;
[SerializeField]
private UnityEvent _whenSelect;
[SerializeField]
private UnityEvent _whenUnselect;
[SerializeField]
private UnityEvent _whenInteractorViewAdded;
[SerializeField]
private UnityEvent _whenInteractorViewRemoved;
[SerializeField]
private UnityEvent _whenSelectingInteractorViewAdded;
[SerializeField]
private UnityEvent _whenSelectingInteractorViewRemoved;
#region Properties
public UnityEvent WhenHover => _whenHover;
public UnityEvent WhenUnhover => _whenUnhover;
public UnityEvent WhenSelect => _whenSelect;
public UnityEvent WhenUnselect => _whenUnselect;
public UnityEvent WhenInteractorViewAdded => _whenInteractorViewAdded;
public UnityEvent WhenInteractorViewRemoved => _whenInteractorViewRemoved;
public UnityEvent WhenSelectingInteractorViewAdded => _whenSelectingInteractorViewAdded;
public UnityEvent WhenSelectingInteractorViewRemoved => _whenSelectingInteractorViewRemoved;
#endregion
protected bool _started = false;
protected virtual void Awake()
{
InteractableView = _interactableView as IInteractableView;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(InteractableView, nameof(InteractableView));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
InteractableView.WhenStateChanged += HandleStateChanged;
InteractableView.WhenInteractorViewAdded += HandleInteractorViewAdded;
InteractableView.WhenInteractorViewRemoved += HandleInteractorViewRemoved;
InteractableView.WhenSelectingInteractorViewAdded += HandleSelectingInteractorViewAdded;
InteractableView.WhenSelectingInteractorViewRemoved += HandleSelectingInteractorViewRemoved;
}
}
protected virtual void OnDisable()
{
if (_started)
{
InteractableView.WhenStateChanged -= HandleStateChanged;
InteractableView.WhenInteractorViewAdded -= HandleInteractorViewAdded;
InteractableView.WhenInteractorViewRemoved -= HandleInteractorViewRemoved;
InteractableView.WhenSelectingInteractorViewAdded -= HandleSelectingInteractorViewAdded;
InteractableView.WhenSelectingInteractorViewRemoved -= HandleSelectingInteractorViewRemoved;
}
}
private void HandleStateChanged(InteractableStateChangeArgs args)
{
switch (args.NewState)
{
case InteractableState.Normal:
if (args.PreviousState == InteractableState.Hover)
{
_whenUnhover.Invoke();
}
break;
case InteractableState.Hover:
if (args.PreviousState == InteractableState.Normal)
{
_whenHover.Invoke();
}
else if (args.PreviousState == InteractableState.Select)
{
_whenUnselect.Invoke();
}
break;
case InteractableState.Select:
if (args.PreviousState == InteractableState.Hover)
{
_whenSelect.Invoke();
}
break;
}
}
private void HandleInteractorViewAdded(IInteractorView interactorView)
{
WhenInteractorViewAdded.Invoke();
}
private void HandleInteractorViewRemoved(IInteractorView interactorView)
{
WhenInteractorViewRemoved.Invoke();
}
private void HandleSelectingInteractorViewAdded(IInteractorView interactorView)
{
WhenSelectingInteractorViewAdded.Invoke();
}
private void HandleSelectingInteractorViewRemoved(IInteractorView interactorView)
{
WhenSelectingInteractorViewRemoved.Invoke();
}
#region Inject
public void InjectAllInteractableUnityEventWrapper(IInteractableView interactableView)
{
InjectInteractableView(interactableView);
}
public void InjectInteractableView(IInteractableView interactableView)
{
_interactableView = interactableView as UnityEngine.Object;
InteractableView = interactableView;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1464721f2283eb14e94a33e812b47be4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Events;
using UnityEngine.Serialization;
namespace Oculus.Interaction
{
/// <summary>
/// This component makes it possible to connect Interactors in the
/// inspector to Unity Events that broadcast on state changes
/// </summary>
public class InteractorUnityEventWrapper : MonoBehaviour
{
[SerializeField, Interface(typeof(IInteractorView))]
private UnityEngine.Object _interactorView;
private IInteractorView InteractorView;
[SerializeField]
private UnityEvent _whenEnabled;
[SerializeField]
private UnityEvent _whenDisabled;
[SerializeField]
private UnityEvent _whenHover;
[SerializeField]
private UnityEvent _whenUnhover;
[SerializeField]
private UnityEvent _whenSelect;
[SerializeField]
private UnityEvent _whenUnselect;
public UnityEvent WhenDisabled => _whenDisabled;
public UnityEvent WhenEnabled => _whenEnabled;
public UnityEvent WhenHover => _whenHover;
public UnityEvent WhenUnhover => _whenUnhover;
public UnityEvent WhenSelect => _whenSelect;
public UnityEvent WhenUnselect => _whenUnselect;
protected bool _started = false;
protected virtual void Awake()
{
InteractorView = _interactorView as IInteractorView;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(InteractorView, nameof(InteractorView));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
InteractorView.WhenStateChanged += HandleStateChanged;
}
}
protected virtual void OnDisable()
{
if (_started)
{
InteractorView.WhenStateChanged -= HandleStateChanged;
}
}
private void HandleStateChanged(InteractorStateChangeArgs args)
{
switch (args.NewState)
{
case InteractorState.Disabled:
_whenDisabled.Invoke();
break;
case InteractorState.Normal:
if (args.PreviousState == InteractorState.Hover)
{
_whenUnhover.Invoke();
}
else if (args.PreviousState == InteractorState.Disabled)
{
_whenEnabled.Invoke();
}
break;
case InteractorState.Hover:
if (args.PreviousState == InteractorState.Normal)
{
_whenHover.Invoke();
}
else if (args.PreviousState == InteractorState.Select)
{
_whenUnselect.Invoke();
}
break;
case InteractorState.Select:
if (args.PreviousState == InteractorState.Hover)
{
_whenSelect.Invoke();
}
break;
}
}
#region Inject
public void InjectAllInteractorUnityEventWrapper(IInteractorView interactorView)
{
InjectInteractorView(interactorView);
}
public void InjectInteractorView(IInteractorView interactorView)
{
_interactorView = interactorView as UnityEngine.Object;
InteractorView = interactorView;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0cf00a93483fd1b4ba357bf6f764d6e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,177 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Serialization;
namespace Oculus.Interaction
{
public class PhysicsGrabbable : MonoBehaviour
{
[SerializeField]
private Grabbable _grabbable;
[SerializeField]
private Rigidbody _rigidbody;
[SerializeField]
[Tooltip("If enabled, the object's mass will scale appropriately as the scale of the object changes.")]
private bool _scaleMassWithSize = true;
private bool _savedIsKinematicState = false;
private bool _isBeingTransformed = false;
private Vector3 _initialScale;
private bool _hasPendingForce;
private Vector3 _linearVelocity;
private Vector3 _angularVelocity;
protected bool _started = false;
public event Action<Vector3, Vector3> WhenVelocitiesApplied = delegate { };
private void Reset()
{
_grabbable = this.GetComponent<Grabbable>();
_rigidbody = this.GetComponent<Rigidbody>();
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(_grabbable, nameof(_grabbable));
this.AssertField(_rigidbody, nameof(_rigidbody));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
_grabbable.WhenPointerEventRaised += HandlePointerEventRaised;
}
}
protected virtual void OnDisable()
{
if (_started)
{
_grabbable.WhenPointerEventRaised -= HandlePointerEventRaised;
}
}
private void HandlePointerEventRaised(PointerEvent evt)
{
switch (evt.Type)
{
case PointerEventType.Select:
if (_grabbable.SelectingPointsCount == 1 && !_isBeingTransformed)
{
DisablePhysics();
}
break;
case PointerEventType.Unselect:
if (_grabbable.SelectingPointsCount == 0)
{
ReenablePhysics();
}
break;
}
}
private void DisablePhysics()
{
_isBeingTransformed = true;
CachePhysicsState();
_rigidbody.isKinematic = true;
}
private void ReenablePhysics()
{
_isBeingTransformed = false;
// update the mass based on the scale change
if (_scaleMassWithSize)
{
float initialScaledVolume = _initialScale.x * _initialScale.y * _initialScale.z;
Vector3 currentScale = _rigidbody.transform.localScale;
float currentScaledVolume = currentScale.x * currentScale.y * currentScale.z;
float changeInMassFactor = currentScaledVolume / initialScaledVolume;
_rigidbody.mass *= changeInMassFactor;
}
// revert the original kinematic state
_rigidbody.isKinematic = _savedIsKinematicState;
}
public void ApplyVelocities(Vector3 linearVelocity, Vector3 angularVelocity)
{
_hasPendingForce = true;
_linearVelocity = linearVelocity;
_angularVelocity = angularVelocity;
}
private void FixedUpdate()
{
if (_hasPendingForce)
{
_hasPendingForce = false;
_rigidbody.AddForce(_linearVelocity, ForceMode.VelocityChange);
_rigidbody.AddTorque(_angularVelocity, ForceMode.VelocityChange);
WhenVelocitiesApplied(_linearVelocity, _angularVelocity);
}
}
private void CachePhysicsState()
{
_savedIsKinematicState = _rigidbody.isKinematic;
_initialScale = _rigidbody.transform.localScale;
}
#region Inject
public void InjectAllPhysicsGrabbable(Grabbable grabbable, Rigidbody rigidbody)
{
InjectGrabbable(grabbable);
InjectRigidbody(rigidbody);
}
public void InjectGrabbable(Grabbable grabbable)
{
_grabbable = grabbable;
}
public void InjectRigidbody(Rigidbody rigidbody)
{
_rigidbody = rigidbody;
}
public void InjectOptionalScaleMassWithSize(bool scaleMassWithSize)
{
_scaleMassWithSize = scaleMassWithSize;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bedaac29a943f844a7074b53c8bdf9f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEngine.UI;
namespace Oculus.Interaction
{
/// <summary>
/// PointableCanvas allows any IPointable to forward its
/// events onto an associated Canvas via the IPointableCanvas interface
/// Requires a PointableCanvasModule present in the scene.
/// </summary>
public class PointableCanvas : PointableElement, IPointableCanvas
{
[Tooltip("PointerEvents will be forwarded to this Unity Canvas.")]
[SerializeField]
private Canvas _canvas;
public Canvas Canvas => _canvas;
private bool _registered = false;
protected override void Start()
{
base.Start();
this.AssertField(Canvas, nameof(Canvas));
this.AssertIsTrue(Canvas.TryGetComponent(out GraphicRaycaster raycaster),
$"{nameof(PointableCanvas)} requires that the {nameof(Canvas)} object has an attached GraphicRaycaster.");
}
private void Register()
{
PointableCanvasModule.RegisterPointableCanvas(this);
_registered = true;
}
private void Unregister()
{
if (!_registered) return;
PointableCanvasModule.UnregisterPointableCanvas(this);
_registered = false;
}
protected override void OnEnable()
{
base.OnEnable();
if (_started)
{
Register();
}
}
protected override void OnDisable()
{
if (_started)
{
Unregister();
}
base.OnDisable();
}
#region Inject
public void InjectAllPointableCanvas(Canvas canvas)
{
InjectCanvas(canvas);
}
public void InjectCanvas(Canvas canvas)
{
_canvas = canvas;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ffe41fe81087fa41a2062cc69b99615
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using Oculus.Interaction.UnityCanvas;
using UnityEngine.Serialization;
namespace Oculus.Interaction
{
public class PointableCanvasMesh : PointableElement
{
[Tooltip("This CanvasMesh determines the Pose of PointerEvents.")]
[SerializeField]
[FormerlySerializedAs("_canvasRenderTextureMesh")]
private CanvasMesh _canvasMesh;
protected override void Start()
{
base.Start();
this.AssertField(_canvasMesh, nameof(_canvasMesh));
}
public override void ProcessPointerEvent(PointerEvent evt)
{
Vector3 transformPosition =
_canvasMesh.ImposterToCanvasTransformPoint(evt.Pose.position);
Pose transformedPose = new Pose(transformPosition, evt.Pose.rotation);
base.ProcessPointerEvent(new PointerEvent(evt.Identifier, evt.Type, transformedPose, evt.Data));
}
#region Inject
public void InjectAllCanvasMeshPointable(CanvasMesh canvasMesh)
{
InjectCanvasMesh(canvasMesh);
}
public void InjectCanvasMesh(CanvasMesh canvasMesh)
{
_canvasMesh = canvasMesh;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b78f8e52fe10fd9499c5e5c8b3f953fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,622 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine;
using UnityEngine.Assertions;
using System;
namespace Oculus.Interaction
{
public class PointableCanvasEventArgs
{
public readonly Canvas Canvas;
public readonly GameObject Hovered;
public readonly bool Dragging;
public PointableCanvasEventArgs(Canvas canvas, GameObject hovered, bool dragging)
{
Canvas = canvas;
Hovered = hovered;
Dragging = dragging;
}
}
/// <summary>
/// IPointerInteractableModule manages all InteractableCanvas events in
/// the scene and translates them into pointer events for Unity Canvas UIs.
/// </summary>
public class PointableCanvasModule : PointerInputModule
{
public static event Action<PointableCanvasEventArgs> WhenSelected;
public static event Action<PointableCanvasEventArgs> WhenUnselected;
public static event Action<PointableCanvasEventArgs> WhenSelectableHovered;
public static event Action<PointableCanvasEventArgs> WhenSelectableUnhovered;
[Tooltip("If true, the initial press position will be used as the drag start " +
"position, rather than the position when drag threshold is exceeded. This is used " +
"to prevent the pointer position shifting relative to the surface while dragging.")]
[SerializeField]
private bool _useInitialPressPositionForDrag = true;
private Camera _pointerEventCamera;
private static PointableCanvasModule _instance = null;
private static PointableCanvasModule Instance
{
get
{
return _instance;
}
}
public static void RegisterPointableCanvas(IPointableCanvas pointerCanvas)
{
Assert.IsNotNull(Instance, $"A <b>{nameof(PointableCanvasModule)}</b> is required in the scene.");
Instance.AddPointerCanvas(pointerCanvas);
}
public static void UnregisterPointableCanvas(IPointableCanvas pointerCanvas)
{
Instance?.RemovePointerCanvas(pointerCanvas);
}
private Dictionary<int, Pointer> _pointerMap = new Dictionary<int, Pointer>();
private List<RaycastResult> _raycastResultCache = new List<RaycastResult>();
private List<Pointer> _pointersForDeletion = new List<Pointer>();
private Dictionary<IPointableCanvas, Action<PointerEvent>> _pointerCanvasActionMap =
new Dictionary<IPointableCanvas, Action<PointerEvent>>();
private Pointer[] _pointersToProcessScratch = Array.Empty<Pointer>();
private void AddPointerCanvas(IPointableCanvas pointerCanvas)
{
Action<PointerEvent> pointerCanvasAction = (args) => HandlePointerEvent(pointerCanvas.Canvas, args);
_pointerCanvasActionMap.Add(pointerCanvas, pointerCanvasAction);
pointerCanvas.WhenPointerEventRaised += pointerCanvasAction;
}
private void RemovePointerCanvas(IPointableCanvas pointerCanvas)
{
Action<PointerEvent> pointerCanvasAction = _pointerCanvasActionMap[pointerCanvas];
_pointerCanvasActionMap.Remove(pointerCanvas);
pointerCanvas.WhenPointerEventRaised -= pointerCanvasAction;
List<int> pointerIDs = new List<int>(_pointerMap.Keys);
foreach (int pointerID in pointerIDs)
{
Pointer pointer = _pointerMap[pointerID];
if (pointer.Canvas != pointerCanvas.Canvas)
{
continue;
}
ClearPointerSelection(pointer.PointerEventData);
pointer.MarkForDeletion();
_pointersForDeletion.Add(pointer);
_pointerMap.Remove(pointerID);
}
}
private void HandlePointerEvent(Canvas canvas, PointerEvent evt)
{
Pointer pointer;
switch (evt.Type)
{
case PointerEventType.Hover:
pointer = new Pointer(canvas);
pointer.PointerEventData = new PointerEventData(eventSystem);
pointer.SetPosition(evt.Pose.position);
_pointerMap.Add(evt.Identifier, pointer);
break;
case PointerEventType.Unhover:
pointer = _pointerMap[evt.Identifier];
_pointerMap.Remove(evt.Identifier);
pointer.MarkForDeletion();
_pointersForDeletion.Add(pointer);
break;
case PointerEventType.Select:
pointer = _pointerMap[evt.Identifier];
pointer.SetPosition(evt.Pose.position);
pointer.Press();
break;
case PointerEventType.Unselect:
pointer = _pointerMap[evt.Identifier];
pointer.SetPosition(evt.Pose.position);
pointer.Release();
break;
case PointerEventType.Move:
pointer = _pointerMap[evt.Identifier];
pointer.SetPosition(evt.Pose.position);
break;
case PointerEventType.Cancel:
pointer = _pointerMap[evt.Identifier];
_pointerMap.Remove(evt.Identifier);
ClearPointerSelection(pointer.PointerEventData);
pointer.MarkForDeletion();
_pointersForDeletion.Add(pointer);
break;
}
}
/// <summary>
/// Pointer class that is used for state associated with IPointables that are currently
/// tracked by any IPointableCanvases in the scene.
/// </summary>
private class Pointer
{
public PointerEventData PointerEventData { get; set; }
public bool MarkedForDeletion { get; private set; }
private Canvas _canvas;
public Canvas Canvas => _canvas;
private Vector3 _position;
public Vector3 Position => _position;
private GameObject _hoveredSelectable;
public GameObject HoveredSelectable => _hoveredSelectable;
private bool _pressing = false;
private bool _pressed;
private bool _released;
public Pointer(Canvas canvas)
{
_canvas = canvas;
_pressed = _released = false;
}
public void Press()
{
if (_pressing) return;
_pressing = true;
_pressed = true;
}
public void Release()
{
if (!_pressing) return;
_pressing = false;
_released = true;
}
public void ReadAndResetPressedReleased(out bool pressed, out bool released)
{
pressed = _pressed;
released = _released;
_pressed = _released = false;
}
public void MarkForDeletion()
{
MarkedForDeletion = true;
Release();
}
public void SetPosition(Vector3 position)
{
_position = position;
}
public void SetHoveredSelectable(GameObject hoveredSelectable)
{
_hoveredSelectable = hoveredSelectable;
}
}
protected override void Awake()
{
base.Awake();
Assert.IsNull(_instance, "There must be at most one PointableCanvasModule in the scene");
_instance = this;
}
protected override void OnDestroy()
{
// Must unset _instance prior to calling the base.OnDestroy, otherwise error is thrown:
// Can't add component to object that is being destroyed.
// UnityEngine.EventSystems.BaseInputModule:get_input ()
_instance = null;
base.OnDestroy();
}
protected bool _started = false;
protected override void Start()
{
this.BeginStart(ref _started, () => base.Start());
this.EndStart(ref _started);
}
protected override void OnEnable()
{
base.OnEnable();
if (_started)
{
_pointerEventCamera = gameObject.AddComponent<Camera>();
_pointerEventCamera.nearClipPlane = 0.1f;
// We do not need this camera to be enabled to serve this module's purposes:
// as a dependency for Canvases and for its WorldToScreenPoint functionality
_pointerEventCamera.enabled = false;
}
}
protected override void OnDisable()
{
if (_started)
{
Destroy(_pointerEventCamera);
_pointerEventCamera = null;
}
base.OnDisable();
}
// Based On FindFirstRaycast
protected static RaycastResult FindFirstRaycastWithinCanvas(List<RaycastResult> candidates, Canvas canvas)
{
GameObject candidateGameObject;
Canvas candidateCanvas;
for (var i = 0; i < candidates.Count; ++i)
{
candidateGameObject = candidates[i].gameObject;
if (candidateGameObject == null) continue;
candidateCanvas = candidateGameObject.GetComponentInParent<Canvas>();
if (candidateCanvas == null) continue;
if (candidateCanvas.rootCanvas != canvas) continue;
return candidates[i];
}
return new RaycastResult();
}
private void UpdateRaycasts(Pointer pointer, out bool pressed, out bool released)
{
PointerEventData pointerEventData = pointer.PointerEventData;
Vector2 prevPosition = pointerEventData.position;
pointerEventData.Reset();
pointer.ReadAndResetPressedReleased(out pressed, out released);
if (pointer.MarkedForDeletion)
{
pointerEventData.pointerCurrentRaycast = new RaycastResult();
return;
}
Canvas canvas = pointer.Canvas;
canvas.worldCamera = _pointerEventCamera;
Vector3 position = Vector3.zero;
var plane = new Plane(-1f * canvas.transform.forward, canvas.transform.position);
var ray = new Ray(pointer.Position - canvas.transform.forward, canvas.transform.forward);
float enter;
if (plane.Raycast(ray, out enter))
{
position = ray.GetPoint(enter);
}
// We need to position our camera at an offset from the Pointer position or else
// a graphic raycast may ignore a world canvas that's outside of our regular camera view(s)
_pointerEventCamera.transform.position = pointer.Position - canvas.transform.forward;
_pointerEventCamera.transform.LookAt(pointer.Position, canvas.transform.up);
Vector2 pointerPosition = _pointerEventCamera.WorldToScreenPoint(position);
pointerEventData.position = pointerPosition;
// RaycastAll raycasts against with every GraphicRaycaster in the scene,
// including nested ones like in the case of a dropdown
eventSystem.RaycastAll(pointerEventData, _raycastResultCache);
RaycastResult firstResult = FindFirstRaycastWithinCanvas(_raycastResultCache, canvas);
pointer.PointerEventData.pointerCurrentRaycast = firstResult;
_raycastResultCache.Clear();
// We use a static translation offset from the canvas for 2D position delta tracking
_pointerEventCamera.transform.position = canvas.transform.position - canvas.transform.forward;
_pointerEventCamera.transform.LookAt(canvas.transform.position, canvas.transform.up);
pointerPosition = _pointerEventCamera.WorldToScreenPoint(position);
pointerEventData.position = pointerPosition;
if (pressed)
{
pointerEventData.delta = Vector2.zero;
}
else
{
pointerEventData.delta = pointerEventData.position - prevPosition;
}
pointerEventData.button = PointerEventData.InputButton.Left;
}
public override void Process()
{
ProcessPointers(_pointersForDeletion, true);
ProcessPointers(_pointerMap.Values, false);
}
private void ProcessPointers(ICollection<Pointer> pointers, bool clearAndReleasePointers)
{
// Before processing pointers, take a copy of the array since _pointersForDeletion or
// _pointerMap may be modified if a pointer event handler adds or removes a
// PointableCanvas.
int pointersToProcessCount = pointers.Count;
if (pointersToProcessCount == 0)
{
return;
}
if (pointersToProcessCount > _pointersToProcessScratch.Length)
{
_pointersToProcessScratch = new Pointer[pointersToProcessCount];
}
pointers.CopyTo(_pointersToProcessScratch, 0);
if (clearAndReleasePointers)
{
pointers.Clear();
}
foreach (Pointer pointer in _pointersToProcessScratch)
{
ProcessPointer(pointer, clearAndReleasePointers);
}
}
private void ProcessPointer(Pointer pointer, bool forceRelease = false)
{
bool pressed = false;
bool released = false;
bool wasDragging = pointer.PointerEventData.dragging;
UpdateRaycasts(pointer, out pressed, out released);
PointerEventData pointerEventData = pointer.PointerEventData;
UpdatePointerEventData(pointerEventData, pressed, released);
released |= forceRelease;
if (!released)
{
ProcessMove(pointerEventData);
ProcessDrag(pointerEventData);
}
else
{
HandlePointerExitAndEnter(pointerEventData, null);
RemovePointerData(pointerEventData);
}
HandleSelectableHover(pointer, wasDragging);
HandleSelectablePress(pointer, pressed, released, wasDragging);
}
private void HandleSelectableHover(Pointer pointer, bool wasDragging)
{
bool dragging = pointer.PointerEventData.dragging || wasDragging;
GameObject currentOverGo = pointer.PointerEventData.pointerCurrentRaycast.gameObject;
GameObject prevHoveredSelectable = pointer.HoveredSelectable;
GameObject newHoveredSelectable = ExecuteEvents.GetEventHandler<ISelectHandler>(currentOverGo);
pointer.SetHoveredSelectable(newHoveredSelectable);
if (newHoveredSelectable != null && newHoveredSelectable != prevHoveredSelectable)
{
WhenSelectableHovered?.Invoke(new PointableCanvasEventArgs(pointer.Canvas, pointer.HoveredSelectable, dragging));
}
else if (prevHoveredSelectable != null && newHoveredSelectable == null)
{
WhenSelectableUnhovered?.Invoke(new PointableCanvasEventArgs(pointer.Canvas, pointer.HoveredSelectable, dragging));
}
}
private void HandleSelectablePress(Pointer pointer, bool pressed, bool released, bool wasDragging)
{
bool dragging = pointer.PointerEventData.dragging || wasDragging;
if (pressed)
{
WhenSelected?.Invoke(new PointableCanvasEventArgs(pointer.Canvas, pointer.HoveredSelectable, dragging));
}
else if (released && !pointer.MarkedForDeletion)
{
// Unity handles UI selection on release, so we verify the hovered element has been selected
bool hasSelectedHoveredObject = pointer.HoveredSelectable != null &&
pointer.HoveredSelectable == pointer.PointerEventData.selectedObject;
GameObject selectedObject = hasSelectedHoveredObject ? pointer.HoveredSelectable : null;
WhenUnselected?.Invoke(new PointableCanvasEventArgs(pointer.Canvas, selectedObject, dragging));
}
}
/// <summary>
/// This method is based on ProcessTouchPoint in StandaloneInputModule,
/// but is instead used for Pointer events
/// </summary>
protected void UpdatePointerEventData(PointerEventData pointerEvent, bool pressed, bool released)
{
var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
// PointerDown notification
if (pressed)
{
pointerEvent.eligibleForClick = true;
pointerEvent.delta = Vector2.zero;
pointerEvent.dragging = false;
pointerEvent.useDragThreshold = true;
pointerEvent.pressPosition = pointerEvent.position;
pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
DeselectIfSelectionChanged(currentOverGo, pointerEvent);
if (pointerEvent.pointerEnter != currentOverGo)
{
// send a pointer enter to the touched element if it isn't the one to select...
HandlePointerExitAndEnter(pointerEvent, currentOverGo);
pointerEvent.pointerEnter = currentOverGo;
}
// search for the control that will receive the press
// if we can't find a press handler set the press
// handler to be what would receive a click.
var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
// didnt find a press handler... search for a click handler
if (newPressed == null)
newPressed = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
float time = Time.unscaledTime;
if (newPressed == pointerEvent.lastPress)
{
var diffTime = time - pointerEvent.clickTime;
if (diffTime < 0.3f)
++pointerEvent.clickCount;
else
pointerEvent.clickCount = 1;
pointerEvent.clickTime = time;
}
else
{
pointerEvent.clickCount = 1;
}
pointerEvent.pointerPress = newPressed;
pointerEvent.rawPointerPress = currentOverGo;
pointerEvent.clickTime = time;
// Save the drag handler as well
pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
if (pointerEvent.pointerDrag != null)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
}
// PointerUp notification
if (released)
{
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
// see if we mouse up on the same element that we clicked on...
var pointerUpHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// PointerClick and Drop events
if (pointerEvent.pointerPress == pointerUpHandler && pointerEvent.eligibleForClick)
{
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler);
}
if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
{
ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
}
pointerEvent.eligibleForClick = false;
pointerEvent.pointerPress = null;
pointerEvent.rawPointerPress = null;
if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
pointerEvent.dragging = false;
pointerEvent.pointerDrag = null;
// send exit events as we need to simulate this on touch up on touch device
ExecuteEvents.ExecuteHierarchy(pointerEvent.pointerEnter, pointerEvent, ExecuteEvents.pointerExitHandler);
pointerEvent.pointerEnter = null;
}
}
/// <summary>
/// Override of PointerInputModule's ProcessDrag to allow using the initial press position for drag begin.
/// Set _useInitialPressPositionForDrag to false if you prefer the default behaviour of PointerInputModule.
/// </summary>
protected override void ProcessDrag(PointerEventData pointerEvent)
{
if (!pointerEvent.IsPointerMoving() ||
pointerEvent.pointerDrag == null)
return;
if (!pointerEvent.dragging
&& ShouldStartDrag(pointerEvent.pressPosition, pointerEvent.position,
eventSystem.pixelDragThreshold, pointerEvent.useDragThreshold))
{
if (_useInitialPressPositionForDrag)
{
pointerEvent.position = pointerEvent.pressPosition;
}
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent,
ExecuteEvents.beginDragHandler);
pointerEvent.dragging = true;
}
// Drag notification
if (pointerEvent.dragging)
{
// Before doing drag we should cancel any pointer down state
// And clear selection!
if (pointerEvent.pointerPress != pointerEvent.pointerDrag)
{
ClearPointerSelection(pointerEvent);
}
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent,
ExecuteEvents.dragHandler);
}
}
private void ClearPointerSelection(PointerEventData pointerEvent)
{
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent,
ExecuteEvents.pointerUpHandler);
pointerEvent.eligibleForClick = false;
pointerEvent.pointerPress = null;
pointerEvent.rawPointerPress = null;
}
/// <summary>
/// Used in PointerInputModule's ProcessDrag implementation. Brought into this subclass with a protected
/// signature (as opposed to the parent's private signature) to be used in this subclass's overridden ProcessDrag.
/// </summary>
protected static bool ShouldStartDrag(Vector2 pressPos, Vector2 currentPos, float threshold, bool useDragThreshold)
{
if (!useDragThreshold)
return true;
return (pressPos - currentPos).sqrMagnitude >= threshold * threshold;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2518c50cb3fc6a6458d4b743c2f69c7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,146 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Events;
namespace Oculus.Interaction
{
/// <summary>
/// This component makes it possible to connect PointableCanvases in the
/// inspector to Unity Events that are broadcast from PointableCanvasModule.
/// Useful for hooking into uGUI navigation.
/// </summary>
public class PointableCanvasUnityEventWrapper : MonoBehaviour
{
[SerializeField, Interface(typeof(IPointableCanvas))]
private UnityEngine.Object _pointableCanvas;
private IPointableCanvas PointableCanvas;
[SerializeField, Tooltip("Selection and hover events will not be fired while dragging.")]
private bool _suppressWhileDragging = true;
[SerializeField, Tooltip("Raised when beginning hover of a uGUI selectable")]
private UnityEvent _whenBeginHighlight;
[SerializeField, Tooltip("Raised when ending hover of a uGUI selectable")]
private UnityEvent _whenEndHighlight;
[SerializeField, Tooltip("Raised when selecting a hovered uGUI selectable")]
private UnityEvent _whenSelectedHovered;
[SerializeField, Tooltip("Raised when selecting with no uGUI selectable hovered")]
private UnityEvent _whenSelectedEmpty;
[SerializeField, Tooltip("Raised when deselecting a hovered uGUI selectable")]
private UnityEvent _whenUnselectedHovered;
[SerializeField, Tooltip("Raised when deselecting with no uGUI selectable hovered")]
private UnityEvent _whenUnselectedEmpty;
protected bool _started = false;
private bool ShouldFireEvent(PointableCanvasEventArgs args)
{
if (args.Canvas != PointableCanvas.Canvas)
{
return false;
}
if (_suppressWhileDragging && args.Dragging)
{
return false;
}
return true;
}
private void PointableCanvasModule_WhenSelectableHoverEnter(PointableCanvasEventArgs args)
{
if (ShouldFireEvent(args))
{
_whenBeginHighlight.Invoke();
}
}
private void PointableCanvasModule_WhenSelectableHoverExit(PointableCanvasEventArgs args)
{
if (ShouldFireEvent(args))
{
_whenEndHighlight.Invoke();
}
}
private void PointableCanvasModule_WhenSelectableSelected(PointableCanvasEventArgs args)
{
if (ShouldFireEvent(args))
{
if (args.Hovered == null)
_whenSelectedEmpty.Invoke();
else
_whenSelectedHovered.Invoke();
}
}
private void PointableCanvasModule_WhenSelectableUnselected(PointableCanvasEventArgs args)
{
if (ShouldFireEvent(args))
{
if (args.Hovered == null)
_whenUnselectedEmpty.Invoke();
else
_whenUnselectedHovered.Invoke();
}
}
protected virtual void Awake()
{
PointableCanvas = _pointableCanvas as IPointableCanvas;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(PointableCanvas, nameof(PointableCanvas));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
PointableCanvasModule.WhenSelectableHovered += PointableCanvasModule_WhenSelectableHoverEnter;
PointableCanvasModule.WhenSelectableUnhovered += PointableCanvasModule_WhenSelectableHoverExit;
PointableCanvasModule.WhenSelected += PointableCanvasModule_WhenSelectableSelected;
PointableCanvasModule.WhenUnselected += PointableCanvasModule_WhenSelectableUnselected;
}
}
protected virtual void OnDisable()
{
if (_started)
{
PointableCanvasModule.WhenSelectableHovered -= PointableCanvasModule_WhenSelectableHoverEnter;
PointableCanvasModule.WhenSelectableUnhovered -= PointableCanvasModule_WhenSelectableHoverExit;
PointableCanvasModule.WhenSelected -= PointableCanvasModule_WhenSelectableSelected;
PointableCanvasModule.WhenUnselected -= PointableCanvasModule_WhenSelectableUnselected;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5626a0b1dc955a43be59ce7ea116678
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Oculus.Interaction
{
/// <summary>
/// This componenet makes it possible to connect IPointables in the
/// inspector to Unity Events that are broadcast on IPointable events.
/// </summary>
public class PointableUnityEventWrapper : MonoBehaviour
{
[SerializeField, Interface(typeof(IPointable))]
private UnityEngine.Object _pointable;
private IPointable Pointable;
private HashSet<int> _pointers;
[SerializeField]
private UnityEvent<PointerEvent> _whenRelease;
[SerializeField]
private UnityEvent<PointerEvent> _whenHover;
[SerializeField]
private UnityEvent<PointerEvent> _whenUnhover;
[SerializeField]
private UnityEvent<PointerEvent> _whenSelect;
[SerializeField]
private UnityEvent<PointerEvent> _whenUnselect;
[SerializeField]
private UnityEvent<PointerEvent> _whenMove;
[SerializeField]
private UnityEvent<PointerEvent> _whenCancel;
public UnityEvent<PointerEvent> WhenRelease => _whenRelease;
public UnityEvent<PointerEvent> WhenHover => _whenHover;
public UnityEvent<PointerEvent> WhenUnhover => _whenUnhover;
public UnityEvent<PointerEvent> WhenSelect => _whenSelect;
public UnityEvent<PointerEvent> WhenUnselect => _whenUnselect;
public UnityEvent<PointerEvent> WhenMove => _whenMove;
public UnityEvent<PointerEvent> WhenCancel => _whenCancel;
protected bool _started = false;
protected virtual void Awake()
{
Pointable = _pointable as IPointable;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Pointable, nameof(Pointable));
_pointers = new HashSet<int>();
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Pointable.WhenPointerEventRaised += HandlePointerEventRaised;
}
}
protected virtual void OnDisable()
{
if (_started)
{
Pointable.WhenPointerEventRaised -= HandlePointerEventRaised;
}
}
private void HandlePointerEventRaised(PointerEvent evt)
{
switch (evt.Type)
{
case PointerEventType.Hover:
_whenHover.Invoke(evt);
_pointers.Add(evt.Identifier);
break;
case PointerEventType.Unhover:
_whenUnhover.Invoke(evt);
_pointers.Remove(evt.Identifier);
break;
case PointerEventType.Select:
_whenSelect.Invoke(evt);
break;
case PointerEventType.Unselect:
if (_pointers.Contains(evt.Identifier))
{
_whenRelease.Invoke(evt);
}
_whenUnselect.Invoke(evt);
break;
case PointerEventType.Move:
_whenMove.Invoke(evt);
break;
case PointerEventType.Cancel:
_whenCancel.Invoke(evt);
_pointers.Remove(evt.Identifier);
break;
}
}
#region Inject
public void InjectAllPointableUnityEventWrapper(IPointable pointable)
{
InjectPointable(pointable);
}
public void InjectPointable(IPointable pointable)
{
_pointable = pointable as UnityEngine.Object;
Pointable = pointable;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2f8f6e9e6f3e114b9bf9a57c2160615
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Events;
using UnityEngine.Serialization;
namespace Oculus.Interaction
{
public class SelectorUnityEventWrapper : MonoBehaviour
{
[SerializeField, Interface(typeof(ISelector))]
private UnityEngine.Object _selector;
private ISelector Selector;
[SerializeField]
private UnityEvent _whenSelected;
[SerializeField]
private UnityEvent _whenUnselected;
public UnityEvent WhenSelected => _whenSelected;
public UnityEvent WhenUnselected => _whenUnselected;
protected bool _started = false;
protected virtual void Awake()
{
Selector = _selector as ISelector;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Selector, nameof(Selector));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Selector.WhenSelected += HandleSelected;
Selector.WhenUnselected += HandleUnselected;
}
}
protected virtual void OnDisable()
{
if (_started)
{
Selector.WhenSelected -= HandleSelected;
Selector.WhenUnselected -= HandleUnselected;
}
}
private void HandleSelected()
{
_whenSelected.Invoke();
}
private void HandleUnselected()
{
_whenUnselected.Invoke();
}
#region Inject
public void InjectAllSelectorUnityEventWrapper(ISelector selector)
{
InjectSelector(selector);
}
public void InjectSelector(ISelector selector)
{
_selector = selector as UnityEngine.Object;
Selector = selector;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de76f7169412b8f4896235a1585d8939
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
namespace Oculus.Interaction
{
/// <summary>
/// Override Toggle to clear state on drag while still bubbling events up through
/// the hierarchy. Particularly useful for buttons inside of scroll views.
/// </summary>
public class ToggleDeselect : Toggle
{
[SerializeField]
private bool _clearStateOnDrag = false;
public bool ClearStateOnDrag
{
get
{
return _clearStateOnDrag;
}
set
{
_clearStateOnDrag = value;
}
}
public void OnBeginDrag(PointerEventData pointerEventData)
{
if (!_clearStateOnDrag)
{
return;
}
InstantClearState();
DoStateTransition(SelectionState.Normal, true);
ExecuteEvents.ExecuteHierarchy(
transform.parent.gameObject,
pointerEventData,
ExecuteEvents.beginDragHandler
);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af559e57b81a946479f0ad978a1a3763
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 86ce14e80f612a041be6385c43fed586
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,317 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using Oculus.Interaction.Surfaces;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.UnityCanvas
{
public class CanvasCylinder : CanvasMesh, ICurvedPlane, ICylinderClipper
{
[Serializable]
public struct MeshGenerationSettings
{
[Delayed]
public float VerticesPerDegree;
[Delayed]
public int MaxHorizontalResolution;
[Delayed]
public int MaxVerticalResolution;
}
public const int MIN_RESOLUTION = 2;
[SerializeField]
[Tooltip("The cylinder used to dictate the position and radius of the mesh.")]
private Cylinder _cylinder;
[SerializeField]
[Tooltip("Determines how the mesh is projected on the cylinder wall. " +
"Vertical results in a left-to-right curvature, Horizontal results in a top-to-bottom curvature.")]
private CylinderOrientation _orientation = CylinderOrientation.Vertical;
[SerializeField]
private MeshGenerationSettings _meshGeneration = new MeshGenerationSettings()
{
VerticesPerDegree = 1.4f,
MaxHorizontalResolution = 128,
MaxVerticalResolution = 32
};
public float Radius => _cylinder.Radius;
public Cylinder Cylinder => _cylinder;
public float ArcDegrees { get; private set; }
public float Rotation { get; private set; }
public float Bottom { get; private set; }
public float Top { get; private set; }
private float CylinderRelativeScale => _cylinder.transform.lossyScale.x / transform.lossyScale.x;
public bool GetCylinderSegment(out CylinderSegment segment)
{
segment = new CylinderSegment(Rotation, ArcDegrees, Bottom, Top);
return _started && isActiveAndEnabled;
}
protected override void Start()
{
this.BeginStart(ref _started, () => base.Start());
this.AssertField(_cylinder, nameof(_cylinder));
this.EndStart(ref _started);
}
#if UNITY_EDITOR
protected virtual void OnValidate()
{
_meshGeneration.MaxHorizontalResolution = Mathf.Max(MIN_RESOLUTION,
_meshGeneration.MaxHorizontalResolution);
_meshGeneration.MaxVerticalResolution = Mathf.Max(MIN_RESOLUTION,
_meshGeneration.MaxVerticalResolution);
_meshGeneration.VerticesPerDegree = Mathf.Max(0, _meshGeneration.VerticesPerDegree);
if (Application.isPlaying && _started)
{
EditorApplication.delayCall += () =>
{
UpdateImposter();
};
}
}
#endif
protected override void UpdateImposter()
{
base.UpdateImposter();
UpdateMeshPosition();
UpdateCurvedPlane();
}
protected override Vector3 MeshInverseTransform(Vector3 localPosition)
{
float angle = Mathf.Atan2(localPosition.x, localPosition.z + Radius);
float x = angle * Radius;
float y = localPosition.y;
return new Vector3(x, y);
}
protected override void GenerateMesh(out List<Vector3> verts,
out List<int> tris,
out List<Vector2> uvs)
{
verts = new List<Vector3>();
tris = new List<int>();
uvs = new List<Vector2>();
Vector2 worldSize = GetWorldSize();
float scaledRadius = Radius * CylinderRelativeScale;
float xPos = worldSize.x * 0.5f;
float xNeg = -xPos;
float yPos = worldSize.y * 0.5f;
float yNeg = -yPos;
Vector2Int GetClampedResolution(float arcMax, float axisMax)
{
int horizontalResolution = Mathf.Max(2,
Mathf.RoundToInt(_meshGeneration.VerticesPerDegree *
Mathf.Rad2Deg * arcMax / scaledRadius));
int verticalResolution =
Mathf.Max(2, Mathf.RoundToInt(horizontalResolution * axisMax / arcMax));
horizontalResolution = Mathf.Clamp(horizontalResolution, 2,
_meshGeneration.MaxHorizontalResolution);
verticalResolution = Mathf.Clamp(verticalResolution, 2,
_meshGeneration.MaxVerticalResolution);
return new Vector2Int(horizontalResolution, verticalResolution);
}
Vector3 GetCurvedPoint(float u, float v)
{
float x = Mathf.Lerp(xNeg, xPos, u);
float y = Mathf.Lerp(yNeg, yPos, v);
float angle;
Vector3 point;
switch (_orientation)
{
default:
case CylinderOrientation.Vertical:
angle = x / scaledRadius;
point.x = Mathf.Sin(angle) * scaledRadius;
point.y = y;
point.z = Mathf.Cos(angle) * scaledRadius - scaledRadius;
break;
case CylinderOrientation.Horizontal:
angle = y / scaledRadius;
point.x = x;
point.y = Mathf.Sin(angle) * scaledRadius;
point.z = Mathf.Cos(angle) * scaledRadius - scaledRadius;
break;
}
return point;
}
Vector2Int resolution;
switch (_orientation)
{
default:
case CylinderOrientation.Vertical:
resolution = GetClampedResolution(xPos, yPos);
break;
case CylinderOrientation.Horizontal:
resolution = GetClampedResolution(yPos, xPos);
break;
}
for (int y = 0; y < resolution.y; y++)
{
for (int x = 0; x < resolution.x; x++)
{
float u = x / (resolution.x - 1.0f);
float v = y / (resolution.y - 1.0f);
verts.Add(GetCurvedPoint(u, v));
uvs.Add(new Vector2(u, v));
}
}
for (int y = 0; y < resolution.y - 1; y++)
{
for (int x = 0; x < resolution.x - 1; x++)
{
int v00 = x + y * resolution.x;
int v10 = v00 + 1;
int v01 = v00 + resolution.x;
int v11 = v00 + 1 + resolution.x;
tris.Add(v00);
tris.Add(v11);
tris.Add(v10);
tris.Add(v00);
tris.Add(v01);
tris.Add(v11);
}
}
}
private void UpdateMeshPosition()
{
Vector3 posInCylinder = _cylinder.transform.InverseTransformPoint(transform.position);
Vector3 localYOffset = new Vector3(0, posInCylinder.y, 0);
Vector3 localCancelY = posInCylinder - localYOffset;
// If canvas position is on cylinder center axis, project forward.
// Otherwise, project canvas onto cylinder wall from center axis.
Vector3 projection = Mathf.Approximately(localCancelY.sqrMagnitude, 0f) ?
Vector3.forward : localCancelY.normalized;
Vector3 localUp;
switch (_orientation)
{
default:
case CylinderOrientation.Vertical:
localUp = Vector3.up;
break;
case CylinderOrientation.Horizontal:
localUp = Vector3.right;
break;
}
transform.position = _cylinder.transform.TransformPoint((projection * _cylinder.Radius) + localYOffset);
transform.rotation = _cylinder.transform.rotation * Quaternion.LookRotation(projection, localUp);
if (_meshCollider != null &&
_meshCollider.transform != transform &&
!transform.IsChildOf(_meshCollider.transform))
{
_meshCollider.transform.position = transform.position;
_meshCollider.transform.rotation = transform.rotation;
_meshCollider.transform.localScale *= transform.lossyScale.x / _meshCollider.transform.lossyScale.x;
}
}
private Vector2 GetWorldSize()
{
Vector2Int resolution = _canvasRenderTexture.GetBaseResolutionToUse();
float width = _canvasRenderTexture.PixelsToUnits(Mathf.RoundToInt(resolution.x));
float height = _canvasRenderTexture.PixelsToUnits(Mathf.RoundToInt(resolution.y));
return new Vector2(width, height) / transform.lossyScale;
}
private void UpdateCurvedPlane()
{
// Get world size in cylinder space
Vector2 cylinderSize = GetWorldSize() / CylinderRelativeScale;
float arcSize, axisSize;
switch (_orientation)
{
default:
case CylinderOrientation.Vertical:
arcSize = cylinderSize.x;
axisSize = cylinderSize.y;
break;
case CylinderOrientation.Horizontal:
arcSize = cylinderSize.y;
axisSize = cylinderSize.x;
break;
}
Vector3 posInCylinder = Cylinder.transform.InverseTransformPoint(transform.position);
Rotation = Mathf.Atan2(posInCylinder.x, posInCylinder.z) * Mathf.Rad2Deg;
ArcDegrees = (arcSize * 0.5f / Radius) * 2f * Mathf.Rad2Deg;
Top = posInCylinder.y + (axisSize * 0.5f);
Bottom = posInCylinder.y - (axisSize * 0.5f);
}
#region Inject
public void InjectAllCanvasCylinder(CanvasRenderTexture canvasRenderTexture,
MeshFilter meshFilter,
Cylinder cylinder,
CylinderOrientation orientation)
{
InjectAllCanvasMesh(canvasRenderTexture, meshFilter);
InjectCylinder(cylinder);
InjectOrientation(orientation);
}
public void InjectCylinder(Cylinder cylinder)
{
_cylinder = cylinder;
}
public void InjectOrientation(CylinderOrientation orientation)
{
_orientation = orientation;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85ef10d17088b3c4cad9a5d13887b157
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,149 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
namespace Oculus.Interaction.UnityCanvas
{
[DisallowMultipleComponent]
public abstract class CanvasMesh : MonoBehaviour
{
[Tooltip("Mesh construction will be driven by this texture.")]
[SerializeField]
protected CanvasRenderTexture _canvasRenderTexture;
[Tooltip("The mesh filter that will be driven.")]
[SerializeField]
protected MeshFilter _meshFilter;
[Tooltip("Optional mesh collider that will be driven.")]
[SerializeField, Optional]
protected MeshCollider _meshCollider = null;
protected bool _started = false;
protected abstract Vector3 MeshInverseTransform(Vector3 localPosition);
protected abstract void GenerateMesh(out List<Vector3> verts, out List<int> tris, out List<Vector2> uvs);
/// <summary>
/// Transform a position in world space relative to the imposter to an associated position relative
/// to the original canvas in world space.
/// </summary>
public Vector3 ImposterToCanvasTransformPoint(Vector3 worldPosition)
{
Vector3 localToImposter =
_meshFilter.transform.InverseTransformPoint(worldPosition);
Vector3 canvasLocalPosition = MeshInverseTransform(localToImposter) /
_canvasRenderTexture.transform.localScale.x;
Vector3 transformedWorldPosition = _canvasRenderTexture.transform.TransformPoint(canvasLocalPosition);
return transformedWorldPosition;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(_meshFilter, nameof(_meshFilter));
this.AssertField(_canvasRenderTexture, nameof(_canvasRenderTexture));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
UpdateImposter();
_canvasRenderTexture.OnUpdateRenderTexture += HandleUpdateRenderTexture;
if (_canvasRenderTexture.Texture != null)
{
HandleUpdateRenderTexture(_canvasRenderTexture.Texture);
}
}
}
protected virtual void OnDisable()
{
if (_started)
{
_canvasRenderTexture.OnUpdateRenderTexture -= HandleUpdateRenderTexture;
}
}
protected virtual void HandleUpdateRenderTexture(Texture texture)
{
UpdateImposter();
}
protected virtual void UpdateImposter()
{
Profiler.BeginSample("InterfaceRenderer.UpdateImposter");
try
{
GenerateMesh(out List<Vector3> verts, out List<int> tris, out List<Vector2> uvs);
Mesh mesh = new Mesh();
mesh.SetVertices(verts);
mesh.SetUVs(0, uvs);
mesh.SetTriangles(tris, 0);
mesh.RecalculateBounds();
mesh.RecalculateNormals();
_meshFilter.mesh = mesh;
if (_meshCollider != null)
{
_meshCollider.sharedMesh = _meshFilter.sharedMesh;
}
}
finally
{
Profiler.EndSample();
}
}
#region Inject
public void InjectAllCanvasMesh(CanvasRenderTexture canvasRenderTexture, MeshFilter meshFilter)
{
InjectCanvasRenderTexture(canvasRenderTexture);
InjectMeshFilter(meshFilter);
}
public void InjectCanvasRenderTexture(CanvasRenderTexture canvasRenderTexture)
{
_canvasRenderTexture = canvasRenderTexture;
}
public void InjectMeshFilter(MeshFilter meshFilter)
{
_meshFilter = meshFilter;
}
public void InjectOptionalMeshCollider(MeshCollider meshCollider)
{
_meshCollider = meshCollider;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e17f00312f4fea429367cb5b305689d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,188 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEngine.Profiling;
namespace Oculus.Interaction.UnityCanvas
{
public class CanvasMeshRenderer : MonoBehaviour
{
private static readonly int MainTexShaderID = Shader.PropertyToID("_MainTex");
[Tooltip("The canvas texture that will be rendered.")]
[SerializeField]
protected CanvasRenderTexture _canvasRenderTexture;
[Tooltip("The mesh renderer that will be driven.")]
[SerializeField]
protected MeshRenderer _meshRenderer;
[Tooltip("Determines the shader used for rendering. " +
"See the documentation for details on these rendering modes.")]
[SerializeField]
protected int _renderingMode = (int)RenderingMode.AlphaCutout;
[Tooltip("Requires MSAA. Provides limited transparency useful for " +
"anti-aliasing soft edges of UI elements.")]
[SerializeField]
private bool _useAlphaToMask = true;
[Tooltip("Select the alpha cutoff used for the cutout rendering.")]
[Range(0, 1)]
[SerializeField]
private float _alphaCutoutThreshold = 0.5f;
private RenderingMode RenderingMode => (RenderingMode)_renderingMode;
protected virtual string GetShaderName()
{
switch (RenderingMode)
{
case RenderingMode.AlphaBlended:
return "Hidden/Imposter_AlphaBlended";
case RenderingMode.AlphaCutout:
if (_useAlphaToMask)
{
return "Hidden/Imposter_AlphaToMask";
}
else
{
return "Hidden/Imposter_AlphaCutout";
}
default:
case RenderingMode.Opaque:
return "Hidden/Imposter_Opaque";
}
}
protected virtual void SetAdditionalProperties(MaterialPropertyBlock block)
{
block.SetFloat("_Cutoff", GetAlphaCutoutThreshold());
}
protected virtual float GetAlphaCutoutThreshold()
{
if (RenderingMode == RenderingMode.AlphaCutout &&
!_useAlphaToMask)
{
return _alphaCutoutThreshold;
}
return 1f;
}
protected Material _material;
protected bool _started;
protected virtual void HandleUpdateRenderTexture(Texture texture)
{
_meshRenderer.material = _material;
var block = new MaterialPropertyBlock();
_meshRenderer.GetPropertyBlock(block);
block.SetTexture(MainTexShaderID, texture);
SetAdditionalProperties(block);
_meshRenderer.SetPropertyBlock(block);
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(_meshRenderer, nameof(_meshRenderer));
this.AssertField(_canvasRenderTexture, nameof(_canvasRenderTexture));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Profiler.BeginSample("InterfaceRenderer.UpdateMaterial");
try
{
_material = new Material(Shader.Find(GetShaderName()));
}
finally
{
Profiler.EndSample();
}
_canvasRenderTexture.OnUpdateRenderTexture += HandleUpdateRenderTexture;
if (_canvasRenderTexture.Texture != null)
{
HandleUpdateRenderTexture(_canvasRenderTexture.Texture);
}
}
}
protected virtual void OnDisable()
{
if (_started)
{
if (_material != null)
{
Destroy(_material);
_material = null;
}
_canvasRenderTexture.OnUpdateRenderTexture -= HandleUpdateRenderTexture;
}
}
public static partial class Properties
{
public static readonly string RenderingMode = nameof(_renderingMode);
public static readonly string UseAlphaToMask = nameof(_useAlphaToMask);
public static readonly string AlphaCutoutThreshold = nameof(_alphaCutoutThreshold);
}
#region Inject
public void InjectAllCanvasMeshRenderer(CanvasRenderTexture canvasRenderTexture,
MeshRenderer meshRenderer)
{
InjectCanvasRenderTexture(canvasRenderTexture);
InjectMeshRenderer(meshRenderer);
}
public void InjectCanvasRenderTexture(CanvasRenderTexture canvasRenderTexture)
{
_canvasRenderTexture = canvasRenderTexture;
}
public void InjectMeshRenderer(MeshRenderer meshRenderer)
{
_meshRenderer = meshRenderer;
}
public void InjectOptionalRenderingMode(RenderingMode renderingMode)
{
_renderingMode = (int)renderingMode;
}
public void InjectOptionalAlphaCutoutThreshold(float alphaCutoutThreshold)
{
_alphaCutoutThreshold = alphaCutoutThreshold;
}
public void InjectOptionalUseAlphaToMask(bool useAlphaToMask)
{
_useAlphaToMask = useAlphaToMask;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 49d3bbd18ed10f44ba1e0b24dbfee754
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using UnityEngine;
namespace Oculus.Interaction.UnityCanvas
{
public class CanvasRect : CanvasMesh
{
protected override Vector3 MeshInverseTransform(Vector3 localPosition)
{
return localPosition;
}
protected override void GenerateMesh(out List<Vector3> verts,
out List<int> tris,
out List<Vector2> uvs)
{
verts = new List<Vector3>();
tris = new List<int>();
uvs = new List<Vector2>();
var resolution = _canvasRenderTexture.GetBaseResolutionToUse();
Vector2 worldSize = new Vector2(
_canvasRenderTexture.PixelsToUnits(Mathf.RoundToInt(resolution.x)),
_canvasRenderTexture.PixelsToUnits(Mathf.RoundToInt(resolution.y))
) / transform.lossyScale;
float xPos = worldSize.x * 0.5f;
float xNeg = -xPos;
float yPos = worldSize.y * 0.5f;
float yNeg = -yPos;
verts.Add(new Vector3(xNeg, yNeg, 0));
verts.Add(new Vector3(xNeg, yPos, 0));
verts.Add(new Vector3(xPos, yPos, 0));
verts.Add(new Vector3(xPos, yNeg, 0));
tris.Add(0);
tris.Add(1);
tris.Add(2);
tris.Add(0);
tris.Add(2);
tris.Add(3);
uvs.Add(new Vector2(0, 0));
uvs.Add(new Vector2(0, 1));
uvs.Add(new Vector2(1, 1));
uvs.Add(new Vector2(1, 0));
}
#region Inject
public void InjectAllCanvasRect(CanvasRenderTexture canvasRenderTexture, MeshFilter meshFilter)
{
InjectAllCanvasMesh(canvasRenderTexture, meshFilter);
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2025423b67857c349bf8cf435762b5cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,384 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using UnityEngine;
using UnityEngine.Profiling;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Oculus.Interaction.UnityCanvas
{
[DisallowMultipleComponent]
public class CanvasRenderTexture : MonoBehaviour
{
private class TransformChangeListener : MonoBehaviour
{
public event Action WhenRectTransformDimensionsChanged = delegate { };
private void OnRectTransformDimensionsChange()
{
WhenRectTransformDimensionsChanged();
}
}
public enum DriveMode
{
Auto,
Manual
}
public const int DEFAULT_UI_LAYERMASK = 1 << 5; //Hardcoded as the UI layer in Unity.
private static readonly Vector2Int DEFAULT_TEXTURE_RES = new Vector2Int(128, 128);
[Tooltip("The Unity canvas that will be rendered.")]
[SerializeField]
private Canvas _canvas;
[Tooltip("If you need extra resolution, you can use this as a whole-integer multiplier " +
"of the final resolution used to render the texture.")]
[Range(1, 3)]
[Delayed]
[SerializeField]
private int _renderScale = 1;
[Tooltip("If set to auto, texture dimensions will take the size of the attached " +
"RectTransform into consideration, in addition to the configured pixel-per-unit ratio.")]
[SerializeField]
private DriveMode _dimensionsDriveMode = DriveMode.Auto;
[Tooltip("The exact pixel resolution of the texture used for interface rendering.")]
[Delayed]
[SerializeField]
private Vector2Int _resolution = DEFAULT_TEXTURE_RES;
[Tooltip("Whether or not mip-maps should be auto-generated for the texture. " +
"Can help aliasing if the texture can be " +
"viewed from many difference distances.")]
[SerializeField]
private bool _generateMipMaps = false;
[Tooltip("Pixels per unit ratio used to drive the texture dimensions.")]
[SerializeField]
private int _pixelsPerUnit = 100;
[Header("Rendering Settings")]
[Tooltip("The layers to render when the rendering texture is created. " +
"All child renderers should be part of this mask.")]
[SerializeField]
private LayerMask _renderingLayers = DEFAULT_UI_LAYERMASK;
public LayerMask RenderingLayers => _renderingLayers;
public Action<Texture> OnUpdateRenderTexture = delegate { };
public int RenderScale
{
get
{
return _renderScale;
}
set
{
if (_renderScale < 1 || _renderScale > 3)
{
throw new ArgumentException($"Render scale must be between 1 and 3, but was {value}");
}
if (_renderScale == value)
{
return;
}
_renderScale = value;
if (isActiveAndEnabled && Application.isPlaying)
{
UpdateCamera();
}
}
}
public Camera OverlayCamera => _camera;
public Texture Texture => _tex;
private TransformChangeListener _listener;
private RenderTexture _tex;
private Camera _camera;
protected bool _started = false;
public Vector2Int CalcAutoResolution()
{
if (_canvas == null)
{
return DEFAULT_TEXTURE_RES;
}
var rectTransform = _canvas.GetComponent<RectTransform>();
if (rectTransform == null)
{
return DEFAULT_TEXTURE_RES;
}
Vector2 size = rectTransform.sizeDelta;
size.x *= rectTransform.lossyScale.x;
size.y *= rectTransform.lossyScale.y;
int x = Mathf.RoundToInt(UnitsToPixels(size.x));
int y = Mathf.RoundToInt(UnitsToPixels(size.y));
return new Vector2Int(Mathf.Max(x, 1), Mathf.Max(y, 1));
}
public Vector2Int GetBaseResolutionToUse()
{
if (_dimensionsDriveMode == DriveMode.Auto)
{
return CalcAutoResolution();
}
else
{
return _resolution;
}
}
public Vector2Int GetScaledResolutionToUse()
{
Vector2 resolution = GetBaseResolutionToUse();
return Vector2Int.RoundToInt(resolution * _renderScale);
}
public float PixelsToUnits(float pixels)
{
return (1f / _pixelsPerUnit) * pixels;
}
public float UnitsToPixels(float units)
{
return _pixelsPerUnit * units;
}
#if UNITY_EDITOR
protected void OnValidate()
{
if (Application.isPlaying && _started)
{
EditorApplication.delayCall += () =>
{
UpdateCamera();
};
}
}
#endif
protected void Start()
{
this.BeginStart(ref _started);
this.AssertField(_canvas, nameof(_canvas));
this.EndStart(ref _started);
}
protected void OnEnable()
{
if (_started)
{
if (_listener == null)
{
_listener = _canvas.gameObject.AddComponent<TransformChangeListener>();
}
_listener.WhenRectTransformDimensionsChanged += WhenCanvasRectTransformDimensionsChanged;
UpdateCamera();
}
}
private void WhenCanvasRectTransformDimensionsChanged()
{
UpdateCamera();
}
protected void OnDisable()
{
if (_started)
{
if (_camera?.gameObject != null)
{
Destroy(_camera.gameObject);
}
if (_tex != null)
{
DestroyImmediate(_tex);
}
if (_listener != null)
{
_listener.WhenRectTransformDimensionsChanged -= WhenCanvasRectTransformDimensionsChanged;
}
}
}
protected void UpdateCamera()
{
if (!Application.isPlaying || !_started)
{
return;
}
Profiler.BeginSample("InterfaceRenderer.UpdateCamera");
try
{
if (_camera == null)
{
GameObject cameraObj = CreateChildObject("__Camera");
_camera = cameraObj.AddComponent<Camera>();
_camera.orthographic = true;
_camera.nearClipPlane = -0.1f;
_camera.farClipPlane = 0.1f;
_camera.backgroundColor = new Color(0, 0, 0, 0);
_camera.clearFlags = CameraClearFlags.SolidColor;
}
UpdateRenderTexture();
UpdateOrthoSize();
UpdateCameraCullingMask();
}
finally
{
Profiler.EndSample();
}
}
protected void UpdateRenderTexture()
{
Profiler.BeginSample("InterfaceRenderer.UpdateRenderTexture");
try
{
Vector2Int resolutionToUse = GetScaledResolutionToUse();
if (_tex == null ||
_tex.width != resolutionToUse.x ||
_tex.height != resolutionToUse.y ||
_tex.autoGenerateMips != _generateMipMaps)
{
if (_tex != null)
{
_camera.targetTexture = null;
DestroyImmediate(_tex);
}
_tex = new RenderTexture(resolutionToUse.x, resolutionToUse.y, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
_tex.filterMode = FilterMode.Bilinear;
_tex.autoGenerateMips = _generateMipMaps;
_camera.targetTexture = _tex;
OnUpdateRenderTexture(_tex);
}
}
finally
{
Profiler.EndSample();
}
}
private void UpdateOrthoSize()
{
if (_camera != null)
{
_camera.orthographicSize = PixelsToUnits(GetBaseResolutionToUse().y) * 0.5f;
}
}
private void UpdateCameraCullingMask()
{
if (_camera != null)
{
_camera.cullingMask = _renderingLayers.value;
}
}
protected GameObject CreateChildObject(string name)
{
GameObject obj = new GameObject(name);
obj.transform.SetParent(_canvas.transform);
obj.transform.localPosition = Vector3.zero;
obj.transform.localRotation = Quaternion.identity;
obj.transform.localScale = Vector3.one;
return obj;
}
public static class Properties
{
public static readonly string DimensionDriveMode = nameof(_dimensionsDriveMode);
public static readonly string Resolution = nameof(_resolution);
public static readonly string RenderScale = nameof(_renderScale);
public static readonly string PixelsPerUnit = nameof(_pixelsPerUnit);
public static readonly string RenderLayers = nameof(_renderingLayers);
public static readonly string GenerateMipMaps = nameof(_generateMipMaps);
public static readonly string Canvas = nameof(_canvas);
}
#region Inject
public void InjectAllCanvasRenderTexture(Canvas canvas,
int pixelsPerUnit,
int renderScale,
LayerMask renderingLayers,
bool generateMipMaps)
{
InjectCanvas(canvas);
InjectPixelsPerUnit(pixelsPerUnit);
InjectRenderScale(renderScale);
InjectRenderingLayers(renderingLayers);
InjectGenerateMipMaps(generateMipMaps);
}
public void InjectCanvas(Canvas canvas)
{
_canvas = canvas;
}
public void InjectPixelsPerUnit(int pixelsPerUnit)
{
_pixelsPerUnit = pixelsPerUnit;
}
public void InjectRenderScale(int renderScale)
{
_renderScale = renderScale;
}
public void InjectRenderingLayers(LayerMask renderingLayers)
{
_renderingLayers = renderingLayers;
}
public void InjectGenerateMipMaps(bool generateMipMaps)
{
_generateMipMaps = generateMipMaps;
}
#endregion
}
}

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: b7ecff74e52843a41ab3a441ac81379e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _defaultUIMaterial: {fileID: 2100000, guid: bc9f80a2ae0e0a24d870176e48ab1b93,
type: 2}
- _imposterMaterial: {fileID: 2100000, guid: 5c093e4058df12042a75bcb967ca1554, type: 2}
- _depthQuadMaterial: {fileID: 2100000, guid: c7cda63c3ebb50847950fc3a925d784f,
type: 2}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
namespace Oculus.Interaction.UnityCanvas
{
public enum RenderingMode
{
[InspectorName("Alpha-Blended")]
AlphaBlended = 0,
[InspectorName("Alpha-Cutout")]
AlphaCutout,
[InspectorName("Opaque")]
Opaque,
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e5de7ab187e8dc47a0f654ad1e1107a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
namespace Oculus.Interaction.UnityCanvas
{
/// <summary>
/// Dropdowns menus in Unity are automatically set to sorting order 30000, which
/// does not play nicely with world space UIs.
/// Meant to be used in conjunction with an EventTrigger on a given Dropdown, this component
/// can be used to set a different sorting order on this and any child canvas.
/// </summary>
public class UpdateCanvasSortingOrder : MonoBehaviour
{
public void SetCanvasSortingOrder(int sortingOrder)
{
Canvas[] canvases = transform.parent.gameObject.GetComponentsInChildren<Canvas>();
if (canvases == null) return;
foreach (Canvas canvas in canvases)
{
canvas.sortingOrder = sortingOrder;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f40996cdf2361b8478af26d6e2630d42
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Oculus.Interaction
{
public static class UnityInfo
{
public static bool IsEditor()
{
#if UNITY_EDITOR
return true;
#else
return false;
#endif
}
public static bool Version_2020_3_Or_Newer()
{
#if UNITY_2020_3_OR_NEWER
return true;
#else
return false;
#endif
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 59709ed6d58f88541a420b6ef3165513
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: