using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.XR.Interaction.Toolkit; namespace _PROJECT.NewHandPresence { public class TutorialController : MonoBehaviour { private enum State { Initializing, Turn, Move, Teleport, WaitForGrip, Grip, Done } public ActionBasedController leftHand; public ActionBasedController rightHand; public ActionBasedContinuousMoveProvider moveProvider; public ActionBasedSnapTurnProvider turnProvider; public TeleportationProvider teleportProvider; public GameObject billboardTemplate; private XRControllerHintController _leftHintController; private XRControllerHintController _rightHintController; private SmartHandPresence _leftSmartHandPresence; private SmartHandPresence _rightSmartHandPresence; private State _state = State.Initializing; private Camera _camera; private List _grabInteractables = new List(); private XRGrabInteractable _grabInteractable; private GameObject _billboard; private void Update() { if (_state == State.Initializing) { TryInitialize(); } if (_state == State.WaitForGrip) { TryFindXRGrabInteractable(); } } private void TryFindXRGrabInteractable() { // Find closest interactable in list XRGrabInteractable closestInteractable = null; var closestDistance = float.MaxValue; foreach (var grabInteractable in _grabInteractables) { var distance = Vector3.Distance(grabInteractable.transform.position, _camera.transform.position); if (!(distance < closestDistance)) continue; if (grabInteractable.CompareTag("Door")) continue; closestDistance = distance; closestInteractable = grabInteractable; } if (closestInteractable == null) return; _grabInteractable = closestInteractable; UpdateState(_state.Next()); } private void UpdateState(State newState) { _state = newState; Debug.Log($"Tutorial state: {_state}"); if (_state == State.Initializing) return; SetHandsVisibility(true); _rightHintController.HideHint(); _leftHintController.HideHint(); switch (_state) { case State.Initializing: break; case State.Turn: ShowTurnHint(); break; case State.Move: ShowLocomotionHint(); break; case State.Teleport: ShowTeleportHint(); break; case State.WaitForGrip: SetHandsVisibility(true); break; case State.Grip: SetHandsVisibility(false); CreateBillboard(_grabInteractable.gameObject, "Grab me!"); ShowGripHint(); break; case State.Done: SetHandsVisibility(true); DestroyBillboard(); break; default: throw new ArgumentOutOfRangeException(); } } private void CreateBillboard(GameObject gmeObject, string gripMe) { var number = 1 / gmeObject.transform.localScale.x; // Create billboard as a child on grab interactable _billboard = Instantiate(billboardTemplate, gmeObject.transform); // Set local position above grab interactable _billboard.transform.localPosition = new Vector3(0, 1, 0); // Unscale billboard relative to parent _billboard.transform.localScale = Vector3.one * number; // Get ControllerTextHint on billboard var controllerTextHint = _billboard.GetComponentInChildren(); // Set text controllerTextHint.defaultText = gripMe; controllerTextHint.ShowHint(gripMe); // Set interactable to outlined layer //gmeObject.layer = LayerMask.NameToLayer("Outlined Objects"); } private void DestroyBillboard() { Destroy(_billboard); } private bool CanInitialize() { if (leftHand == null || rightHand == null) { Debug.Log("Left or right hand is null"); return false; } if (turnProvider.rightHandSnapTurnAction.action == null) { Debug.Log("Right hand snap turn action is null"); return false; } if (moveProvider.leftHandMoveAction.action == null) { Debug.Log("Left hand move action is null"); return false; } if (teleportProvider == null) { Debug.Log("Teleport provider is null"); return false; } return true; } private void TryInitialize() { if (!CanInitialize()) return; _camera = Camera.main; Debug.Log("Initializing tutorial"); _leftHintController = leftHand.GetComponentInChildren(); _rightHintController = rightHand.GetComponentInChildren(); Debug.Log($"Left hint controller: {_leftHintController}"); Debug.Log($"Right hint controller: {_rightHintController}"); _leftSmartHandPresence = leftHand.GetComponentInChildren(); _rightSmartHandPresence = rightHand.GetComponentInChildren(); Debug.Log($"Left smart hand presence: {_leftSmartHandPresence}"); Debug.Log($"Right smart hand presence: {_rightSmartHandPresence}"); if (_leftHintController == null || _rightHintController == null || _leftSmartHandPresence == null || _rightSmartHandPresence == null) { Debug.Log("Hint controller or smart hand presence is null"); return; } _leftHintController.xrBaseController = leftHand; _rightHintController.xrBaseController = rightHand; // Setup action listeners turnProvider.rightHandSnapTurnAction.action.performed += OnTurnPerformed; moveProvider.leftHandMoveAction.action.performed += OnMovePerformed; teleportProvider.endLocomotion += OnTeleportPerformed; leftHand.GetComponent().selectEntered.AddListener(OnGripPerformed); rightHand.GetComponent().selectEntered.AddListener(OnGripPerformed); UpdateState(_state.Next()); Debug.Log("Tutorial initialized"); } private void OnGripPerformed(SelectEnterEventArgs arg0) { if (_state != State.Grip) return; Debug.Log("Grip performed"); UpdateState(_state.Next()); } private void OnTeleportPerformed(LocomotionSystem obj) { if (_state != State.Teleport) return; Debug.Log("Teleport performed"); UpdateState(_state.Next()); } private void OnMovePerformed(InputAction.CallbackContext obj) { if (_state != State.Move) return; Debug.Log("Move performed"); UpdateState(_state.Next()); } private void OnTurnPerformed(InputAction.CallbackContext obj) { if (_state != State.Turn) return; Debug.Log("Turn performed"); UpdateState(_state.Next()); } private void ShowTeleportHint() { _rightHintController.ShowTeleportHint(); _leftHintController.ShowTeleportHint(); } private void ShowTurnHint() { _rightHintController.ShowTurnHint(); _leftHintController.ShowTurnHint(); } private void ShowLocomotionHint() { _rightHintController.ShowSmoothLocomotionHint(); _leftHintController.ShowSmoothLocomotionHint(); } private void ShowGripHint() { _rightHintController.ShowGripHint(); _leftHintController.ShowGripHint(); } private void SetControllerVisibility(bool visible) { _leftSmartHandPresence.showController = visible; _rightSmartHandPresence.showController = visible; } private void SetHandsVisibility(bool visible) { _leftSmartHandPresence.showHand = visible; _rightSmartHandPresence.showHand = visible; } private void OnTriggerEnter(Collider other) { // If has component add to list var grabInteractable = other.GetComponent(); if (grabInteractable != null) { _grabInteractables.Add(grabInteractable); } } private void OnTriggerExit(Collider other) { // If has component remove from list var grabInteractable = other.GetComponent(); if (grabInteractable != null) { _grabInteractables.Remove(grabInteractable); } } } }