first commit
This commit is contained in:
commit
aa44d3ad70
1585 changed files with 277994 additions and 0 deletions
60
Assets/Resources/Scripts/StepsLink/ArrowSript.cs
Normal file
60
Assets/Resources/Scripts/StepsLink/ArrowSript.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class ArrowSript : MonoBehaviour
|
||||
{
|
||||
private GameObject targetObj;
|
||||
|
||||
// set the target obj
|
||||
public void SetTargetObj(GameObject obj)
|
||||
{
|
||||
targetObj = obj;
|
||||
}
|
||||
|
||||
// method to place the arrow over the passed object
|
||||
private void PlaceArrowOverObject(GameObject obj)
|
||||
{
|
||||
// get the obj position
|
||||
Vector3 objPos = obj.transform.position;
|
||||
|
||||
// put the arrow over the obj
|
||||
transform.position = objPos;
|
||||
|
||||
// rotate the arrow to face the obj
|
||||
transform.LookAt(obj.transform);
|
||||
|
||||
// increase the arrow height from the obj top
|
||||
transform.position += new Vector3(0, 0.3f, 0);
|
||||
|
||||
// show
|
||||
ShowArrow(obj);
|
||||
}
|
||||
|
||||
// method to show the arrow
|
||||
public void ShowArrow(GameObject obj)
|
||||
{
|
||||
// enable the arrow
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// rotate on y axis
|
||||
transform.Rotate(0.5f, 0, 0);
|
||||
|
||||
// if the target obj is not null
|
||||
if (targetObj != null)
|
||||
{
|
||||
// place the arrow over the target obj
|
||||
PlaceArrowOverObject(targetObj);
|
||||
}
|
||||
}
|
||||
|
||||
// method to hide the arrow
|
||||
public void HideArrow()
|
||||
{
|
||||
// disable the arrow
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
}
|
11
Assets/Resources/Scripts/StepsLink/ArrowSript.cs.meta
Normal file
11
Assets/Resources/Scripts/StepsLink/ArrowSript.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6e6c5889610581b42b0f7abdd08644aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
345
Assets/Resources/Scripts/StepsLink/HapticManager.cs
Normal file
345
Assets/Resources/Scripts/StepsLink/HapticManager.cs
Normal file
|
@ -0,0 +1,345 @@
|
|||
using UnityEngine;
|
||||
using Bhaptics.SDK2;
|
||||
using System;
|
||||
using NaughtyAttributes;
|
||||
|
||||
namespace Tools.Bhaptics
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that manages haptic feedback for the step link.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is implemented as a singleton. Use <see cref="GetInstance"/> to obtain the instance of the <see cref="HapticManager"/>.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// This class is not thread-safe; however, it should not pose a problem for this use case. The <see cref="BhapticsLibrary"/>
|
||||
/// appears to be thread-safe while playing the haptic feedback.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Get the instance of the HapticManager
|
||||
/// HapticManager hapticManager = HapticManager.GetInstance;
|
||||
///
|
||||
/// // Play the haptic feedback for the start of the game
|
||||
/// hapticManager.PlayStartGame();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class HapticManager : MonoBehaviour
|
||||
{
|
||||
// Const strings
|
||||
private const string EXC_NOT_INIT = "HapticManager not initialized, call Initialize first";
|
||||
private const string LOG_ALREADY_INIT = "HapticManager already initialized";
|
||||
private const string EXC_CAMERA_NULL = "PlayerCamera not set.";
|
||||
private const string EXC_MIN_DISTANCE = "MinDistance_meters must be greater than 0 meters";
|
||||
private const string EXC_REFRATE_MIN = "RefreshRate (in seconds) must be greater than 0.1";
|
||||
private const string LOG_HIGH_REFRESHRATE = "RefreshRate is high, consider using a lower value";
|
||||
private const string LOG_INIT_DONE = "HapticManager initialized";
|
||||
|
||||
/// <summary>
|
||||
/// Haptic feedback for the start of the game
|
||||
/// </summary>
|
||||
private static readonly string STEP_LINK_GAMESTART = BhapticsEvent.GAME_START;
|
||||
|
||||
/// <summary>
|
||||
/// Haptic feedback for the step link update of the haptic feedback
|
||||
/// </summary>
|
||||
private static readonly string STEP_LINK_EFFECT = BhapticsEvent.STEP_LINK;
|
||||
|
||||
/// <summary>
|
||||
/// Haptic feedback for the step link update of the haptic feedback when the user is looking precisely at the object
|
||||
/// </summary>
|
||||
private static readonly string STEP_LINK_EFFECT_PERFECT = BhapticsEvent.HAPTIC_STEP_LINK_PERFECT;
|
||||
|
||||
/// <summary>
|
||||
/// Angle in degrees for the perfect look at the object
|
||||
/// </summary>
|
||||
private static readonly float ANGLE_FOR_PERFECTION = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// The camera of the player, used as center of the reference system
|
||||
/// </summary>
|
||||
[Tooltip("The camera of the player, used as center of the reference system")]
|
||||
[Required]
|
||||
public Camera playerCamera;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum distance starting decreasing the intensity of the haptic feedback
|
||||
/// </summary>
|
||||
[Tooltip("Minimum distance starting decreasing the intensity of the haptic feedback")]
|
||||
[MinValue(0.0f)]
|
||||
public float minDistance_meters = 4f;
|
||||
|
||||
/// <summary>
|
||||
/// Refresh rate of the distance / angle calculation and haptic feedback in seconds
|
||||
/// </summary>
|
||||
[Tooltip("Refresh rate of the distance / angle calculation and haptic feedback in seconds")]
|
||||
[MinValue(0.2f)]
|
||||
public float refreshRate = 0.3f;
|
||||
|
||||
/// <summary>
|
||||
/// If true, when precisely looking at the object, the haptic feedback on head will be used intead
|
||||
/// </summary>
|
||||
[Header("Use Haptic Tactal")]
|
||||
[Tooltip("If true, when precisely looking at the object, use the tactal")]
|
||||
public bool headForPreciseLook = true;
|
||||
|
||||
/// <summary>
|
||||
/// The camera of the player, used as center of the reference system
|
||||
/// </summary>
|
||||
public float MinDistance_meters
|
||||
{
|
||||
get
|
||||
{
|
||||
return minDistance_meters;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value >= 0)
|
||||
{
|
||||
minDistance_meters = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(EXC_MIN_DISTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh rate of the distance / angle calculation and haptic feedback in seconds
|
||||
/// </summary>
|
||||
public float RefreshRate
|
||||
{
|
||||
get
|
||||
{
|
||||
return refreshRate;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 0.1f) { refreshRate = value; }
|
||||
else
|
||||
{
|
||||
throw new Exception(EXC_REFRATE_MIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (playerCamera == null)
|
||||
{
|
||||
throw new Exception(EXC_CAMERA_NULL);
|
||||
}
|
||||
else if (minDistance_meters < 0.1)
|
||||
{
|
||||
throw new Exception(EXC_MIN_DISTANCE);
|
||||
}
|
||||
else if (refreshRate <= 0.1)
|
||||
{
|
||||
throw new Exception(EXC_REFRATE_MIN);
|
||||
}
|
||||
else if (refreshRate >= 5)
|
||||
{
|
||||
Debug.LogWarning(LOG_HIGH_REFRESHRATE);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play the haptic feedback intended for the start of the game
|
||||
/// </summary>
|
||||
public void PlayStartGame()
|
||||
{
|
||||
BhapticsLibrary.Play(STEP_LINK_GAMESTART);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play the haptic feedback of the fast heartbeat for the seconds specified
|
||||
/// </summary>
|
||||
public void PlayHeartBeatFast()
|
||||
{
|
||||
if (!BhapticsLibrary.IsPlayingByEventId(BhapticsEvent.HEARTBEAT_FAST))
|
||||
{
|
||||
BhapticsLibrary.PlayParam(BhapticsEvent.HEARTBEAT_FAST,
|
||||
intensity: 1f,
|
||||
duration: 1f,
|
||||
angleX: 0f,
|
||||
offsetY: 0f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the haptic feedback of the heartbeat
|
||||
/// </summary>
|
||||
public void StopHeartBeatFast()
|
||||
{
|
||||
BhapticsLibrary.StopByEventId(BhapticsEvent.HEARTBEAT_FAST);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play the haptic feedback of the fast heartbeat for the seconds specified
|
||||
/// </summary>
|
||||
public void PlayHeartBeat()
|
||||
{
|
||||
if (!BhapticsLibrary.IsPlayingByEventId(BhapticsEvent.HEARTBEAT))
|
||||
{
|
||||
BhapticsLibrary.PlayParam(BhapticsEvent.HEARTBEAT,
|
||||
intensity: 1f,
|
||||
duration: 1f,
|
||||
angleX: 0f,
|
||||
offsetY: 0f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the haptic feedback of the heartbeat
|
||||
/// </summary>
|
||||
public void StopHeartBeat()
|
||||
{
|
||||
BhapticsLibrary.StopByEventId(BhapticsEvent.HEARTBEAT_FAST);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play the haptic feedback of the fbomb for the seconds specified
|
||||
/// </summary>
|
||||
public void PlayBomb(GameObject gameObject)
|
||||
{
|
||||
BhapticsLibrary.PlayParam(BhapticsEvent.EXPLOSION,
|
||||
intensity: 2f,
|
||||
duration: 1f,
|
||||
angleX: ComputeXZAngle(playerCamera.gameObject, gameObject),
|
||||
offsetY: 0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop all the haptic feedback
|
||||
/// </summary>
|
||||
public void StopAll()
|
||||
{
|
||||
BhapticsLibrary.StopAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get XZ angle between the camera and the target
|
||||
/// The XZ is the horizontal plane
|
||||
/// </summary>
|
||||
/// <param name="camera"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
private float ComputeXZAngle(GameObject camera, GameObject target)
|
||||
{
|
||||
Vector2 cameraPositionXZ = new(camera.transform.position.x, camera.transform.position.z);
|
||||
Vector2 targetPositionFromCameraXZ = new(target.transform.position.x, target.transform.position.z);
|
||||
|
||||
Vector2 cameraToNextObjectXZDirection = targetPositionFromCameraXZ - cameraPositionXZ;
|
||||
cameraToNextObjectXZDirection.Normalize();
|
||||
|
||||
Vector2 cameraDirectionXZ = new(camera.transform.forward.x, camera.transform.forward.z);
|
||||
|
||||
return Vector2.SignedAngle(cameraDirectionXZ, cameraToNextObjectXZDirection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the angle between the camera and the target on the YZ plane
|
||||
/// The YZ is the vertical plane going forward from the camera
|
||||
/// </summary>
|
||||
private float ComputeYZAngle(GameObject camera, GameObject target)
|
||||
{
|
||||
//plane of the, create 2 vectors to use for the angleXZ calculation
|
||||
Vector2 cameraPositionYZ = new(camera.transform.position.y, camera.transform.position.z);
|
||||
Vector2 targetPositionFromCameraYZ = new(target.transform.position.y, target.transform.position.z);
|
||||
|
||||
Vector2 cameraToNextObjectYZDirection = targetPositionFromCameraYZ - cameraPositionYZ;
|
||||
cameraToNextObjectYZDirection.Normalize();
|
||||
|
||||
Vector2 cameraDirectionYZ = new(camera.transform.forward.y, camera.transform.forward.z);
|
||||
|
||||
return Vector2.SignedAngle(cameraDirectionYZ, cameraToNextObjectYZDirection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the distance between the camera and the target
|
||||
/// </summary>
|
||||
/// <param name="camera"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
private float ComputeDistance(GameObject camera, GameObject target)
|
||||
{
|
||||
float distance = Vector3.Distance(camera.transform.position, target.transform.position);
|
||||
return distance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the intensity of the haptic feedback based on the distance between the camera and the target
|
||||
/// </summary>
|
||||
/// <param name="camera"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
private float ComputeIntensitiy(GameObject camera, GameObject target)
|
||||
{
|
||||
float distance = ComputeDistance(camera, target);
|
||||
float intensity = distance > minDistance_meters ? 1 : distance /= minDistance_meters;
|
||||
return intensity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the offsetY of the haptic feedback based on the angleYZ between the camera and the target
|
||||
/// </summary>
|
||||
/// <param name="angleYZ"></param>
|
||||
/// <returns></returns>
|
||||
private float ComputeOffsetY(float angleYZ)
|
||||
{
|
||||
float offsetY = angleYZ / 180f;
|
||||
if (offsetY > 0.5f) offsetY = 0.5f;
|
||||
else if (offsetY < -0.5f) offsetY = -0.5f;
|
||||
|
||||
return -offsetY;
|
||||
}
|
||||
|
||||
// This method plays the haptic feedback based on the distance and angleXZ between
|
||||
// the headset and the parameter object
|
||||
/// <summary>
|
||||
/// Play the haptic feedback based on the distance and angleXZ between the headset and the parameter object
|
||||
/// also the offsetY is calculated based on the angleYZ between the headset and the parameter object
|
||||
/// its possible to use the head for precise look, in this case the haptic feedback on the head will be used
|
||||
/// </summary>
|
||||
/// <param name="linkedObject"></param>
|
||||
/// <returns></returns>
|
||||
public void PlayDirectionalLink(GameObject linkedObject)
|
||||
{
|
||||
float angleXZ = ComputeXZAngle(playerCamera.gameObject, linkedObject);
|
||||
float angleYZ = ComputeYZAngle(playerCamera.gameObject, linkedObject);
|
||||
|
||||
BhapticsLibrary.StopAll();
|
||||
|
||||
// if the user is looking at the object, play head haptic feedback, otherwise play vest haptic feedback
|
||||
if (headForPreciseLook && Mathf.Abs(angleXZ) < ANGLE_FOR_PERFECTION && Mathf.Abs(angleYZ) < ANGLE_FOR_PERFECTION)
|
||||
{
|
||||
BhapticsLibrary.PlayParam(STEP_LINK_EFFECT_PERFECT,
|
||||
intensity: 0.5f,
|
||||
duration: RefreshRate,
|
||||
angleX: 0f,
|
||||
offsetY: 0f);
|
||||
|
||||
// Debug.Log("HEAD: Distance: " + 0 + " m, angleXZ: " + 0 + "°, intensity: " + 0.1f + " duration: " + RefreshRate + " s, offsetY: " + 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
float intensity = ComputeIntensitiy(playerCamera.gameObject, linkedObject);
|
||||
|
||||
float offsetY = ComputeOffsetY(angleYZ);
|
||||
|
||||
// intensity: 1f, The value multiplied by the original value
|
||||
// duration: 1f, The value multiplied by the original value
|
||||
// angleX: 20f, The value that rotates around global Vector3.up(0~360f)
|
||||
// offsetY: 0.3f, // The value to move up and down(-0.5~0.5)
|
||||
BhapticsLibrary.PlayParam(STEP_LINK_EFFECT,
|
||||
intensity: intensity,
|
||||
duration: RefreshRate,
|
||||
angleX: angleXZ,
|
||||
offsetY: offsetY);
|
||||
|
||||
// Debug.Log("SUIT: Distance: " + distance + " m, angleXZ: " + angleXZ + "°, intensity: " + intensity + " duration: " + RefreshRate + " s, offsetY: " + offsetY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Resources/Scripts/StepsLink/HapticManager.cs.meta
Normal file
11
Assets/Resources/Scripts/StepsLink/HapticManager.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1910163bebfe0354a81ef3daedfd4dd1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
211
Assets/Resources/Scripts/StepsLink/StepOrchestrator.cs
Normal file
211
Assets/Resources/Scripts/StepsLink/StepOrchestrator.cs
Normal file
|
@ -0,0 +1,211 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using NaughtyAttributes;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public class StepOrchestrator : MonoBehaviour
|
||||
{
|
||||
// Const strings
|
||||
private const string LOG_START_INIT = "Started Loading initializing the steps Link System";
|
||||
|
||||
[Header("Disclaimer")]
|
||||
[InfoBox("It needs to be a parent of StepsList objects. Or manually add the lists. Not both.", EInfoBoxType.Normal)]
|
||||
|
||||
[HorizontalLine(color: EColor.Blue)]
|
||||
|
||||
|
||||
[Header("Callbacks")]
|
||||
|
||||
[Tooltip("Event called when the system is initialized")]
|
||||
[SerializeField]
|
||||
private UnityEvent OnSystemInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the lists will be loaded from the children of the game object that have the StepsList script attached
|
||||
/// </summary>
|
||||
[Tooltip("If true, the lists will be loaded from the children of the game object that have the StepsList script attached")]
|
||||
[SerializeField]
|
||||
private bool loadsListsFromChildren = true;
|
||||
|
||||
/// <summary>
|
||||
/// List of all the StepsList that are part of the game. Order is respected.
|
||||
/// </summary>
|
||||
[HideIf("loadsListsFromChildren")]
|
||||
[Tooltip("List of all the StepsList that are part of the game. Order is respected.")]
|
||||
[SerializeField]
|
||||
private List<StepsListScript> stepsLists = new();
|
||||
|
||||
/// <summary>
|
||||
/// Flag to check if the step link finished initializing all lists
|
||||
/// </summary>
|
||||
private bool isInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to check if the steps link system is running the lists
|
||||
/// </summary>
|
||||
private bool isRunning = false;
|
||||
|
||||
void Start()
|
||||
{
|
||||
Debug.Log(LOG_START_INIT);
|
||||
StartCoroutine(InitStepLinkSystem());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the step link system, getting all the children lists and initialize them, too
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private IEnumerator InitStepLinkSystem()
|
||||
{
|
||||
if (!isInitialized)
|
||||
{
|
||||
if (loadsListsFromChildren)
|
||||
{
|
||||
stepsLists.Clear();
|
||||
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
if (child.GetComponent<StepsListScript>() != null)
|
||||
{
|
||||
Debug.Log("Found StepsList: " + child.name);
|
||||
stepsLists.Add(child.GetComponent<StepsListScript>());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (StepsListScript list in stepsLists)
|
||||
{
|
||||
if (list.GetComponent<StepsListScript>() == null)
|
||||
{
|
||||
stepsLists.Remove(list);
|
||||
Debug.LogWarning("The list: " + list.name + " has not the StepsList attached. And has been removed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stepsLists.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("No Valid StepsList found");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (StepsListScript list in stepsLists)
|
||||
{
|
||||
yield return StartCoroutine(list.InitListSteps());
|
||||
}
|
||||
}
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("This step link system is already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run all the lists in order
|
||||
/// </summary>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void RunAllLists()
|
||||
{
|
||||
if (!isInitialized)
|
||||
{
|
||||
throw new Exception("The step link system is not initialized");
|
||||
}
|
||||
else if (isRunning)
|
||||
{
|
||||
throw new Exception("The step link system is already running a list");
|
||||
}
|
||||
else if (isInitialized && !isRunning)
|
||||
{
|
||||
StartCoroutine(RunAll());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a list by index
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void RunListByIndex(int index)
|
||||
{
|
||||
if (!isInitialized)
|
||||
{
|
||||
throw new Exception("The step link system is not initialized");
|
||||
}
|
||||
else if (isRunning)
|
||||
{
|
||||
throw new Exception("The step link system is already running a list");
|
||||
}
|
||||
else if (isInitialized && !isRunning)
|
||||
{
|
||||
if (index < stepsLists.Count && index >= 0)
|
||||
{
|
||||
StartCoroutine(stepsLists[index].StartSteps());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Invalid index of list, list has " + stepsLists.Count + " elements");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a list by name
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void RunListByName(String name)
|
||||
{
|
||||
if (!isInitialized)
|
||||
{
|
||||
throw new Exception("The step link system is not initialized");
|
||||
}
|
||||
else if (isRunning)
|
||||
{
|
||||
throw new Exception("The step link system is already running a list");
|
||||
}
|
||||
else if (isInitialized && !isRunning)
|
||||
{
|
||||
foreach (StepsListScript list in stepsLists)
|
||||
{
|
||||
if (list.name == name)
|
||||
{
|
||||
StartCoroutine(list.StartSteps());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("No list found with the name: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run all the lists in order
|
||||
/// </summary>
|
||||
private IEnumerator RunAll()
|
||||
{
|
||||
if (isRunning)
|
||||
{
|
||||
throw new Exception("The step link system is already running a list");
|
||||
}
|
||||
else
|
||||
{
|
||||
isRunning = true;
|
||||
|
||||
for (int index = 0; index < stepsLists.Count; index++)
|
||||
{
|
||||
yield return StartCoroutine(stepsLists[index].StartSteps());
|
||||
}
|
||||
|
||||
isRunning = false;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
Assets/Resources/Scripts/StepsLink/StepOrchestrator.cs.meta
Normal file
11
Assets/Resources/Scripts/StepsLink/StepOrchestrator.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6e1d0773fab9f5f488c108e6ca6a5ab0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
239
Assets/Resources/Scripts/StepsLink/StepScript.cs
Normal file
239
Assets/Resources/Scripts/StepsLink/StepScript.cs
Normal file
|
@ -0,0 +1,239 @@
|
|||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
using NaughtyAttributes;
|
||||
|
||||
public class StepScript : MonoBehaviour
|
||||
{
|
||||
[Header("Step Info")]
|
||||
/// <summary>
|
||||
/// Add a description to the step to explain what is supposed to be
|
||||
/// </summary>
|
||||
[Tooltip("Add a description to the step to explain what is supposed to be")]
|
||||
[TextArea(3, 10)]
|
||||
public string description;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the game object is enabled at game start
|
||||
/// </summary>
|
||||
[Tooltip("If true, the game object is visible at game start")]
|
||||
public bool isEnabledBeforeStart = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the game object will stay enabled after the step is completed
|
||||
/// </summary>
|
||||
[Tooltip("If true, the game object will stay visible after the step is completed")]
|
||||
public bool isEnabledAfterEnd = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Refresh rate of the step, used to update the step
|
||||
/// </summary>
|
||||
public float RefreshRate = 0.2f;
|
||||
|
||||
[HorizontalLine(1, EColor.Blue)]
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If true, the step is completed at start, so no other completion event is needed
|
||||
/// </summary>
|
||||
[Tooltip("If true, the step is completed at start, so no other completion event is needed. It can still use pre and post callbacks")]
|
||||
public bool startCompleted = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the step is completed when the item is grabbed it needs the XRGrabInteractable component
|
||||
/// to detect when the item is grabbed. At least one completion event must be set to avoid an infinite loop
|
||||
/// </summary>
|
||||
[Tooltip("If true, the step is completed when the item is grabbed it needs the XRGrabInteractable component " +
|
||||
"to detect when the item is grabbed. At least one completion event must be set to avoid an infinite loop")]
|
||||
[InfoBox("Use this if no other complention events are added", EInfoBoxType.Warning)]
|
||||
[HideIf("startCompleted")]
|
||||
public bool completeOnItemGrabbed = true;
|
||||
|
||||
/// <summary>
|
||||
/// These will be subscribed, so that they will act as completion events
|
||||
/// </summary>
|
||||
//[Tooltip("These will be subscribed, so that they will act as completion events. They can be used with the rab event.")]
|
||||
//[HideIf("startCompleted")]
|
||||
//public UnityEvent StepCompletionEvents;
|
||||
|
||||
[HorizontalLine(1, EColor.Blue)]
|
||||
|
||||
[Header("Callbacks")]
|
||||
|
||||
/// <summary>
|
||||
/// If true, run the specified callbacks when the step starts
|
||||
/// </summary>
|
||||
[Tooltip("If true, run the specified callbacks when the step starts")]
|
||||
public bool useOnStart = false;
|
||||
|
||||
/// <summary>
|
||||
/// Event called when the step starts
|
||||
/// </summary>
|
||||
[ShowIf("useOnStart")]
|
||||
[Tooltip("Event called when the step starts, before the update loop")]
|
||||
public UnityEvent OnStart;
|
||||
|
||||
/// <summary>
|
||||
/// If true, run the specified callbacks when the step is updated
|
||||
/// </summary>
|
||||
[HideIf("startCompleted")]
|
||||
[Tooltip("This run the main callbacks that run until the step is complete by another event. The refresh rate is the same of the HapticManager")]
|
||||
public UnityEvent OnUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// If true, run the specified callbacks when the step is complete
|
||||
/// </summary>
|
||||
[Tooltip("If true, run the specified callbacks when the step is complete")]
|
||||
public bool useOnEnd = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, run the specified callbacks when the step is complete
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ShowIf("useOnEnd")]
|
||||
[Tooltip("If true, run the specified callbacks when the step is complete")]
|
||||
public UnityEvent OnEnd;
|
||||
|
||||
/// <summary>
|
||||
/// If true, once the OnEnd callback are called, a delay is started, this allows to
|
||||
/// run another set of callback after these
|
||||
/// </summary>
|
||||
[Tooltip("If true, once the OnEnd callback are called, a delay is started, this allows to " +
|
||||
"run another set of callback after these")]
|
||||
public bool useDelayedEnd = false;
|
||||
|
||||
/// <summary>
|
||||
/// Delay after the list ends, used to wait for delayed effects
|
||||
/// </summary>
|
||||
[ShowIf("useDelayedEnd")]
|
||||
[MinValue(0.0f), MaxValue(60)]
|
||||
[Tooltip("Delay after the list ends, used to wait for delayed effects, runned before the OnEndPostDelay")]
|
||||
public float endTimeDurationSeconds = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Event called after the list ends, after a delay
|
||||
/// </summary>
|
||||
[ShowIf("useDelayedEnd")]
|
||||
[Tooltip("Event called after the list ends, after a delay")]
|
||||
public UnityEvent OnEndPostDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to check if the step is complete
|
||||
/// </summary>
|
||||
private bool isStepComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the XRGrabInteractable component, used to detect when the item is grabbed
|
||||
/// </summary>
|
||||
private XRGrabInteractable grabInteractable;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if(startCompleted)
|
||||
{
|
||||
completeOnItemGrabbed = false;
|
||||
OnUpdate = null;
|
||||
}
|
||||
|
||||
if (gameObject == null)
|
||||
{
|
||||
throw new Exception("This script cannot access a gameObject");
|
||||
}
|
||||
else if (gameObject.transform == null)
|
||||
{
|
||||
throw new Exception("This script cannot access a gameObject transform");
|
||||
}
|
||||
else if (completeOnItemGrabbed) {
|
||||
grabInteractable = gameObject.GetComponent<XRGrabInteractable>();
|
||||
|
||||
if (grabInteractable == null)
|
||||
{
|
||||
throw new Exception("This script cannot access a XRGrabInteractable component");
|
||||
}
|
||||
}
|
||||
|
||||
gameObject.SetActive(isEnabledBeforeStart);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the step, this is called by the StepsListScript
|
||||
public IEnumerator StepRun()
|
||||
{
|
||||
Debug.Log("Step: " + name + " started");
|
||||
|
||||
isStepComplete = startCompleted;
|
||||
|
||||
gameObject.SetActive(true);
|
||||
|
||||
if (useOnStart)
|
||||
{
|
||||
OnStart.Invoke();
|
||||
}
|
||||
|
||||
if (!startCompleted)
|
||||
{
|
||||
grabInteractable = gameObject.GetComponent<XRGrabInteractable>();
|
||||
SubscribeToCompletionEvents();
|
||||
|
||||
do
|
||||
{
|
||||
OnUpdate.Invoke();
|
||||
// Debug.Log("Step: " + name + " updated");
|
||||
yield return new WaitForSeconds(RefreshRate);
|
||||
} while (!isStepComplete);
|
||||
|
||||
UnSubscribeToCompletionEvents();
|
||||
}
|
||||
|
||||
if (useOnEnd)
|
||||
{
|
||||
OnEnd.Invoke();
|
||||
}
|
||||
|
||||
if (useDelayedEnd)
|
||||
{
|
||||
yield return new WaitForSeconds(endTimeDurationSeconds);
|
||||
OnEndPostDelay.Invoke();
|
||||
}
|
||||
|
||||
gameObject.SetActive(isEnabledAfterEnd);
|
||||
|
||||
Debug.Log("Step complete");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for to set the step as complete from any event that calls it
|
||||
/// </summary>
|
||||
/// <param name="arg0"></param>
|
||||
public void StepCompletionListener(SelectEnterEventArgs arg0)
|
||||
{
|
||||
isStepComplete = true;
|
||||
Debug.Log("Step completed by grabbing an item");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to the completion events
|
||||
/// </summary>
|
||||
private void SubscribeToCompletionEvents()
|
||||
{
|
||||
// subscribe to the grab event, if needed
|
||||
if (completeOnItemGrabbed)
|
||||
{
|
||||
grabInteractable.selectEntered.AddListener(StepCompletionListener);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe to the completion events
|
||||
/// </summary>
|
||||
private void UnSubscribeToCompletionEvents()
|
||||
{
|
||||
if (completeOnItemGrabbed)
|
||||
{
|
||||
grabInteractable.selectEntered.RemoveListener(StepCompletionListener);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Resources/Scripts/StepsLink/StepScript.cs.meta
Normal file
11
Assets/Resources/Scripts/StepsLink/StepScript.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6e1e0970cae255b4baaa25a995c7d2ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
206
Assets/Resources/Scripts/StepsLink/StepsListScript.cs
Normal file
206
Assets/Resources/Scripts/StepsLink/StepsListScript.cs
Normal file
|
@ -0,0 +1,206 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using NaughtyAttributes;
|
||||
|
||||
public class StepsListScript : MonoBehaviour
|
||||
{
|
||||
// Const strings
|
||||
private const string LOG_START_LIST = "Starting list coroutine";
|
||||
|
||||
[Header("Disclaimer")]
|
||||
[InfoBox("This script is part of the StepsLink system. It loads represent a list of steps do launch in order.", EInfoBoxType.Normal)]
|
||||
|
||||
[HorizontalLine(color: EColor.Blue)]
|
||||
|
||||
[Header("Callbacks")]
|
||||
[InfoBox("Callbacks are called before starting the first step, or after ending the last.", EInfoBoxType.Normal)]
|
||||
/// <summary>
|
||||
/// If true, run the callbacks when the list starts
|
||||
/// </summary>
|
||||
[Tooltip("If true, run the callbacks when the list starts")]
|
||||
public bool useOnStart = false;
|
||||
|
||||
/// <summary>
|
||||
/// Event called when the list starts
|
||||
/// </summary>
|
||||
[ShowIf("useOnStart")]
|
||||
[Tooltip("Event called when the list starts")]
|
||||
public UnityEvent OnStart;
|
||||
|
||||
/// <summary>
|
||||
/// If true, run the callbacks when the list ends
|
||||
/// </summary>
|
||||
[Tooltip("If true, run the callbacks when the list ends")]
|
||||
public bool useOnEnd = false;
|
||||
|
||||
/// <summary>
|
||||
/// Event called when the list ends
|
||||
/// </summary>
|
||||
[Tooltip("Event called when the list ends")]
|
||||
[ShowIf("useOnEnd")]
|
||||
public UnityEvent OnEnd;
|
||||
|
||||
/// <summary>
|
||||
/// If true, once the OnEnd callback are called, a delay is started, this allows to
|
||||
/// run another set of callback after these
|
||||
/// </summary>
|
||||
[Tooltip("If true, once the OnEnd callback are called, a delay is started, this allows to " +
|
||||
"run another set of callback after these")]
|
||||
public bool useDelayedEnd = false;
|
||||
|
||||
/// <summary>
|
||||
/// Delay after the list ends, used to wait for delayed effects
|
||||
/// </summary>
|
||||
[ShowIf("useDelayedEnd")]
|
||||
[MinValue(0.0f), MaxValue(60)]
|
||||
[Tooltip("Delay after the list ends, used to wait for delayed effects, runned before the OnEndPostDelay")]
|
||||
public float endTimeDurationSeconds = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Event called after the list ends, after a delay
|
||||
/// </summary>
|
||||
[ShowIf("useDelayedEnd")]
|
||||
[Tooltip("Event called after the list ends, after a delay")]
|
||||
public UnityEvent OnEndPostDelay;
|
||||
|
||||
[HorizontalLine(color: EColor.Blue)]
|
||||
|
||||
/// <summary>
|
||||
/// If true, the list of steps will be loaded from the children of this gameObject
|
||||
/// else, the list of steps must be added manually
|
||||
/// </summary>
|
||||
[Header("How to load steps?")]
|
||||
[InfoBox("To be loaded, a step must have StepScript component", EInfoBoxType.Normal)]
|
||||
[Tooltip("If true, the list of steps will be loaded from the children of this gameObject")]
|
||||
public bool useChildrenAsSteps = true;
|
||||
|
||||
/// <summary>
|
||||
/// List of steps to run, a step must have the StepScript component attached
|
||||
/// </summary>
|
||||
[InfoBox("Childen of this list will be ignored. All steps must be added manually", EInfoBoxType.Normal)]
|
||||
[Tooltip("List of steps to run, manually linked from any position")]
|
||||
[HideIf("useChildrenAsSteps")]
|
||||
public List<StepScript> stepsList = new();
|
||||
|
||||
/// <summary>
|
||||
/// True if the list is completed, after the StartSteps() coroutine is completed
|
||||
/// </summary>
|
||||
private bool isCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// True if the list is completed, after the StartSteps() coroutine is completed
|
||||
/// </summary>
|
||||
internal bool IsCompleted { get => isCompleted; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the list is initialized, after the InitListSteps() coroutine is completed
|
||||
/// <see cref="InitListSteps"/>"
|
||||
/// </summary>
|
||||
private bool initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// True if the list is initialized, after the InitListSteps() coroutine is completed
|
||||
/// </summary>
|
||||
public bool Initialized { get => initialized; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of steps in the list
|
||||
/// </summary>
|
||||
private int StepsCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Number of steps in the list
|
||||
/// </summary>
|
||||
public int GetStepsCount { get => StepsCount; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the list of steps. This is called by the StepOrchestrator to initialize the list
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public IEnumerator InitListSteps()
|
||||
{
|
||||
isCompleted = false;
|
||||
|
||||
// get all the children ojjects that have the Step script attached
|
||||
if (useChildrenAsSteps)
|
||||
{
|
||||
stepsList.Clear();
|
||||
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
if (child.GetComponent<StepScript>() != null)
|
||||
{
|
||||
stepsList.Add(child.GetComponent<StepScript>());
|
||||
}
|
||||
|
||||
if (stepsList.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("No steps found in the children of the list: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (stepsList.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("No steps found in the list: " + name);
|
||||
}
|
||||
else if (stepsList.Count > 0)
|
||||
{
|
||||
foreach (StepScript step in stepsList)
|
||||
{
|
||||
Debug.Log("Loading manual list and filtering steps in the list: " + name);
|
||||
|
||||
// check if there is a StepScript component attached to every stel added
|
||||
if (step.GetComponent<StepScript>() == null)
|
||||
{
|
||||
throw new Exception("The step: " + step.name + " has not the StepScript attached");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StepsCount = stepsList.Count;
|
||||
|
||||
initialized = true;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the list of steps. This is called by the StepOrchestrator when the list is ready
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator StartSteps()
|
||||
{
|
||||
Debug.Log(LOG_START_LIST + ": " + name);
|
||||
|
||||
if (useOnStart)
|
||||
{
|
||||
OnStart.Invoke();
|
||||
}
|
||||
|
||||
foreach (StepScript step in stepsList)
|
||||
{
|
||||
// wait for the step to complete
|
||||
yield return StartCoroutine(step.StepRun());
|
||||
}
|
||||
|
||||
if (useOnEnd)
|
||||
{
|
||||
OnEnd.Invoke();
|
||||
}
|
||||
|
||||
if (useDelayedEnd)
|
||||
{
|
||||
yield return new WaitForSeconds(endTimeDurationSeconds);
|
||||
OnEndPostDelay.Invoke();
|
||||
}
|
||||
|
||||
Debug.Log("List: " + name + " completed");
|
||||
|
||||
|
||||
isCompleted = true;
|
||||
}
|
||||
}
|
11
Assets/Resources/Scripts/StepsLink/StepsListScript.cs.meta
Normal file
11
Assets/Resources/Scripts/StepsLink/StepsListScript.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 546aed1e34360c54090c6b42c25a3808
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue