シーン内にオブジェクトを配置していきたいと思います。
1. 土台の配置
[Hierarchy]->[Create]->[3D Object]->[Plane]で土台を作り、[Inspector]->[Transform]を編集していきます。
—————————————–
Position | X : 0 | Y : -1 | Z : 0
—————————————–
2. 球の配置
[Hierarchy]->[Create]->[3D Object]->[Sphere]で球を作り、[Inspector]->[Transform]を編集していきます。
—————————————–
Position | X : 2 | Y : 1 | Z : 2
—————————————–
3. シリンダーの配置
[Hierarchy]->[Create]->[3D Object]->[Cylinder]でシリンダーを作り、[Inspector]->[Transform]を編集していきます。
—————————————–
Position | X : -2 | Y : 1 | Z : 2
—————————————–
4. 正方形の配置
[Hierarchy]->[Create]->[3D Object]->[Cube]で正方形を作り、[Inspector]->[Transform]を編集していきます。
Position | X : 0 | Y : 1 | Z : 4
Rotation | X : 45 | Y : 45 | Z : 45

5. テキストの配置
a. [Hierarchy]->[Create]->[3D Object]->[Text]でテキストを作り、名前をDictationTextとします。[Inspector]->[Transform]を編集していきます。
Position | X : -2 | Y : 6 | Z : 9
Scale | X : 0.1 | Y : 0.1 | Z : 0.1
b. [Inspector]内のその他の編集を行っていきます。
5. カメラまわりの編集
デフォルトで備わっているMain Cameraの代わりにHoloToolkitを使います。使うものを以下のリンクからダウンロードしてください。
HoloToolkit-Unity-2017.4.2.0.unitypackage
HoloToolkit-Unity-Example-2017.4.2.0.unitypackage
a. [Assets]->[Import Package]->[Custom Package..]からダウンロードしたpackageを二つともインポートします。
b. [Hierarchy]->[Main camera]をdeleteします。
c. [Project]から以下の三つを検索し、[Hierarchy]パネルへD&Dします。
・MixedRealityCameraParent
・DefaultCursor
・MixedRealityCameraParent
・DefaultCursor
・InputManger
d. [Hierarchy]->[MixedRealityCameraParent]->[MixedRealityCamera]の[Inspector]を編集していきます。
[Camera]
Clear Flags | Solid Color
Clear Flags | color
Near clip | 0.2
Clear Flags | color
e. [Hierarchy]->[Input Manager]の[Inspector]を編集していきます。[Simple Single Pointer Selector]のCursorへ[Hierarchy]->[Default Cursor]をアタッチします。
f. [Hierarchy]->[MixedRealityCameraParent]->[MixedRealityCamera]の[Inspector]->[Add Component]からAudioSourceを検索しこれを追加します。
この章で設定した[Inspector]->[Transform]は目安ですので、より見やすいように編集してください。
ここでは以下の4つのスクリプトを作っていきたいと思います。
・MicrophoneManagerLuis
・LuisManager
・Behaviours
・Gaze
スクリプトの作り方はまず、すべてのスクリプトをまとめておくフォルダーを作成します。[Project]->[Create]をクリックし、[Folder]を選択して新しいフォルダーを作ります。名前をScriptsとします。続いて[Project]->[Create]->[C# Script]をクリックし、クラス名をつけていきます。
このクラスはMicrophoneで音声をキャプチャし、そのキャプチャした音声をテキストにするクラスです。聞いた音声を順に文字におこしていきます。今回は簡単のため「change」と聞き取ったらテキストを「The color of the cylinder must be red」として使用することにします。また、しゃべった言葉を直接使う方法については後述します。喋り始めてから一度止まるまでの音声がテキストになります。コードは以下の通りです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows.Speech;
public class MicrophoneManagerLuis : MonoBehaviour
{
public static MicrophoneManagerLuis instance; //help to access instance of this object
private int frequency = 44100; //recording frequency of mic
private AudioSource audioSource; //AudioSource component, provides access to mic
private DictationRecognizer dictationRecognizer; //Component converting speech to text
public TextMesh dictationText; //a UI object used to debug dictation result
private void Awake()
{
// allows this class instance to behave like a singleton
instance = this;
}
// Use this for initialization
void Start()
{
if (Microphone.devices.Length > 0)
{
// Once the scene starts, begin to capture the audio
audioSource = GetComponent<AudioSource>();
Debug.Log("Mic Detected");
StartCapturingAudio();
}
}
/// <summary>
/// Start microphone capture, by providing the microphone as a continual audio source (looping),
/// then initialise the DictationRecognizer, which will capture spoken words
/// </summary>
public void StartCapturingAudio()
{
dictationRecognizer = new DictationRecognizer();
dictationRecognizer.DictationHypothesis += DictationRecognizer_DictationHypothesis;
dictationRecognizer.Start();
Debug.Log("Capturing Audio...");
}
private void DictationRecognizer_DictationHypothesis(string dictationCaptured)
{
Debug.Log("text:" + dictationCaptured);
if(dictationCaptured == "change")
{
dictationCaptured = "The color of the cylinder must be red";
Debug.Log("text translation:" + dictationCaptured);
StartCoroutine(LuisManager.instance.SubmitRequestToLuis(dictationCaptured));
dictationText.text = dictationCaptured;
}
}
}
喋った言葉を直接使いたい場合は上のコードの以下の部分を変更します。
private void DictationRecognizer_DictationHypothesis(string dictationCaptured)
{
Debug.Log("text:" + dictationCaptured);
if(dictationCaptured == "change")
{
dictationCaptured = "The color of the cylinder must be red";
Debug.Log("text translation:" + dictationCaptured);
StartCoroutine(LuisManager.instance.SubmitRequestToLuis(dictationCaptured));
dictationText.text = dictationCaptured;
}
}
の部分を
private void DictationRecognizer_DictationHypothesis(string dictationCaptured)
{
Debug.Log("text:" + dictationCaptured);
StartCoroutine(LuisManager.instance.SubmitRequestToLuis(dictationCaptured));
dictationText.text = dictationCaptured;
}
とします。
・LuisManager
このクラスのやることはMicrophoneManagerLuisクラスからテキストを受け取り、Azure Language Understanding APIにテキストを送ることです。ここで2.準備(2)で取得したEndpointを使用します。–Insert your Endpoint–にEndpointを挿入してください。コードは以下の通りです。
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
public class LuisManager : MonoBehaviour
{
[System.Serializable] //this class represents the LUIS response
public class AnalysedQuery
{
public TopScoringIntentData topScoringIntent;
public EntityData[] entities;
public string query;
}
// This class contains the Intent LUIS determines
// to be the most likely
[System.Serializable]
public class TopScoringIntentData
{
public string intent;
public float score;
}
// This class contains data for an Entity
[System.Serializable]
public class EntityData
{
public string entity;
public string type;
public int startIndex;
public int endIndex;
public float score;
}
public static LuisManager instance;
//Substitute the value of luis Endpoint with your own End Point
string luisEndpoint = " --Insert your Endpoint --";
private void Awake()
{
// allows this class instance to behave like a singleton
instance = this;
}
/// <summary>
/// Call LUIS to submit a dictation result.
/// </summary>
public IEnumerator SubmitRequestToLuis(string dictationResult)
{
WWWForm webForm = new WWWForm();
string queryString;
queryString = string.Concat(Uri.EscapeDataString(dictationResult));
using (UnityWebRequest unityWebRequest = UnityWebRequest.Get(luisEndpoint + queryString))
{
unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
yield return unityWebRequest.SendWebRequest();
long responseCode = unityWebRequest.responseCode;
try
{
using (Stream stream = GenerateStreamFromString(unityWebRequest.downloadHandler.text))
{
StreamReader reader = new StreamReader(stream);
AnalysedQuery analysedQuery = new AnalysedQuery();
analysedQuery = JsonUtility.FromJson<AnalysedQuery>(unityWebRequest.downloadHandler.text);
//analyse the elements of the response
AnalyseResponseElements(analysedQuery);
}
}
catch (Exception exception)
{
Debug.Log("Luis Request Exception Message: " + exception.Message);
}
yield return null;
}
}
public static Stream GenerateStreamFromString(string receivedString)
{
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(receivedString);
writer.Flush();
stream.Position = 0;
return stream;
}
private void AnalyseResponseElements(AnalysedQuery aQuery)
{
string topIntent = aQuery.topScoringIntent.intent;
// Create a dictionary of entities associated with their type
Dictionary<string, string> entityDic = new Dictionary<string, string>();
foreach (EntityData ed in aQuery.entities)
{
entityDic.Add(ed.type, ed.entity);
}
// Depending on the topmost recognised intent, read the entities name
switch (aQuery.topScoringIntent.intent)
{
case "ChangeObjectColor":
string targetForColor = null;
string color = null;
foreach (var pair in entityDic)
{
if (pair.Key == "target")
{
targetForColor = pair.Value;
}
else if (pair.Key == "color")
{
color = pair.Value;
}
}
Behaviours.instance.ChangeTargetColor(targetForColor, color);
break;
case "ChangeObjectSize":
string targetForSize = null;
foreach (var pair in entityDic)
{
if (pair.Key == "target")
{
targetForSize = pair.Value;
}
}
if (entityDic.ContainsKey("upsize") == true)
{
Behaviours.instance.UpSizeTarget(targetForSize);
}
else if (entityDic.ContainsKey("downsize") == true)
{
Behaviours.instance.DownSizeTarget(targetForSize);
}
break;
}
}
}
・Behaviours
このクラスは適切なアクションを起こすためのクラスです。変更するオブジェクト、色を指定します。コードは以下の通りです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Behaviours : MonoBehaviour
{
public static Behaviours instance;
// the following variables are references to possible targets
public GameObject sphere;
public GameObject cylinder;
public GameObject cube;
internal GameObject gazedTarget;
void Awake()
{
// allows this class instance to behave like a singleton
instance = this;
}
/// <summary>
/// Changes the color of the target GameObject by providing the name of the object
/// and the name of the color
/// </summary>
public void ChangeTargetColor(string targetName, string colorName)
{
GameObject foundTarget = FindTarget(targetName);
if (foundTarget != null)
{
Debug.Log("color = " + colorName + ", target = " + foundTarget.name);
switch (colorName)
{
case "blue":
Debug.Log("change blue");
foundTarget.GetComponent<Renderer>().material.color = Color.blue;
break;
case "red":
Debug.Log("change red");
foundTarget.GetComponent<Renderer>().material.color = Color.red;
break;
case "yellow":
Debug.Log("change yellow");
foundTarget.GetComponent<Renderer>().material.color = Color.yellow;
break;
case "green":
Debug.Log("change green");
foundTarget.GetComponent<Renderer>().material.color = Color.green;
break;
case "white":
Debug.Log("change white");
foundTarget.GetComponent<Renderer>().material.color = Color.white;
break;
case "black":
Debug.Log("change black");
foundTarget.GetComponent<Renderer>().material.color = Color.black;
break;
}
}
}
/// <summary>
/// Reduces the size of the target GameObject by providing its name
/// </summary>
public void DownSizeTarget(string targetName)
{
GameObject foundTarget = FindTarget(targetName);
foundTarget.transform.localScale -= new Vector3(0.5F, 0.5F, 0.5F);
}
/// <summary>
/// Increases the size of the target GameObject by providing its name
/// </summary>
public void UpSizeTarget(string targetName)
{
GameObject foundTarget = FindTarget(targetName);
foundTarget.transform.localScale += new Vector3(0.5F, 0.5F, 0.5F);
}
/// <summary>
/// Determines which obejct reference is the target GameObject by providing its name
/// </summary>
private GameObject FindTarget(string name)
{
GameObject targetAsGO = null;
switch (name)
{
case "sphere":
targetAsGO = sphere;
break;
case "cylinder":
targetAsGO = cylinder;
break;
case "cube":
Debug.Log("change cube");
targetAsGO = cube;
break;
case "this": // as an example of target words that the user may use when looking at an object
case "it": // as this is the default, these are not actually needed in this example
case "that":
default: // if the target name is none of those above, check if the user is looking at something
if (gazedTarget != null)
{
targetAsGO = gazedTarget;
}
break;
}
return targetAsGO;
}
}
・Gaze
このクラスはユーザーの視点を更新するクラスです。コードは以下の通りです。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Experimental.XR;
using UnityEngine.UI;
using UnityEngine.XR;
using UnityEngine.XR.WSA.Input;
public class Gaze : MonoBehaviour
{
internal GameObject gazedObject;
public float gazeMaxDistance = 300;
void Update()
{
// Uses a raycast from the Main Camera to determine which object is gazed upon.
Vector3 fwd = gameObject.transform.TransformDirection(Vector3.forward);
Ray ray = new Ray(Camera.main.transform.position, fwd);
RaycastHit hit;
Debug.DrawRay(Camera.main.transform.position, fwd);
if (Physics.Raycast(ray, out hit, gazeMaxDistance) && hit.collider != null)
{
if (gazedObject == null)
{
gazedObject = hit.transform.gameObject;
// Set the gazedTarget in the Behaviours class
Behaviours.instance.gazedTarget = gazedObject;
}
}
else
{
ResetGaze();
}
}
// Turn the gaze off, reset the gazeObject in the Behaviours class.
public void ResetGaze()
{
if (gazedObject != null)
{
Behaviours.instance.gazedTarget = null;
gazedObject = null;
}
}
}
全てのスクリプトが完成したら、動作するようにアタッチしていきたいと思います。記述した全てのスクリプトを[Hierarchy]パネル内の[MixedRealityCamera]にD&Dします。
[MixedRealityCamera]の[Inspector]内に先ほどD&Dしたスクリプトが追加されていると思います。[Behaviours(script)]->[Sphere]、[Cylinder]、[Cube]に[Hierarchy]パネル内の[Sphere]、[Cylinder]、[Cube]を、[Microphone Manager Luis(script)]->[Dictation Text]へ[Hierarchy]パネル内の[Dictation Text]をそれぞれアタッチします。
[File]->[Build Settings..]->[Build]の順に選択します。新しいフォルダー(BUILDS)を作成し、その中にさらにフォルダー(Luis_V0.1)を作ります。このフォルダーを選択し、保存します。
Unity上で実行した様子です。
HoloLensで実行した時の様子です。
以上となります。 お疲れ様でした。