đź‘€ Stumbled here on accident? Start with the introduction!
📚 The objective of this article is to animate the door in response to the voice command “open the door”. To achieve this, we will develop an animation specifically for the door and make necessary updates to the Player and OpenDoorConduit scripts. This integration of voice command functionality with animation will enhance the interactivity and realism of our application.
ℹ️ If you find yourself facing any difficulties, remember that you can always refer to or download the code from our accompanying GitHub repository
Lets start with the animation. Edit our Door Prefab by double-clicking it within the Project window. Select the Door mesh as seen in the next screenshot.
Now add the component Animator via Add Component.
With the Door mesh still selected, navigate to Window → Animation → Animation to open the animation window in Unity. Once the animation window is open, click on the Create button. You will then be prompted to choose a location for saving the Animation Clip. Save it in a new folder Assets/Animations and name it DoorAnimation.
The next step involves adding a property to animate. As we are crafting an “opening the door” animation, we need to animate the Transform rotation. To do this, click on Add Property, and then select Transform → Rotation. This action allows us to specifically target and animate the door's rotation, creating a realistic opening effect in our animation sequence.
First move the Timeline to 60. You can either drag and drop the indicator or enter 60 on the left side of the Timeline. Then, enter -120 for the Rotation.y value.
In your Scene window the door should now look as follows:
When creating an animation in Unity, the default setting typically causes the animation to loop continuously. However, since our objective is to trigger the door animation just once upon recognizing the correct voice command, we need to adjust the looping behavior in the Door Animation Controller.
To make this change, navigate to Assets/Animations in the Unity Editor and double-click on the Door Animation controller. This action will open the Animator window with the Door.controller loaded. In the Animator window, we can modify the settings to ensure that the door animation plays only once instead of looping endlessly.
Right-click on an empty space within the Animator window and select Create State -> Empty, as shown in the upcoming screenshot. This step will add a new, empty state to the animation controller.
Right-click on the newly created state, named New State, and select Set as Layer Default State. This action will designate the New State as the default state for that particular layer in the animation controller, ensuring that this is the starting state when the animation sequence begins.
The result should be as follows: Since the state is empty and no properties have been added to it, this results in the absence of any animation when the GameObject loads. This setup ensures that the door remains static initially, allowing for the animation to be triggered specifically by an event, such as a voice command, rather than starting automatically upon loading.
Now, select the DoorAnimation animation clip in your Projectwindow. Then, in the inspector, uncheck Loop Time.
We are now good to go to trigger the animation in our MRArticleSeriesController script.
using System.Collections;
using System.Collections.Generic;
using Meta.WitAi;
using Meta.WitAi.Requests;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.Interaction.Toolkit;
namespace Taikonauten.Unity.ArticleSeries
{
public class MRArticleSeriesController : MonoBehaviour
{
[SerializeField] private ARAnchorManager anchorManager;
[SerializeField] private GameObject door;
[SerializeField] private GameObject uI;
[SerializeField] private InputActionReference buttonActionLeft;
[SerializeField] private InputActionReference buttonActionRight;
[SerializeField] private VoiceService voiceService;
[SerializeField] private XRRayInteractor rayInteractor;
private VoiceServiceRequest voiceServiceRequest;
private VoiceServiceRequestEvents voiceServiceRequestEvents;
private GameObject doorInstance;
void OnEnable()
{
Debug.Log("MRArticleSeriesController -> OnEnable()");
buttonActionRight.action.performed += OnButtonPressedRightAsync;
buttonActionLeft.action.performed += OnButtonPressedLeft;
}
void OnDisable()
{
Debug.Log("MRArticleSeriesController -> OnDisable()");
buttonActionRight.action.performed -= OnButtonPressedRightAsync;
buttonActionLeft.action.performed -= OnButtonPressedLeft;
}
private void ActivateVoiceService()
{
Debug.Log("MRArticleSeriesController -> ActivateVoiceService()");
if (voiceServiceRequestEvents == null)
{
voiceServiceRequestEvents = new VoiceServiceRequestEvents();
voiceServiceRequestEvents.OnInit.AddListener(OnInit);
voiceServiceRequestEvents.OnComplete.AddListener(OnComplete);
}
voiceServiceRequest = voiceService.Activate(voiceServiceRequestEvents);
}
private void DeactivateVoiceService()
{
Debug.Log("MRArticleSeriesController -> DeactivateVoiceService()");
voiceServiceRequest.DeactivateAudio();
}
private void OnInit(VoiceServiceRequest request)
{
uI.SetActive(true);
}
private void OnComplete(VoiceServiceRequest request)
{
uI.SetActive(false);
DeactivateVoiceService();
}
private async void OnButtonPressedRightAsync(InputAction.CallbackContext context)
{
Debug.Log("MRArticleSeriesController -> OnButtonPressedRightAsync()");
if (doorInstance != null)
{
Debug.Log("MRArticleSeriesController -> OnButtonPressedRightAsync(): Door already instantiated");
return;
}
if (rayInteractor.TryGetCurrent3DRaycastHit(out RaycastHit hit))
{
Pose pose = new(hit.point, Quaternion.identity);
Result<ARAnchor> result = await anchorManager.TryAddAnchorAsync(pose);
result.TryGetResult(out ARAnchor anchor);
if (anchor != null)
{
// Instantiate the door Prefab
doorInstance = Instantiate(door, hit.point, Quaternion.identity);
// Unity recommends parenting your content to the anchor.
doorInstance.transform.parent = anchor.transform;
}
}
}
private void OnButtonPressedLeft(InputAction.CallbackContext context)
{
Debug.Log("MRArticleSeriesController -> OnButtonPressedLeft()");
ActivateVoiceService();
}
public void OpenDoor()
{
Debug.Log("MRArticleSeriesController -> OpenDoor()");
if (doorInstance == null)
{
Debug.Log("MRArticleSeriesController -> OpenDoor(): no door instantiated yet.");
return;
}
Animator animator = doorInstance.GetComponentInChildren<Animator>();
animator.Play("DoorAnimation", -1, 0);
}
}
}
Let's go over the modifications made to the MRArticleSeriesController script:
- Added
private GameObject doorInstance;This variable holds the reference to the instantiated door in the scene. -
OnButtonPressedRightAsync: In this method, we now check if a door has already been instantiated in the scene. If a door is present, the method returns early to prevent another door from being instantiated. -
OpenDoor: This public method will be called from theOpenDoorConduit. It triggers the door's animation usinganimator.Play, initiating the opening sequence of the door.
These changes enhance the functionality of the Player script, ensuring proper management and control of the door animation in response to user interactions and voice commands.
Now, edit the OpenDoorConduit script.
using System.Collections;
using System.Collections.Generic;
using Meta.WitAi;
using UnityEngine;
namespace Taikonauten.Unity.ArticleSeries
{
public class OpenDoorConduit : MonoBehaviour
{
[SerializeField] private MRArticleSeriesController mRArticleSeriesController;
private const string OPEN_DOOR_INTENT = "open_door";
[MatchIntent(OPEN_DOOR_INTENT)]
public void OpenDoor(string[] values)
{
Debug.Log("OpenDoorConduit -> OpenDoor()");
string action = values[0];
string entity = values[1];
if (!string.IsNullOrEmpty(action) && !string.IsNullOrEmpty(entity))
{
if (action == "open" && entity == "door") {
mRArticleSeriesController.OpenDoor();
}
}
}
}
}
Let's go over the modifications made to the OpenDoorConduit script:
-
[SerializeField] private MRArticleSeriesController mRArticleSeriesController;This line declares a private variable and marks it with[SerializeField]so it can be assigned via the Unity Editor. This variable holds the reference to our Player component, allowing the script to interact with the Player component's public methods and properties. -
OpenDoor: In this part of the script, we invoke theOpenDoorpublic method of the Player script which starts the door animation.
These modifications are essential for enabling communication and interaction between different components and scripts in our Unity project, particularly for handling the door-opening functionality.
Make sure to select the MRArticleSeriesController component in the inspector:
Testing the app
We are now prepared to test the app. Select Build and Run.
- Press the trigger on the left controller.
- The label indicated "...Listening...".
- Speak the word phrase “open the door”.
- After a brief delay the door animation should now be playing.
Next article
In the next article, we will focus on enhancing the visual appeal of our scene. This will include implementing graphic improvements, adding a scene to be displayed behind the door, and disabling the plane material, which was previously used solely for debugging purposes. These refinements are aimed at elevating the aesthetic quality and immersive experience of our application, making it more engaging and visually appealing to users.












Top comments (0)