first commit

This commit is contained in:
Stefano Rossi 2025-07-12 18:59:18 +02:00
commit aa44d3ad70
Signed by: chadmin
GPG key ID: 9EFA2130646BC893
1585 changed files with 277994 additions and 0 deletions

View file

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

View 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);
}
}

View file

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

View 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);
}
}
}
}

View file

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

View 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;
}
}
}

View file

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

View 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);
}
}
}

View file

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

View 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;
}
}

View file

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