// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.UI; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; namespace Microsoft.MixedReality.Toolkit.Input { /// /// This component handles the speech input events raised form the . /// [DisallowMultipleComponent] [AddComponentMenu("Scripts/MRTK/SDK/SpeechInputHandler")] public class SpeechInputHandler : BaseInputHandler, IMixedRealitySpeechHandler { /// /// The keywords to be recognized and optional keyboard shortcuts. /// public KeywordAndResponse[] Keywords => keywords; [SerializeField] [Tooltip("The keywords to be recognized and optional keyboard shortcuts.")] private KeywordAndResponse[] keywords = Array.Empty(); [SerializeField] [Tooltip("Keywords are persistent across all scenes. This Speech Input Handler instance will not be destroyed when loading a new scene.")] private bool persistentKeywords = false; [SerializeField] [Tooltip("Assign SpeechConfirmationTooltip.prefab here to display confirmation label. Optional.")] private SpeechConfirmationTooltip speechConfirmationTooltipPrefab = null; /// /// Tooltip prefab used to display confirmation label. Optional. /// public SpeechConfirmationTooltip SpeechConfirmationTooltipPrefab { get { return speechConfirmationTooltipPrefab; } set { speechConfirmationTooltipPrefab = value; } } private SpeechConfirmationTooltip speechConfirmationTooltipPrefabInstance = null; private readonly Dictionary responses = new Dictionary(); #region MonoBehaviour Implementation protected override void Start() { base.Start(); if (persistentKeywords) { Debug.Assert(gameObject.transform.parent == null, "Persistent keyword GameObject must be at the root level of the scene hierarchy."); DontDestroyOnLoad(gameObject); } // Convert the struct array into a dictionary, with the keywords and the methods as the values. // This helps easily link the keyword recognized to the UnityEvent to be invoked. int keywordCount = keywords.Length; for (int index = 0; index < keywordCount; index++) { KeywordAndResponse keywordAndResponse = keywords[index]; string keyword = keywordAndResponse.Keyword.ToLower(); if (responses.ContainsKey(keyword)) { Debug.LogError($"Duplicate keyword \'{keyword}\' specified in \'{gameObject.name}\'."); } else { responses.Add(keyword, keywordAndResponse.Response); } } } #endregion MonoBehaviour Implementation #region InputSystemGlobalHandlerListener Implementation protected override void RegisterHandlers() { CoreServices.InputSystem?.RegisterHandler(this); } protected override void UnregisterHandlers() { CoreServices.InputSystem?.UnregisterHandler(this); } #endregion InputSystemGlobalHandlerListener Implementation #region SpeechInputHandler public methods public void AddResponse(string keyword, UnityAction action) { string lowerKeyword = keyword.ToLower(); if (!responses.ContainsKey(lowerKeyword)) { responses[lowerKeyword] = new UnityEvent(); } responses[lowerKeyword].AddListener(action); } public void RemoveResponse(string keyword, UnityAction action) { string lowerKeyword = keyword.ToLower(); if (responses.ContainsKey(lowerKeyword)) { responses[lowerKeyword].RemoveListener(action); } } #endregion SpeechInputHandler public methods #region IMixedRealitySpeechHandler Implementation void IMixedRealitySpeechHandler.OnSpeechKeywordRecognized(SpeechEventData eventData) { // early return if the script is already heading for destruction if (this.IsNull()) { return; } // Check to make sure the recognized keyword exists in the methods dictionary, then invoke the corresponding method. if (enabled && responses.TryGetValue(eventData.Command.Keyword.ToLower(), out UnityEvent keywordResponse)) { keywordResponse.Invoke(); eventData.Use(); // Instantiate the Speech Confirmation Tooltip prefab if assigned // Ignore "Select" keyword since OS will display the tooltip. if (SpeechConfirmationTooltipPrefab != null && speechConfirmationTooltipPrefabInstance == null && !eventData.Command.Keyword.Equals("select", StringComparison.CurrentCultureIgnoreCase)) { speechConfirmationTooltipPrefabInstance = Instantiate(speechConfirmationTooltipPrefab); // Update the text label with recognized keyword speechConfirmationTooltipPrefabInstance.SetText(eventData.Command.Keyword); // Trigger animation of the Speech Confirmation Tooltip prefab speechConfirmationTooltipPrefabInstance.TriggerConfirmedAnimation(); // Tooltip prefab instance will be destroyed on animation complete // by DestroyOnAnimationComplete.cs in the SpeechConfirmationTooltip.prefab } } } #endregion IMixedRealitySpeechHandler Implementation } }