Move third party assets to ThirdParty folder

This commit is contained in:
2024-08-08 11:26:28 +02:00
parent 386f303057
commit bd91af6f98
10340 changed files with 100 additions and 175 deletions

View File

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

View File

@@ -0,0 +1,45 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ArmoredDoor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.CameraUtils;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Doors
{
/// <summary>
/// Component to model an automatic door that can not be opened from the outside unless
/// <see cref="AutomaticDoor.OpenDoor" /> is called explicitly.
/// </summary>
public class ArmoredDoor : AutomaticDoor
{
#region Protected Overrides AutomaticDoor
/// <summary>
/// Gets whether automatic opening is allowed. Only from the inside and the if avatar is not peeking through geometry.
/// </summary>
protected override bool IsOpeningAllowed => IsAvatarInside(UxrAvatar.LocalAvatar) && !UxrCameraWallFade.IsAvatarPeekingThroughGeometry(UxrAvatar.LocalAvatar);
#endregion
#region Private Methods
/// <summary>
/// Checks whether the avatar is on the back side of the door.
/// </summary>
/// <param name="avatar">Avatar to check</param>
/// <returns>
/// Whether the avatar is on the back side of the door. This means it is behind the plane pointed by the floor
/// center and looking towards the negative Z.
/// </returns>
private bool IsAvatarInside(UxrAvatar avatar)
{
Plane doorPlane = new Plane(FloorCenter.forward, FloorCenter.position);
return !doorPlane.GetSide(avatar.CameraPosition);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,179 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AutomaticDoor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Linq;
using UltimateXR.Animation.Interpolation;
using UltimateXR.Audio;
using UltimateXR.Avatar;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Doors
{
/// <summary>
/// Component to model de behavior of a door that opens automatically when the user gets near and closes
/// when the user moves away from it.
/// </summary>
public class AutomaticDoor : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Transform _floorCenter;
[SerializeField] private Transform _leftDoor;
[SerializeField] private Transform _rightDoor;
[SerializeField] private Vector3 _leftOpenLocalOffset;
[SerializeField] private Vector3 _rightOpenLocalOffset;
[SerializeField] private float _openDelaySeconds;
[SerializeField] private float _openDurationSeconds = 0.8f;
[SerializeField] private float _openDistance = 1.5f;
[SerializeField] private float _closeDistance = 2.0f;
[SerializeField] private UxrEasing _openEasing = UxrEasing.EaseOutCubic;
[SerializeField] private UxrEasing _closeEasing = UxrEasing.EaseInCubic;
[SerializeField] private UxrAudioSample _audioOpen;
[SerializeField] private UxrAudioSample _audioClose;
#endregion
#region Public Types & Data
/// <summary>
/// Gets a value from 0.0 (completely closed) to 1.0 (completely open) telling how open the door currently is.
/// </summary>
public float OpenValue { get; private set; }
/// <summary>
/// Gets if the door is open or opening.
/// </summary>
public bool IsOpen { get; private set; }
#endregion
#region Public Methods
/// <summary>
/// Opens the door. This can be used in child implementations where opening can be disallowed under certain
/// conditions. See <see cref="ArmoredDoor" /> for an example.
/// </summary>
/// <param name="playSound">Whether to play the open sound</param>
public void OpenDoor(bool playSound)
{
BeginSync();
IsOpen = true;
if (playSound)
{
_audioOpen.Play(FloorCenter.position);
}
EndSyncMethod(new object[] { playSound });
}
/// <summary>
/// Closes the door.
/// </summary>
/// <param name="playSound">Whether to play the close sound</param>
public void CloseDoor(bool playSound)
{
BeginSync();
// Over closing distance and door completely open: close door
IsOpen = false;
_openDelayTimer = 0.0f;
if (playSound)
{
_audioClose.Play(FloorCenter.position);
}
EndSyncMethod(new object[] { playSound });
}
#endregion
#region Unity
/// <summary>
/// Stores initial state.
/// </summary>
protected override void Awake()
{
base.Awake();
_leftStartLocalPosition = _leftDoor.localPosition;
_rightStartLocalPosition = _rightDoor.localPosition;
}
/// <summary>
/// Updates the door.
/// </summary>
private void Update()
{
if (UxrAvatar.LocalAvatar == null)
{
return;
}
// Check distance to door
UxrAvatar closestAvatar = UxrAvatar.EnabledComponents.OrderBy(a => Vector3.Distance(a.CameraFloorPosition, FloorCenter.position)).FirstOrDefault();
if (closestAvatar == UxrAvatar.LocalAvatar)
{
// The closest avatar will determine the door state.
float closestAvatarDistance = Vector3.Distance(closestAvatar.CameraFloorPosition, FloorCenter.position);
if (closestAvatarDistance < _openDistance && Mathf.Approximately(OpenValue, 0.0f))
{
_openDelayTimer += Time.deltaTime;
if (_openDelayTimer > _openDelaySeconds && IsOpeningAllowed)
{
// Within opening distance, door completely closed and opening allowed: open door
OpenDoor(true);
}
}
else if (closestAvatarDistance > _closeDistance && Mathf.Approximately(OpenValue, 1.0f))
{
CloseDoor(true);
}
}
// Update timer and perform interpolation
OpenValue = Mathf.Clamp01(OpenValue + Time.deltaTime * (1.0f / _openDurationSeconds) * (IsOpen ? 1.0f : -1.0f));
float t = UxrInterpolator.GetInterpolationFactor(OpenValue, IsOpen ? _openEasing : _closeEasing);
_leftDoor.transform.localPosition = Vector3.Lerp(_leftStartLocalPosition, _leftStartLocalPosition + _leftOpenLocalOffset, t);
_rightDoor.transform.localPosition = Vector3.Lerp(_rightStartLocalPosition, _rightStartLocalPosition + _rightOpenLocalOffset, t);
}
#endregion
#region Protected Types & Data
/// <summary>
/// Gets if opening is allowed. Can be used by child implementations to disallow opening under certain conditions. See
/// <see cref="ArmoredDoor" /> for an example.
/// </summary>
protected virtual bool IsOpeningAllowed => true;
/// <summary>
/// Gets the door center at floor level.
/// </summary>
protected Transform FloorCenter => _floorCenter != null ? _floorCenter : transform;
#endregion
#region Private Types & Data
private float _openDelayTimer;
private Vector3 _leftStartLocalPosition;
private Vector3 _rightStartLocalPosition;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,347 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="HandScanner.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Animation.Interpolation;
using UltimateXR.Animation.Materials;
using UltimateXR.Audio;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity.Math;
using UltimateXR.Extensions.Unity.Render;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Doors
{
/// <summary>
/// Component that handles the hand scanning required to open an <see cref="ArmoredDoor" />.
/// </summary>
public class HandScanner : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private ArmoredDoor _armoredDoor;
[SerializeField] private Renderer _validLight;
[SerializeField] private Renderer _invalidLight;
[SerializeField] private Renderer _scannerBeam;
[SerializeField] private Vector3 _scannerBeamTopLocalPos;
[SerializeField] private Vector3 _scannerBeamBottomLocalPos;
[SerializeField] private int _beamCount = 5;
[SerializeField] [Range(0.0f, 1.0f)] private float _beamTrailDelay = 0.1f;
[SerializeField] private UxrEasing _beamEeasing = UxrEasing.EaseInOutQuint;
[SerializeField] private Vector3 _beamMaxScale = Vector3.one;
[SerializeField] private Renderer _handRendererLeft;
[SerializeField] private Renderer _handRendererRight;
[SerializeField] private UxrHandSide _defaultHandSide = UxrHandSide.Right;
[SerializeField] private BoxCollider _handBoxValidPos;
[SerializeField] private float _scanSeconds = 1.5f;
[SerializeField] private float _resultSeconds = 2.0f;
[SerializeField] private UxrAudioSample _audioScan;
[SerializeField] private UxrAudioSample _audioError;
[SerializeField] private UxrAudioSample _audioOk;
#endregion
#region Public Types & Data
/// <summary>
/// Event called right after a hand was scanned. Parameters are the avatar that was scanned and if the scan granted
/// access.
/// </summary>
public event Action<UxrAvatar, UxrHandSide, bool> HandScanned;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_colorValid = _validLight.sharedMaterial.GetColor(UxrConstants.Shaders.StandardColorVarName);
_colorInvalid = _invalidLight.sharedMaterial.GetColor(UxrConstants.Shaders.StandardColorVarName);
_handSide = _defaultHandSide;
_beamScale = _scannerBeam.transform.localScale;
_beams.Add(_scannerBeam);
for (int i = 0; i < _beamCount - 1; ++i)
{
_beams.Add(Instantiate(_scannerBeam.gameObject, _scannerBeam.transform.position, _scannerBeam.transform.rotation, _scannerBeam.transform.parent).GetComponent<Renderer>());
}
}
/// <summary>
/// Sets the default scanning state.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_scanReady = true;
_validLight.enabled = false;
_invalidLight.enabled = false;
_handRendererLeft.enabled = _defaultHandSide == UxrHandSide.Left;
_handRendererRight.enabled = _defaultHandSide == UxrHandSide.Right;
_handRendererLeft.material.color = ColorExt.ColorAlpha(_handRendererLeft.material.color, _defaultHandSide == UxrHandSide.Left ? 1.0f : 0.0f);
_handRendererRight.material.color = ColorExt.ColorAlpha(_handRendererRight.material.color, _defaultHandSide == UxrHandSide.Right ? 1.0f : 0.0f);
EnableBeams(false);
}
/// <summary>
/// Disables the component.
/// </summary>
protected override void OnDisable()
{
base.OnEnable();
_scanReady = true;
_validLight.enabled = false;
_invalidLight.enabled = false;
EnableBeams(false);
}
/// <summary>
/// Updates the component. Performs the scanning process.
/// </summary>
private void Update()
{
if (UxrAvatar.LocalAvatar == null || !_scanReady)
{
return;
}
// Update scanning & beam
if (_scanTimer < 0.0f)
{
if (_armoredDoor != null && _armoredDoor.IsOpen)
{
// If we are controlling an armored door and it is already open, ignore the hands.
}
else
{
// Waiting for hand to be scanned. Look for hand:
if (UxrAvatar.LocalAvatar.GetHandBone(UxrHandSide.Left).position.IsInsideBox(_handBoxValidPos))
{
StartScan(UxrAvatar.LocalAvatar, UxrHandSide.Left);
}
else if (UxrAvatar.LocalAvatar.GetHandBone(UxrHandSide.Right).position.IsInsideBox(_handBoxValidPos))
{
StartScan(UxrAvatar.LocalAvatar, UxrHandSide.Right);
}
}
}
else
{
// Hand is scanning
_scanTimer += Time.deltaTime;
EnableBeams(true);
for (int i = 0; i < _beams.Count; ++i)
{
float beamStartTime = i / (_beams.Count == 1 ? 1.0f : _beams.Count - 1.0f) * _beamTrailDelay * _scanSeconds;
float beamDuration = _scanSeconds - _beamTrailDelay;
float t = Mathf.Clamp01((_scanTimer - beamStartTime) / beamDuration);
float tScale = 1.0f - Mathf.Abs(t - 0.5f) * 2.0f;
_beams[i].transform.localPosition = Vector3.Lerp(_scannerBeamTopLocalPos, _scannerBeamBottomLocalPos, UxrInterpolator.GetInterpolationFactor(t, _beamEeasing));
_beams[i].transform.localScale = Vector3.Lerp(_beamScale, _beamMaxScale, Mathf.Pow(tScale, 8.0f));
}
// Check for conditions
if (_avatarScanning == UxrAvatar.LocalAvatar)
{
if (UxrAvatar.LocalAvatar.GetHandBone(_handSide).position.IsInsideBox(_handBoxValidPos))
{
if (!UxrAvatar.LocalAvatar.GetHandBone(UxrUtils.GetOppositeSide(_handSide)).position.IsInsideBox(_handBoxValidPos))
{
// Keep scanning
if (_scanTimer > _scanSeconds)
{
ProcessScanResult(UxrAvatar.LocalAvatar, _handSide, true);
if (_armoredDoor != null)
{
_armoredDoor.OpenDoor(false);
}
}
}
else
{
// Opposite hand got in. Aborting.
ProcessScanResult(UxrAvatar.LocalAvatar, _handSide, false);
}
}
else
{
// Scanning hand got out. Aborting.
ProcessScanResult(UxrAvatar.LocalAvatar, _handSide, false);
}
}
}
// Update scan side
if (_handSide == UxrHandSide.Left)
{
if (_handRendererRight.enabled)
{
float rightAlpha = _handRendererRight.material.color.a - Time.deltaTime * HandAlphaSwitchSpeed;
_handRendererRight.material.color = ColorExt.ColorAlpha(_handRendererRight.material.color, Mathf.Clamp01(rightAlpha));
if (rightAlpha < 0.0f)
{
_handRendererRight.enabled = false;
}
}
if (!_handRendererLeft.enabled)
{
_handRendererLeft.enabled = true;
}
_handRendererLeft.material.color = ColorExt.ColorAlpha(_handRendererLeft.material.color, Mathf.Clamp01(_handRendererLeft.material.color.a + Time.deltaTime * HandAlphaSwitchSpeed));
}
else
{
if (_handRendererLeft.enabled)
{
float leftAlpha = _handRendererLeft.material.color.a - Time.deltaTime * HandAlphaSwitchSpeed;
_handRendererLeft.material.color = ColorExt.ColorAlpha(_handRendererLeft.material.color, Mathf.Clamp01(leftAlpha));
if (leftAlpha < 0.0f)
{
_handRendererLeft.enabled = false;
}
}
if (!_handRendererRight.enabled)
{
_handRendererRight.enabled = true;
}
_handRendererRight.material.color = ColorExt.ColorAlpha(_handRendererRight.material.color, Mathf.Clamp01(_handRendererRight.material.color.a + Time.deltaTime * HandAlphaSwitchSpeed));
}
}
#endregion
#region Private Methods
/// <summary>
/// Starts scanning a hand.
/// </summary>
/// <param name="avatarScanning">The avatar that started scanning</param>
/// <param name="handSide">The side of the hand being scanned</param>
private void StartScan(UxrAvatar avatarScanning, UxrHandSide handSide)
{
BeginSync();
_scanTimer = 0.0f;
_handSide = handSide;
_avatarScanning = avatarScanning;
_audioScan.Play(transform.position);
EndSyncMethod(new object[] { avatarScanning, handSide });
}
/// <summary>
/// Enables/disables the scanning beams.
/// </summary>
/// <param name="enable">Whether the beams should be enabled</param>
private void EnableBeams(bool enable)
{
_beams.ForEach(r => r.enabled = enable);
}
/// <summary>
/// Processes a scan result.
/// </summary>
/// <param name="avatar">Avatar that was scanned</param>
/// <param name="handSide">Which hand was scanned</param>
/// <param name="isValid">Whether the access was granted</param>
private void ProcessScanResult(UxrAvatar avatar, UxrHandSide handSide, bool isValid)
{
BeginSync();
EnableBeams(false);
_scanReady = false;
_scanTimer = -1.0f;
if (isValid)
{
_audioOk.Play(transform.position);
_validLight.enabled = true;
UxrAnimatedMaterial.AnimateBlinkColor(_validLight.gameObject,
UxrConstants.Shaders.StandardColorVarName,
_colorValid.WithAlpha(0.0f),
_colorValid,
UxrAnimatedMaterial.DefaultBlinkFrequency,
_resultSeconds,
UxrMaterialMode.InstanceOnly,
() =>
{
_scanReady = true;
_validLight.enabled = false;
});
}
else
{
_audioError.Play(transform.position);
_invalidLight.enabled = true;
UxrAnimatedMaterial.AnimateBlinkColor(_invalidLight.gameObject,
UxrConstants.Shaders.StandardColorVarName,
_colorInvalid.WithAlpha(0.0f),
_colorInvalid,
UxrAnimatedMaterial.DefaultBlinkFrequency,
_resultSeconds,
UxrMaterialMode.InstanceOnly,
() =>
{
_scanReady = true;
_invalidLight.enabled = false;
});
}
HandScanned?.Invoke(avatar, handSide, isValid);
EndSyncMethod(new object[] { avatar, handSide, isValid });
}
#endregion
#region Private Types & Data
/// <summary>
/// Controls how fast the hand image will switch from one side to the other when a different hand than the currently
/// shown is placed on the scanner.
/// </summary>
private const float HandAlphaSwitchSpeed = 4.0f;
private readonly List<Renderer> _beams = new List<Renderer>();
private bool _scanReady = true;
private float _scanTimer = -1.0f;
private Vector3 _beamScale;
private UxrAvatar _avatarScanning;
private UxrHandSide _handSide;
private Color _colorValid;
private Color _colorInvalid;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,186 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GlobalLogic.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.CameraUtils;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Devices.Keyboard;
using UltimateXR.Examples.FullScene.Doors;
using UltimateXR.Extensions.Unity;
using UltimateXR.Extensions.Unity.Math;
using UltimateXR.Locomotion;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace UltimateXR.Examples.FullScene
{
public class GlobalLogic : UxrComponent
{
#region Inspector Properties/Serialized Fields
[Header("Spawn positions")] [SerializeField] private Transform _spawnMain;
[SerializeField] private Transform _spawnLab;
[SerializeField] private Transform _spawnControllers;
[SerializeField] private Transform _spawnShootingRange;
[Header("Location volumes")] [SerializeField] private BoxCollider _boxSpawnRoomMirror;
[SerializeField] private BoxCollider _boxSpawnRoomDoor;
[SerializeField] private BoxCollider _boxCentralRoom;
[SerializeField] private BoxCollider _boxLabRoom;
[SerializeField] private BoxCollider _boxControllerRoom;
[SerializeField] private BoxCollider _boxShootingRange;
[Header("Relevant elements")] [SerializeField] private GameObject _rootUnrestrictedArea;
[SerializeField] private GameObject _rootRestrictedArea;
[SerializeField] private UxrComponent _mirrorComponent;
[SerializeField] private GameObject _controllerRoomElements;
[SerializeField] private GameObject _rootLabElements;
[SerializeField] private ArmoredDoor _armoredDoor;
#endregion
#region Unity
/// <summary>
/// Subscribes to avatar events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrAvatar.LocalAvatarStarted += UxrAvatar_LocalAvatarStarted;
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
}
/// <summary>
/// Unsubscribes from avatar events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrAvatar.LocalAvatarStarted -= UxrAvatar_LocalAvatarStarted;
UxrAvatar.GlobalAvatarMoved -= UxrAvatar_GlobalAvatarMoved;
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
/// <summary>
/// Handles some keyboard shortcuts to reset, quit or quick spawn to different places.
/// </summary>
private void Update()
{
if (UxrKeyboardInput.GetPressDown(UxrKey.Enter))
{
SceneManager.LoadScene(0);
}
else if (UxrKeyboardInput.GetPressDown(UxrKey.Q))
{
Application.Quit();
}
if (UxrKeyboardInput.GetPressDown(UxrKey.Digit1))
{
UxrManager.Instance.TeleportLocalAvatar(_spawnMain.position, _spawnMain.rotation, UxrTranslationType.Fade);
}
else if (UxrKeyboardInput.GetPressDown(UxrKey.Digit2))
{
UxrManager.Instance.TeleportLocalAvatar(_spawnLab.position, _spawnLab.rotation, UxrTranslationType.Fade);
}
else if (UxrKeyboardInput.GetPressDown(UxrKey.Digit3))
{
UxrManager.Instance.TeleportLocalAvatar(_spawnControllers.position, _spawnControllers.rotation, UxrTranslationType.Fade);
}
else if (UxrKeyboardInput.GetPressDown(UxrKey.Digit4))
{
UxrManager.Instance.TeleportLocalAvatar(_spawnShootingRange.position, _spawnShootingRange.rotation, UxrTranslationType.Fade);
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when the local avatar called its Start(). Moves the avatar to the spawn point and initializes the visible
/// elements.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void UxrAvatar_LocalAvatarStarted(object sender, UxrAvatarStartedEventArgs e)
{
UxrManager.Instance.MoveAvatarTo(UxrAvatar.LocalAvatar, _spawnMain);
UpdateVisibility();
}
/// <summary>
/// Called when the avatar moved/teleported. We use it to enable/disable objects based on potential visibility.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event parameters</param>
private void UxrAvatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e)
{
if (ReferenceEquals(sender, UxrAvatar.LocalAvatar))
{
UpdateVisibility();
}
}
/// <summary>
/// Called each frame after all avatars have been updated.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
if (UxrAvatar.LocalAvatar == null || UxrCameraWallFade.IsAvatarPeekingThroughGeometry(UxrAvatar.LocalAvatar))
{
return;
}
_rootRestrictedArea.CheckSetActive(UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxShootingRange) || _armoredDoor.OpenValue > 0.0f);
_rootUnrestrictedArea.CheckSetActive(!UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxShootingRange) || _armoredDoor.OpenValue > 0.0f);
}
#endregion
#region Private Methods
/// <summary>
/// Updates the visible elements based on the current avatar position.
/// </summary>
private void UpdateVisibility()
{
if (UxrAvatar.LocalAvatar == null || UxrCameraWallFade.IsAvatarPeekingThroughGeometry(UxrAvatar.LocalAvatar))
{
return;
}
_mirrorComponent.CheckSetEnabled(UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxSpawnRoomMirror));
_rootRestrictedArea.CheckSetActive(UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxShootingRange) || _armoredDoor.OpenValue > 0.0f);
_rootUnrestrictedArea.CheckSetActive(!UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxShootingRange) || _armoredDoor.OpenValue > 0.0f);
if (UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxSpawnRoomMirror))
{
_controllerRoomElements.CheckSetActive(false);
_rootLabElements.CheckSetActive(false);
}
else if (UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxSpawnRoomDoor))
{
_controllerRoomElements.CheckSetActive(false);
_rootLabElements.CheckSetActive(true);
}
else if (UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxCentralRoom) || UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxLabRoom) || UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxControllerRoom))
{
_controllerRoomElements.CheckSetActive(true);
_rootLabElements.CheckSetActive(true);
}
else if (UxrAvatar.LocalAvatar.CameraPosition.IsInsideBox(_boxShootingRange))
{
_controllerRoomElements.CheckSetActive(false);
_rootLabElements.CheckSetActive(false);
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ad8f739b413ab794aa3cc730f7546754
timeCreated: 1539279150
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: fb1671cf87acc9d4294eb168f5985392
folderAsset: yes
timeCreated: 1530530963
licenseType: Pro
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,111 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Battery.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Manipulation;
using UltimateXR.Manipulation.Helpers;
namespace UltimateXR.Examples.FullScene.Lab
{
/// <summary>
/// Component that adds the smooth slide-in/slide-out effects to the grabbable battery.
/// </summary>
public class Battery : UxrAutoSlideInObject
{
#region Unity
/// <summary>
/// Subscribes to the avatars updated event so that the manipulation logic is done after UltimateXR's manipulation
/// logic has been updated.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
}
/// <summary>
/// Unsubscribes from the avatars updated event.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called after UltimateXR has done all the frame updating. Does the manipulation logic.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
if (GrabbableObject.CurrentAnchor != null)
{
// Constrain transform when the battery is inside a door
GeneratorDoor generatorDoor = GrabbableObject.CurrentAnchor.GetComponentInParent<GeneratorDoor>();
if (generatorDoor != null && !generatorDoor.IsLockOpen)
{
// Lock is closed? Battery can't move
GrabbableObject.IsLockedInPlace = true;
}
else
{
// Battery can move
GrabbableObject.IsLockedInPlace = false;
}
}
}
#endregion
#region Event Trigger Methods
/// <inheritdoc />
protected override void OnPlacedAfterSlidingIn()
{
base.OnPlacedAfterSlidingIn();
if (GrabbableObject.CurrentAnchor != null)
{
GeneratorDoor generatorDoor = GrabbableObject.CurrentAnchor.GetComponentInParent<GeneratorDoor>();
if (generatorDoor != null)
{
// Turn lights on when the battery finished sliding in
generatorDoor.IsBatteryInContact = true;
}
}
}
/// <summary>
/// Called right after the battery was grabbed.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
base.OnObjectGrabbed(e);
if (!GrabbableObject.IsLockedInPlace && e.GrabbableAnchor != null)
{
GeneratorDoor generatorDoor = e.GrabbableAnchor.GetComponentInParent<GeneratorDoor>();
if (generatorDoor != null)
{
// Turn lights off when the battery is moved away
generatorDoor.IsBatteryInContact = true;
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: f9d66e1c4a827a345a4c670b5cbf2d14
timeCreated: 1530605952
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="BatteryAnchor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Lab
{
/// <summary>
/// Component that helps enumerate the potential battery anchors in a scene using LabBatteryAnchor.EnabledComponents.
/// Battery placing has some additional special mechanics for the sliding-in/sliding-out behavior. This component
/// helps identifying where those places are.
/// </summary>
public class BatteryAnchor : UxrComponent<BatteryAnchor>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrGrabbableObjectAnchor _batteryAnchor;
/// <summary>
/// Actual anchor component where a battery can be placed.
/// </summary>
public UxrGrabbableObjectAnchor Anchor => _batteryAnchor;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8d25f62cc89144e0811a69ceae042e06
timeCreated: 1655561113

View File

@@ -0,0 +1,201 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GeneratorDoor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.Manipulation;
using UltimateXR.Manipulation.Helpers;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Lab
{
/// <summary>
/// Component that handles the generator door, which has battery locks to lock the battery in place.
/// </summary>
public class GeneratorDoor : UxrComponent<GeneratorDoor>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrAutoSlideInAnchor _batteryAnchor;
[SerializeField] private UxrGrabbableObject _grabbableLock;
[SerializeField] private Transform[] _locks;
[SerializeField] private float _lockHandleAngleClosed;
[SerializeField] private float _lockHandleAngleOpen;
[SerializeField] private bool _startLockOpen = true;
#endregion
#region Public Types & Data
/// <summary>
/// Gets whether the battery lock is open.
/// </summary>
public bool IsLockOpen
{
get => LockHandleOpenValue > 0.5f;
private set
{
// Set rotation using the correct property to avoid interference between grabbable object constraint calculation and manually setting its transform.
_grabbableLock.SingleRotationAxisDegrees = value ? _lockHandleAngleOpen : _lockHandleAngleClosed;
for (int i = 0; i < _locks.Length; ++i)
{
_locks[i].transform.localRotation = _lockInitialRotation[i] * Quaternion.AngleAxis((value ? 1.0f : 0.0f) * (_lockHandleAngleOpen - _lockHandleAngleClosed), Vector3.right);
}
}
}
/// <summary>
/// Gets whether there is a battery placed inside the generator and it is currently in contact with the bottom. This
/// allows to switch things on when the battery is actually in contact instead of being placed, because there is a
/// slide-in animation after the battery has been placed.
/// </summary>
public bool IsBatteryInContact { get; set; }
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_lockInitialRotation = new Quaternion[_locks.Length];
for (int i = 0; i < _locks.Length; ++i)
{
_lockInitialRotation[i] = _locks[i].localRotation;
}
}
/// <summary>
/// Subscribes to the events that help model the behavior.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_batteryAnchor.Anchor.Placed += Battery_Placed;
_batteryAnchor.Anchor.Removed += Battery_Removed;
_grabbableLock.ConstraintsApplied += Lock_ConstraintsApplied;
}
/// <summary>
/// Unsubscribes from the events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
_batteryAnchor.Anchor.Placed -= Battery_Placed;
_batteryAnchor.Anchor.Removed -= Battery_Removed;
_grabbableLock.ConstraintsApplied -= Lock_ConstraintsApplied;
}
/// <summary>
/// Initializes the lock open state.
/// </summary>
protected override void Start()
{
base.Start();
IsBatteryInContact = _batteryAnchor.Anchor.CurrentPlacedObject != null;
IsLockOpen = _startLockOpen;
}
/// <summary>
/// Updates the ability of the door to let a battery be placed inside.
/// </summary>
private void Update()
{
// The battery door anchor is disabled if the lock is closed and there is no battery inside
if (_batteryAnchor.Anchor.CurrentPlacedObject == null && !IsLockOpen)
{
_batteryAnchor.enabled = false;
}
else
{
_batteryAnchor.enabled = true;
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called after the lock has finished being manipulated so that additional constraints can be applied to it.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Lock_ConstraintsApplied(object sender, UxrApplyConstraintsEventArgs e)
{
float lockHandleOpenValue = LockHandleOpenValue;
float locksOpenValue = 1.0f - (1.0f - lockHandleOpenValue) * (1.0f - lockHandleOpenValue);
// Update small locks based on the main lock open value
for (int i = 0; i < _locks.Length; ++i)
{
_locks[i].transform.localRotation = _lockInitialRotation[i] * Quaternion.AngleAxis(locksOpenValue * (_lockHandleAngleOpen - _lockHandleAngleClosed), Vector3.right);
}
// Main lock can be manipulated only while the battery is completely inside or there is no battery
if (_batteryAnchor.Anchor.CurrentPlacedObject != null && _batteryAnchor.Anchor.CurrentPlacedObject.transform.localPosition.z > 0.01f)
{
_grabbableLock.IsLockedInPlace = true;
}
else
{
_grabbableLock.IsLockedInPlace = false;
}
}
/// <summary>
/// Called right after a battery was placed inside.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Battery_Placed(object sender, UxrManipulationEventArgs e)
{
// In order to make the lights turn on only when the battery reached the bottom, we control this from the Battery component.
}
/// <summary>
/// Called right after the battery was removed.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Battery_Removed(object sender, UxrManipulationEventArgs e)
{
IsBatteryInContact = false;
}
#endregion
#region Private Types & Data
/// <summary>
/// Returns a value between 0.0 and 1.0 telling how open the lock is.
/// </summary>
private float LockHandleOpenValue
{
get
{
float lockHandleOpenValue = Mathf.Clamp01((_grabbableLock.transform.localRotation.eulerAngles.z - _lockHandleAngleClosed) / (_lockHandleAngleOpen - _lockHandleAngleClosed));
return lockHandleOpenValue;
}
}
private bool _isBatteryInContact;
private Quaternion[] _lockInitialRotation;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 241d54f6114ee524895079db62d245cf
timeCreated: 1530536555
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,51 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Lamp.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Lab
{
/// <summary>
/// Binds the light intensity to the state of the currently placed light bulbs.
/// </summary>
public class Lamp : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrGrabbableObjectAnchor[] _sockets;
[SerializeField] private Light _light;
#endregion
#region Unity
/// <summary>
/// Updates the light intensity based on the currently placed light bulbs.
/// </summary>
private void Update()
{
float lightBulbIntensity = 0.0f;
foreach (UxrGrabbableObjectAnchor socket in _sockets)
{
if (socket.CurrentPlacedObject != null)
{
LightBulb lightBulb = socket.CurrentPlacedObject.GetComponentInChildren<LightBulb>();
if (lightBulb != null)
{
lightBulbIntensity += lightBulb.Intensity;
}
}
}
_light.intensity = lightBulbIntensity;
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 1672ad7753c9a75438933016a4b0e54d
timeCreated: 1530530970
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,82 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Laser.LaserBurn.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Lab
{
public partial class Laser
{
#region Private Types & Data
/// <summary>
/// Stores all information for a burn result of pointing the enabled laser to an object.
/// </summary>
private class LaserBurn
{
#region Public Types & Data
/// <summary>
/// Gets the transform component of the GameObject that is used to represent the burn.
/// </summary>
public Transform Transform => GameObject.transform;
/// <summary>
/// Gets the last normal of the laser impact that caused the laser burn.
/// </summary>
public Vector3 LastWorldNormal => Transform.TransformVector(LastNormal);
/// <summary>
/// Gets the last world-space position in the burn path.
/// </summary>
public Vector3 LastWorldPathPosition => Transform.TransformPoint(PathPositions[PathPositions.Count - 1]);
/// <summary>
/// Gets the dynamically created object to represent the burn.
/// </summary>
public GameObject GameObject { get; set; }
/// <summary>
/// Gets the dynamically created object that represents the incandescent part in the burn.
/// </summary>
public GameObject GameObjectIncandescent { get; set; }
/// <summary>
/// Gets the collider that was hit.
/// </summary>
public Collider Collider { get; set; }
/// <summary>
/// Gets the burn path line renderer.
/// </summary>
public LineRenderer LineRenderer { get; set; }
/// <summary>
/// Gets the incandescent path line renderer.
/// </summary>
public LineRenderer IncandescentLineRenderer { get; set; }
/// <summary>
/// Gets the positions in the burn path.
/// </summary>
public List<Vector3> PathPositions { get; set; }
/// <summary>
/// Gets the creation times of each path position so that we can fade them out based on age.
/// </summary>
public List<float> PathCreationTimes { get; set; }
/// <summary>
/// Last hit normal in local coordinates of the burn object.
/// </summary>
public Vector3 LastNormal { get; set; }
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8164104655d2449c88e83840fb643de0
timeCreated: 1655550081

View File

@@ -0,0 +1,586 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Laser.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Devices;
using UltimateXR.Extensions.Unity.Math;
using UltimateXR.Extensions.Unity.Render;
using UltimateXR.Haptics.Helpers;
using UltimateXR.Manipulation;
using UnityEngine;
using UnityEngine.Rendering;
namespace UltimateXR.Examples.FullScene.Lab
{
/// <summary>
/// Component that handles the laser in the lab room.
/// </summary>
public partial class Laser : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Transform _laserSource;
[SerializeField] private LayerMask _collisionMask = -1;
[SerializeField] private float _laserRayWidth = 0.003f;
[SerializeField] private float _laserRayLength = 5.0f;
[SerializeField] private Color _laserColor = Color.red;
[SerializeField] private float _laserBurnDelaySeconds = 0.2f; // Time the laser needs to be travelling at a speed less than LaserBurnParticlesMaxSpeed to create burn FX
[SerializeField] private float _laserBurnSpeedThreshold = 0.4f; // Maximum speed the laser can travel before stopping particles emission
[SerializeField] private Color _laserBurnColor = new Color(0.0f, 0.0f, 0.0f, 0.6f);
[SerializeField] private float _laserBurnVertexDistance = 0.03f;
[SerializeField] private float _laserBurnHeightOffset = 0.001f;
[SerializeField] private float _laserBurnDurationFadeStart = 3.0f;
[SerializeField] private float _laserBurnDurationFadeEnd = 6.0f;
[SerializeField] private Color _laserBurnIncandescentColor = new Color(0.7f, 0.7f, 0.1f, 1.0f);
[SerializeField] private float _laserBurnIncandescentDurationFadeEnd = 1.0f;
[SerializeField] private ParticleSystem _laserBurnParticles;
[SerializeField] private float _laserBurnParticlesHeightOffset;
[SerializeField] private bool _laserBurnReflectParticles;
[SerializeField] private GameObject _enableWhenLaserActive;
[SerializeField] private UxrGrabbableObject _triggerGrabbable;
[SerializeField] private Transform _trigger;
[SerializeField] private Vector3 _triggerOffset;
[SerializeField] private UxrFixedHapticFeedback _laserHaptics;
#endregion
#region Unity
/// <summary>
/// Sets up internal data.
/// </summary>
protected override void Awake()
{
base.Awake();
_triggerInitialOffset = _trigger.localPosition;
// Line renderer setup
_laserLineRenderer = gameObject.AddComponent<LineRenderer>();
_laserLineRenderer.useWorldSpace = false;
SetLaserLineRendererMesh(_laserRayLength);
_laserLineRenderer.material = new Material(ShaderExt.UnlitTransparentColor);
_laserLineRenderer.material.renderQueue = (int)RenderQueue.Overlay + 1;
_laserLineRenderer.material.color = _laserColor;
_laserLineRenderer.loop = true;
_laserLineRenderer.enabled = false;
_laserBurns = new List<LaserBurn>();
ParticleSystem.EmissionModule emission = _laserBurnParticles.emission;
emission.enabled = false;
_laserHaptics.enabled = false;
_createBurnTimer = _laserBurnDelaySeconds;
}
/// <summary>
/// Subscribe to avatar updated event.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
}
/// <summary>
/// Unsubscribe from avatar updated event.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
#endregion
#region Event Handling Methods
/// <summary>
/// We update the laser after all VR avatars have been updated to make sure it's processed after all manipulation.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
// Check if there is a hand grabbing the laser
if (UxrGrabManager.Instance.GetGrabbingHand(_triggerGrabbable, 0, out UxrGrabber grabber))
{
// Check if it's the local avatar, because the local avatar will drive the state changes (IsLaserEnabled is network synchronized).
if (grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
{
// It is! see which hand to check for a trigger squeeze
float trigger = UxrAvatar.LocalAvatarInput.GetInput1D(grabber.Side, UxrInput1D.Trigger);
_trigger.localPosition = _triggerInitialOffset + _triggerOffset * trigger;
_triggerGrabbable.GetGrabPoint(0).GetGripPoseInfo(grabber.Avatar).PoseBlendValue = trigger;
if (UxrAvatar.LocalAvatarInput.GetButtonsPress(grabber.Side, UxrInputButtons.Trigger))
{
// Trigger is squeezed
if (IsLaserEnabled == false)
{
IsLaserEnabled = true;
}
}
else
{
IsLaserEnabled = false;
}
}
}
else
{
// If there are no grabs, we don't need to sync using IsLaserEnabled because it can be solved locally.
_laserLineRenderer.enabled = false;
}
// Check laser raycast
if (IsLaserEnabled)
{
float currentRayLength = _laserRayLength;
if (Physics.Raycast(_laserSource.position, _laserSource.forward, out RaycastHit hitInfo, currentRayLength, _collisionMask, QueryTriggerInteraction.Ignore))
{
if (CurrentLaserBurn == null)
{
// This is a new burn -> initialize
_laserBurns[_laserBurns.Count - 1] = CreateNewLaserBurn(hitInfo.collider);
_createBurnTimer = _laserBurnDelaySeconds;
}
else if (CurrentLaserBurn.Collider != hitInfo.collider)
{
// If we hit another object we create a new laser burn entry
_laserBurns.Add(CreateNewLaserBurn(hitInfo.collider));
_createBurnTimer = _laserBurnDelaySeconds;
}
// Check if laser travel speed is below threshold to create a burn. If so decrement timer which will start a burn if it reaches 0.
if (Vector3.Distance(_lastLaserHitPosition, hitInfo.point) / Time.deltaTime < _laserBurnSpeedThreshold)
{
_createBurnTimer -= Time.deltaTime;
}
else
{
// Not enough speed -> new burn
_laserBurns.Add(CreateNewLaserBurn(hitInfo.collider));
_createBurnTimer = _laserBurnDelaySeconds;
}
_lastLaserHitPosition = hitInfo.point;
// Needs to start burn FX?
ParticleSystem.EmissionModule emission = _laserBurnParticles.emission;
emission.enabled = _createBurnTimer < 0.0f;
_laserBurnParticles.transform.position = hitInfo.point + hitInfo.normal * _laserBurnParticlesHeightOffset;
_laserBurnParticles.transform.rotation = _laserBurnReflectParticles ? Quaternion.LookRotation(Vector3.Reflect(_laserSource.forward, hitInfo.normal)) : Quaternion.Slerp(Quaternion.LookRotation(hitInfo.normal, Vector3.right), Quaternion.LookRotation(Vector3.up, Vector3.right), 0.9f);
if (_createBurnTimer < 0.0f)
{
// Burn trail
float segmentDistance = CurrentLaserBurn.PathPositions.Count == 0 ? 0.0f : Vector3.Distance(hitInfo.point, CurrentLaserBurn.LastWorldPathPosition);
if (CurrentLaserBurn.PathPositions.Count == 0 || segmentDistance > _laserBurnVertexDistance)
{
// Here we should create a new segment since the burn has travelled enough distance to create a new one, but first we are going to check if we somehow
// went over a hole, bump or depression in the geometry from the last point to this one. We do not want a burn strip to appear over gaps on the geometry so in that case
// what we will do is just create a new laser burn and skip this segment
if (CurrentLaserBurn.PathPositions.Count > 0 && segmentDistance > BurnGapCheckMinDistance)
{
bool startNewBurn = false;
Vector3 vectorAB = hitInfo.point - CurrentLaserBurn.LastWorldPathPosition;
for (int checkStep = 0; checkStep < BurnGapCheckSteps; ++checkStep)
{
// Perform a series of steps casting rays from the laser source to intermediate points between the last burn segment and the current one looking for changes in depth
float t = (checkStep + 1.0f) / (BurnGapCheckSteps + 1.0f);
Vector3 pointCheck = hitInfo.point + vectorAB * t;
Vector3 perpendicular = Vector3.Cross(Vector3.Cross(vectorAB.normalized, ((CurrentLaserBurn.LastWorldNormal + hitInfo.normal) * 0.5f).normalized), vectorAB.normalized);
Vector3 raySource = CurrentLaserBurn.LastWorldPathPosition + vectorAB * 0.5f + perpendicular * BurnGapCheckRaySourceDistance;
if (Physics.Raycast(raySource, (pointCheck - raySource).normalized, out RaycastHit hitInfoBurnGapCheck, _laserRayLength, _collisionMask, QueryTriggerInteraction.Ignore))
{
float distanceToLine = hitInfoBurnGapCheck.point.DistanceToLine(CurrentLaserBurn.LastWorldPathPosition, hitInfo.point);
if (distanceToLine > BurnGapCheckLineDistanceThreshold)
{
// Depth change too big -> create new laser burn
startNewBurn = true;
break;
}
}
else
{
// Raycast did not find intersection -> create new laser burn
startNewBurn = true;
break;
}
}
if (startNewBurn)
{
_laserBurns.Add(CreateNewLaserBurn(hitInfo.collider));
}
}
// Add last point and offset a little bit from the geometry to draw correctly
CurrentLaserBurn.PathPositions.Add(CurrentLaserBurn.Transform.InverseTransformPoint(hitInfo.point + hitInfo.normal * _laserBurnHeightOffset));
CurrentLaserBurn.PathCreationTimes.Add(Time.time);
CurrentLaserBurn.LastNormal = CurrentLaserBurn.Transform.InverseTransformVector(hitInfo.normal);
UpdateLaserBurnLineRenderer(CurrentLaserBurn);
UpdateLaserBurns(0, _laserBurns.Count - 1);
}
else
{
UpdateLaserBurns(0, _laserBurns.Count);
}
}
else
{
UpdateLaserBurns(0, _laserBurns.Count);
}
currentRayLength = hitInfo.distance;
}
else
{
ParticleSystem.EmissionModule emission = _laserBurnParticles.emission;
emission.enabled = false;
_createBurnTimer = _laserBurnDelaySeconds;
UpdateLaserBurns(0, _laserBurns.Count);
}
SetLaserLineRendererMesh(currentRayLength);
}
else
{
ParticleSystem.EmissionModule emission = _laserBurnParticles.emission;
emission.enabled = false;
_createBurnTimer = _laserBurnDelaySeconds;
UpdateLaserBurns(0, _laserBurns.Count);
}
if (_laserHaptics && grabber != null && grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
{
_laserHaptics.enabled = IsLaserEnabled;
_laserHaptics.HandSide = grabber.Side;
}
else
{
_laserHaptics.enabled = false;
}
if (_enableWhenLaserActive && !_enableWhenLaserActive.activeSelf && IsLaserEnabled)
{
_enableWhenLaserActive.SetActive(true);
}
else if (_enableWhenLaserActive && _enableWhenLaserActive.activeSelf && !IsLaserEnabled)
{
_enableWhenLaserActive.SetActive(false);
}
}
#endregion
#region Private Methods
/// <summary>
/// Updates the laser line renderer
/// </summary>
/// <param name="rayLength">Current laser ray length</param>
private void SetLaserLineRendererMesh(float rayLength)
{
_laserLineRenderer.startWidth = _laserRayWidth;
_laserLineRenderer.endWidth = _laserRayWidth;
Vector3[] positions =
{
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(0.0f, 0.0f, rayLength > LaserGradientLength ? LaserGradientLength : rayLength * 0.33f),
new Vector3(0.0f, 0.0f, rayLength < LaserGradientLength * 2.0f ? rayLength * 0.66f : rayLength - LaserGradientLength),
new Vector3(0.0f, 0.0f, rayLength)
};
for (int i = 0; i < positions.Length; ++i)
{
positions[i] = _laserLineRenderer.transform.InverseTransformPoint(_laserSource.TransformPoint(positions[i]));
}
_laserLineRenderer.positionCount = 4;
_laserLineRenderer.SetPositions(positions);
Gradient colorGradient = new Gradient();
colorGradient.colorKeys = new[]
{
new GradientColorKey(Color.white, 0.0f),
new GradientColorKey(Color.white, rayLength > LaserGradientLength ? LaserGradientLength / rayLength : 0.33f),
new GradientColorKey(Color.white, rayLength < LaserGradientLength * 2.0f ? 0.66f : 1.0f - LaserGradientLength / rayLength),
new GradientColorKey(Color.white, 1.0f)
};
colorGradient.alphaKeys = new[]
{
new GradientAlphaKey(0.0f, 0.0f),
new GradientAlphaKey(_laserColor.a, rayLength > LaserGradientLength ? LaserGradientLength / rayLength : 0.3f),
new GradientAlphaKey(_laserColor.a, rayLength < LaserGradientLength * 2.0f ? 0.66f : 1.0f - LaserGradientLength / rayLength),
new GradientAlphaKey(0.0f, 1.0f)
};
_laserLineRenderer.colorGradient = colorGradient;
}
/// <summary>
/// Creates a new laser burn entry
/// </summary>
/// <param name="collider">Collider the laser burn is attached to</param>
/// <returns>New laser burn object</returns>
private LaserBurn CreateNewLaserBurn(Collider collider)
{
LaserBurn newLaserBurn = new LaserBurn();
newLaserBurn.Collider = collider;
newLaserBurn.GameObject = new GameObject("LaserBurn");
newLaserBurn.GameObject.transform.parent = collider.transform;
newLaserBurn.GameObject.transform.localPosition = Vector3.zero;
newLaserBurn.GameObject.transform.localRotation = Quaternion.identity;
newLaserBurn.LineRenderer = newLaserBurn.GameObject.AddComponent<LineRenderer>();
newLaserBurn.LineRenderer.receiveShadows = true;
newLaserBurn.LineRenderer.shadowCastingMode = ShadowCastingMode.Off;
newLaserBurn.LineRenderer.useWorldSpace = false;
newLaserBurn.LineRenderer.material = new Material(ShaderExt.UnlitTransparentColor);
newLaserBurn.LineRenderer.material.renderQueue = (int)RenderQueue.Overlay + 1;
newLaserBurn.LineRenderer.material.color = _laserBurnColor;
newLaserBurn.LineRenderer.loop = false;
newLaserBurn.LineRenderer.positionCount = 0;
newLaserBurn.GameObjectIncandescent = new GameObject("LaserBurnIncandescent");
newLaserBurn.GameObjectIncandescent.transform.parent = collider.transform;
newLaserBurn.GameObjectIncandescent.transform.localPosition = Vector3.zero;
newLaserBurn.GameObjectIncandescent.transform.localRotation = Quaternion.identity;
newLaserBurn.IncandescentLineRenderer = newLaserBurn.GameObjectIncandescent.AddComponent<LineRenderer>();
newLaserBurn.IncandescentLineRenderer.receiveShadows = false;
newLaserBurn.IncandescentLineRenderer.shadowCastingMode = ShadowCastingMode.Off;
newLaserBurn.IncandescentLineRenderer.useWorldSpace = false;
newLaserBurn.IncandescentLineRenderer.material = new Material(ShaderExt.UnlitAdditiveColor);
newLaserBurn.IncandescentLineRenderer.material.renderQueue = (int)RenderQueue.Overlay + 2;
newLaserBurn.IncandescentLineRenderer.material.color = _laserBurnIncandescentColor;
newLaserBurn.IncandescentLineRenderer.loop = false;
newLaserBurn.IncandescentLineRenderer.positionCount = 0;
newLaserBurn.PathPositions = new List<Vector3>();
newLaserBurn.PathCreationTimes = new List<float>();
_createBurnTimer = _laserBurnDelaySeconds;
return newLaserBurn;
}
/// <summary>
/// Updates the given range of laser burns. Unused laser burns will be deleted
/// </summary>
/// <param name="startIndex">The start index</param>
/// <param name="count">The number of items to update</param>
private void UpdateLaserBurns(int startIndex, int count)
{
for (int i = startIndex; i < startIndex + count && i < _laserBurns.Count; ++i)
{
UpdateLaserBurnLineRenderer(_laserBurns[i]);
if (_laserBurns[i].PathPositions.Count <= 1 && _laserBurns[i] != CurrentLaserBurn)
{
Destroy(_laserBurns[i].GameObject);
Destroy(_laserBurns[i].GameObjectIncandescent);
_laserBurns.RemoveAt(i);
i--;
}
}
}
/// <summary>
/// Updates the laser burn line renderer
/// </summary>
/// <param name="laserBurn">LaserBurn object whose LineRenderer to update</param>
private void UpdateLaserBurnLineRenderer(LaserBurn laserBurn)
{
if (laserBurn == null || laserBurn.GameObject == null)
{
return;
}
laserBurn.LineRenderer.startWidth = _laserRayWidth * 2.5f;
laserBurn.LineRenderer.endWidth = _laserRayWidth * 2.5f;
laserBurn.IncandescentLineRenderer.startWidth = _laserRayWidth * 1.5f;
laserBurn.IncandescentLineRenderer.endWidth = _laserRayWidth * 1.5f;
// Remove segment positions that have already faded out. Keep just 1 if all need to be deleted to have LastPathPosition accessible.
int lastIndexToDelete = -1;
for (int i = 0; i < laserBurn.PathCreationTimes.Count; ++i)
{
if (Time.time - laserBurn.PathCreationTimes[i] >= _laserBurnDurationFadeEnd)
{
lastIndexToDelete = i;
}
else
{
break;
}
}
if (lastIndexToDelete >= 0 && laserBurn.PathPositions.Count > 1)
{
laserBurn.PathCreationTimes.RemoveRange(0, lastIndexToDelete + 1);
laserBurn.PathPositions.RemoveRange(0, lastIndexToDelete + 1);
}
// Inside the burn range, compute the start index for the incandescent part
int incandescentIndexStart = 0;
for (int i = 0; i < laserBurn.PathCreationTimes.Count; ++i)
{
if (Time.time - laserBurn.PathCreationTimes[i] < _laserBurnIncandescentDurationFadeEnd)
{
incandescentIndexStart = i;
break;
}
}
// Create color and alpha gradients. Maximum number of entries for Unity's LineRenderer are 8.
if (laserBurn.PathCreationTimes.Count > 0)
{
Gradient colorGradient = new Gradient();
Gradient colorGradientIncandescent = new Gradient();
colorGradient.colorKeys = new[] { new GradientColorKey(Color.white, 0.0f), new GradientColorKey(Color.white, 1.0f) };
colorGradientIncandescent.colorKeys = new[] { new GradientColorKey(Color.white, 0.0f), new GradientColorKey(Color.white, 1.0f) };
GradientAlphaKey[] alphaKeys = new GradientAlphaKey[8];
GradientAlphaKey[] alphaKeysIncandescent = new GradientAlphaKey[8];
for (int i = 0; i < alphaKeys.Length; ++i)
{
float t = i / (alphaKeys.Length - 1.0f);
int timeIndex = Mathf.Clamp(Mathf.RoundToInt(t * (laserBurn.PathCreationTimes.Count - 1.0f)), 0, laserBurn.PathCreationTimes.Count - 1);
float life = Time.time - laserBurn.PathCreationTimes[timeIndex];
alphaKeys[i].alpha = life < _laserBurnDurationFadeStart ? 1.0f : 1.0f - Mathf.Clamp01((life - _laserBurnDurationFadeStart) / (_laserBurnDurationFadeEnd - _laserBurnDurationFadeStart));
alphaKeys[i].time = t;
}
for (int i = 0; i < alphaKeysIncandescent.Length; ++i)
{
float t = i / (alphaKeysIncandescent.Length - 1.0f);
int numIncandescentEntries = laserBurn.PathCreationTimes.Count - incandescentIndexStart;
int timeIndex = Mathf.Clamp(Mathf.RoundToInt(t * (numIncandescentEntries - 1.0f)), 0, numIncandescentEntries - 1);
float life = Time.time - laserBurn.PathCreationTimes[incandescentIndexStart + timeIndex];
alphaKeysIncandescent[i].alpha = 1.0f - Mathf.Clamp01(life / _laserBurnIncandescentDurationFadeEnd);
alphaKeysIncandescent[i].time = t;
}
colorGradient.alphaKeys = alphaKeys;
colorGradientIncandescent.alphaKeys = alphaKeysIncandescent;
laserBurn.LineRenderer.colorGradient = colorGradient;
laserBurn.IncandescentLineRenderer.colorGradient = colorGradientIncandescent;
}
// Update positions
if (laserBurn.PathPositions.Count > 1)
{
laserBurn.LineRenderer.positionCount = laserBurn.PathPositions.Count;
laserBurn.LineRenderer.SetPositions(laserBurn.PathPositions.ToArray());
}
else
{
laserBurn.LineRenderer.positionCount = 0;
}
if (incandescentIndexStart < laserBurn.PathPositions.Count - 1)
{
int count = laserBurn.PathPositions.Count - incandescentIndexStart;
laserBurn.IncandescentLineRenderer.positionCount = count;
laserBurn.IncandescentLineRenderer.SetPositions(laserBurn.PathPositions.GetRange(incandescentIndexStart, count).ToArray());
}
else
{
laserBurn.IncandescentLineRenderer.positionCount = 0;
}
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets or sets whether the laser is enabled.
/// </summary>
private bool IsLaserEnabled
{
get => _laserLineRenderer.enabled;
set
{
if (_laserLineRenderer.enabled != value)
{
BeginSync();
if (value)
{
// Start a new empty laser burn (it will be setup later, we use null to force to set up a new one)
_laserBurns.Add(null);
}
_laserLineRenderer.enabled = value;
EndSyncProperty(value);
}
}
}
/// <summary>
/// Gets the current laser burn being generated.
/// </summary>
private LaserBurn CurrentLaserBurn => _laserBurns[_laserBurns.Count - 1];
private const float LaserGradientLength = 0.4f; // Laser is rendered with a gradient of this length to make it a little less dull
private const float BurnGapCheckSteps = 4; // Number of steps to check for gaps in the geometry when computing laser burns
private const float BurnGapCheckMinDistance = 0.02f; // Only perform gap checks if the separation between two consecutive laser burn segments is greater than this value
private const float BurnGapCheckRaySourceDistance = 0.1f; // The raycasts performed on each gap check step will be casted from this distance to the geometry
private const float BurnGapCheckLineDistanceThreshold = 0.002f; // Allow this amount of depth variation when checking for gaps or bumps in the geometry
private Vector3 _triggerInitialOffset;
private LineRenderer _laserLineRenderer;
private List<LaserBurn> _laserBurns;
private Vector3 _lastLaserHitPosition;
private float _createBurnTimer;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 82bab9d2f1f530649af83fd846d679e9
timeCreated: 1530622409
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,112 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LightBulb.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Lab
{
/// <summary>
/// Allows to model light bulbs that will affect the light attached to the <see cref="Lamp" /> they are placed on.
/// </summary>
public class LightBulb : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrGrabbableObject _grabbableObject;
[SerializeField] private float _lightIntensity;
[SerializeField] private bool _isFaulty;
[SerializeField] private Color _emissiveDisabled = Color.black;
[SerializeField] private Color _emissiveEnabled = Color.white;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the light intensity contributed by the light bulb, which may flicker if it's faulty or be zero if it's not
/// connected to the lamp.
/// </summary>
public float Intensity
{
get
{
if (_grabbableObject.CurrentAnchor == null)
{
// Not attached to anything
return 0.0f;
}
if (_grabbableObject.CurrentAnchor.GetComponentInParent<Lamp>())
{
// Attached to a lamp. See if it is faulty or works correctly.
if (_isFaulty)
{
float noise = Mathf.PerlinNoise(_randX + Time.time * 20.0f, _randY * 10.0f);
if (noise > 0.66f)
{
return _lightIntensity;
}
if (noise > 0.16f)
{
return 0.0f;
}
return _lightIntensity * 0.5f;
}
return _lightIntensity;
}
// Not attached to a lamp
return 0.0f;
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
Renderer renderer = GetComponent<Renderer>();
if (renderer)
{
_material = renderer.material;
}
_randX = Random.value;
_randY = Random.value;
}
/// <summary>
/// Updates the emissive based on the light intensity.
/// </summary>
private void Update()
{
if (_material && _grabbableObject && _lightIntensity > 0.0f)
{
Color emissiveColor = Color.Lerp(_emissiveDisabled, _emissiveEnabled, Intensity / _lightIntensity);
_material.SetColor(UxrConstants.Shaders.EmissionColorVarName, emissiveColor);
}
}
#endregion
#region Private Types & Data
private Material _material;
private float _randX;
private float _randY;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 70e977db6e159b54f80cba9f6dc6f6a3
timeCreated: 1530531429
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,122 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SpringOnRelease.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components.Composite;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Lab
{
/// <summary>
/// Adds a spring behavior to an object whenever it is released.
/// </summary>
[RequireComponent(typeof(UxrGrabbableObject))]
public class SpringOnRelease : UxrGrabbableObjectComponent<SpringOnRelease>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _springAmplitudeMultiplier;
[SerializeField] private float _springMaxAmplitude;
[SerializeField] private float _springRotAmplitudeMultiplier;
[SerializeField] private float _springMaxRotAmplitude;
[SerializeField] private float _springDuration;
[SerializeField] private float _springFrequency;
#endregion
#region Unity
/// <summary>
/// Updates the spring if it is currently active.
/// </summary>
private void Update()
{
if (_timer > 0.0f)
{
_timer -= Time.deltaTime;
if (_timer < 0.0f)
{
transform.position = _releasePosition;
transform.eulerAngles = _releaseEuler;
}
else
{
float t = (_springDuration - _timer) / _springDuration;
Vector3 delta = _filteredVelocity * (Mathf.Sin(-(_springDuration - _timer) * Mathf.PI * 2.0f * _springFrequency) * (1.0f - t));
transform.position = _releasePosition + delta;
Vector3 deltaEuler = _filteredAngularVelocity * (Mathf.Sin(-(_springDuration - _timer) * Mathf.PI * 2.0f * _springFrequency) * (1.0f - t));
transform.eulerAngles = _releaseEuler + deltaEuler;
}
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Called right after the object was grabbed.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
_timer = -1.0f;
}
/// <summary>
/// Called right after the object was released.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectReleased(UxrManipulationEventArgs e)
{
if (e.IsGrabbedStateChanged)
{
_releasePosition = transform.position;
_releaseEuler = transform.eulerAngles;
_releaseVelocity = e.ReleaseVelocity;
_releaseAngularVelocity = e.ReleaseAngularVelocity;
_timer = _springDuration;
_filteredVelocity = _releaseVelocity * _springAmplitudeMultiplier;
if (_filteredVelocity.magnitude > _springMaxAmplitude)
{
_filteredVelocity = _filteredVelocity.normalized * _springMaxAmplitude;
}
_filteredAngularVelocity = _releaseAngularVelocity * _springRotAmplitudeMultiplier;
if (Mathf.Abs(_filteredAngularVelocity.x) > _springMaxRotAmplitude)
{
_filteredAngularVelocity.x = _filteredAngularVelocity.x > 0.0f ? _springMaxRotAmplitude : -_springMaxRotAmplitude;
}
if (Mathf.Abs(_filteredAngularVelocity.y) > _springMaxRotAmplitude)
{
_filteredAngularVelocity.y = _filteredAngularVelocity.y > 0.0f ? _springMaxRotAmplitude : -_springMaxRotAmplitude;
}
if (Mathf.Abs(_filteredAngularVelocity.z) > _springMaxRotAmplitude)
{
_filteredAngularVelocity.z = _filteredAngularVelocity.z > 0.0f ? _springMaxRotAmplitude : -_springMaxRotAmplitude;
}
}
}
#endregion
#region Private Types & Data
private Vector3 _releasePosition;
private Vector3 _releaseEuler;
private Vector3 _releaseVelocity;
private Vector3 _releaseAngularVelocity;
private Vector3 _filteredVelocity;
private Vector3 _filteredAngularVelocity;
private float _timer;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 6e9ebb4e3c01c8c4287ce10c7e1f4edd
timeCreated: 1530876385
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 175666b96a0a9704493170bcd4896304
folderAsset: yes
timeCreated: 1530189790
licenseType: Pro
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,71 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MagAmmoIndicator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.Mechanics.Weapons;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Weapons
{
[RequireComponent(typeof(UxrFirearmMag))]
public class MagAmmoIndicator : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _setValueOnStart = true;
[SerializeField] private Renderer _renderer;
#endregion
#region Unity
protected override void Awake()
{
base.Awake();
_mag = GetComponent<UxrFirearmMag>();
}
protected override void OnEnable()
{
base.OnEnable();
_mag.RoundsChanged += OnRoundsChanged;
}
protected override void OnDisable()
{
base.OnDisable();
_mag.RoundsChanged -= OnRoundsChanged;
}
protected override void Start()
{
base.OnEnable();
if (_setValueOnStart)
{
OnRoundsChanged();
}
}
#endregion
#region Event Trigger Methods
private void OnRoundsChanged()
{
_renderer.material.SetFloat(FillVariableName, (float)_mag.Rounds / _mag.Capacity);
}
#endregion
#region Private Types & Data
private const string FillVariableName = "_Fill";
private UxrFirearmMag _mag;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 80780a72a3aeaf549a04f479bfc41f47
timeCreated: 1533626585
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: