#概要
Microsoft社が公開している、MRのチュートリアルのソースを初心者なりに解読した。
#Chapter 2 - Gaze
Chapter 1はソースコードが出てこないので省略。
Chapter 2は目線の先にオブジェクトを表示する。つまり、PCでいうマウスポインタを表示するキャプチャー。
CursorにこのC#スクリプトをアタッチする。
大まかな説明はコメントに、補足説明はコメント文ではなくソースの下に書いた。
using UnityEngine;
public class WorldCursor : MonoBehaviour
{
//MeshRenderer型のmeshRendererという変数を用意する
private MeshRenderer meshRenderer;
// Startは初期化に使用する
void Start()
{
// アタッチしたオブジェクトの子供のオブジェクトのmesh renderコンポーネントを取得
// アタッチされたオブジェクトをレンダリングした。
meshRenderer = this.gameObject.GetComponentInChildren<MeshRenderer>();
}
// 毎フレームごと呼び出される
void Update()
{
//頭の位置 = MainCameraの位置
var headPosition = Camera.main.transform.position;
//視線の位置 = MainCameraが向いている方向[1]
var gazeDirection = Camera.main.transform.forward;
RaycastHit hitInfo;//Raycastによる情報を得るための構造体
//Physics.Raycast(rayの開始地点, rayの向き,当たったオブジェクトの情報を格納)[2]
//もし目の前にオブジェクトがあったら・・・
if (Physics.Raycast(headPosition, gazeDirection, out hitInfo))
{
// 有効にした場合、レンダリングされた3D オブジェクトが表示される。
// 有効にしているのでオブジェクトは表示される。
meshRenderer.enabled = true;
// hitInfo.pointはRayがコライダに当たった位置(ワールド座標)[3]
// アタッチされたオブジェクトの位置をRayがコライダに当たった位置とする
this.transform.position = hitInfo.point;
// Quaternion.FromToRotation(開始方向, 終了方向)[4]
// Vector3.upはVector3(0, 1, 0)と同じ意味。つまりy方向の単位ベクトル (これってどんなベクトルでも関係ない??)
// hitInfo.normalはRayがヒットしたオブジェクトの法線ベクトル
// アタッチされたオブジェクトの回転をオブジェクトの表面に沿うようにする
this.transform.rotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
}
else
{
// 有効にした場合、レンダリングされた3D オブジェクトが表示される。
// 無効にしているのでオブジェクトは表示されない。
meshRenderer.enabled = false;
}
}
}
[1]Camera.main.transform.forwardはメインカメラを基準にしたZ方向の単位ベクトル。カメラはZ軸方向を向いているのでこのように書くとカメラが向いている方向(人間が向いている方向)となる。
[2]Rayとは光線の意味。Physics.Raycastは目線の先にコライダがアタットされているオブジェクトがあったらtrue。
[3]ワールド座標とは原点から見た座標。対比にローカル座標があり、ローカル座標は親オブジェクトとの相対的な座標空間のこと。
[4]FromToRotationはある方向からある方向へ回転させる。
#Chapter 3 - Gestures
エアタップしたオブジェクトと落下させます。
OrigamiCollectionオブジェクトにこのC#スクリプトをアタッチする。
using UnityEngine;
//HoloLensでInput系を使うのためのAPI[1]
using UnityEngine.XR.WSA.Input;
public class GazeGestureManager : MonoBehaviour
{
// Instance(実態)を生成[2]
public static GazeGestureManager Instance { get; private set; }
// 視線の先にあるオブジェクトが入るFocusedObjectを用意
public GameObject FocusedObject { get; private set; }
// GestureRecognizerはユーザージェスチャを認識するAPIを備えたマネージャクラス
// recognizer という名前で使用
GestureRecognizer recognizer;
// ここで初期化をする[3]
void Awake()
{
//自分自身(GazeGestureManagerクラス)をInstanceに代入
Instance = this;
// recognizerにGestureRecognizerを設定できるよう代入
recognizer = new GestureRecognizer();
//エアタップしたときに呼ばれるイベントをつくる!
recognizer.Tapped += (args) =>
{
if (FocusedObject != null)
{
// Component.SendMessageUpwards(呼び出すメゾット名,オブジェクトにメソッドが存在しない場合エラーをどうするか)
// OnSelestメゾットを呼び出す
// SendMessageOptions.DontRequireReceiver : 受信先がなくてよい
FocusedObject.SendMessageUpwards("OnSelect", SendMessageOptions.DontRequireReceiver);
}
};
//ジェスチャーの認識を開始
recognizer.StartCapturingGestures();
}
// 毎フレームごと呼び出される
void Update()
{
// 視線の先にあるオブジェクトをoldFocusObjectに代入
GameObject oldFocusObject = FocusedObject;
//頭の位置 = MainCameraの位置
var headPosition = Camera.main.transform.position;
//視線の位置 = MainCameraが向いている方向
var gazeDirection = Camera.main.transform.forward;
//Raycastによる情報を得るための構造体
RaycastHit hitInfo;
//Physics.Raycast(rayの開始地点, rayの向き,当たったオブジェクトの情報を格納)[4]
//もし目の前にオブジェクトがあったら・・・
if (Physics.Raycast(headPosition, gazeDirection, out hitInfo))
{
// Raycastが当たったオブジェクトを FocusedObjectとして扱う
FocusedObject = hitInfo.collider.gameObject;
}
else
{
// FocusedObjectはない[5]
FocusedObject = null;
}
// もしFocusedObjectがエアタップされイベントが起こった場合
if (FocusedObject != oldFocusObject)
{
// ジェスチャーの初期化
recognizer.CancelGestures();
// ジェスチャーの認識を開始
recognizer.StartCapturingGestures();
}
}
}
[1]仕様変更でUnity2017.1以前はusing UnityEngine.VR.WSA;
だったらしいので、バージョンに注意!
[2]{ get; private set; }
この怪しい書き方はC#のプロパティ構文。
[3]Awake()は常にStart()の前およびプレハブのインスタンス化直後に呼び出される。
[4]第三引数にあるoutはreturn以外でメソッド内からメソッド外へデータを受け渡す場合で使用される。
[5]つまり、目線の先にオブジェクトはない。
SphereCommands.csは動くオブジェクトにアタッチするスクリプト。Sphere1、Sphere2にアタッチする。
using UnityEngine;
public class SphereCommands : MonoBehaviour
{
// GazeGestureManagerに呼ばれるメゾット
void OnSelect()
{
// 自分自身にRigidbodyがない場合
if (!this.GetComponent<Rigidbody>())
{
// Rigidbodyを追加、rigitbodyに代入
var rigidbody = this.gameObject.AddComponent<Rigidbody>();
// すり抜け防止
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
}
#Chapter 4 - Voice
日本人の場合英語の発音がネイティブではないため反応しないことがあった。私自身使わないと思うので省略。
#Chapter 5 - Spatial sound
3D音源に対応します。音を発するオブジェクトが左右どちらにあるかわかります。
Sphere1とSphere2 オブジェクトにこのC#スクリプトをアタッチする。
このセッションは細かいパラメータ設定をする必要があるので、そこは公式サイトを見てください。
using UnityEngine;
public class SphereSounds : MonoBehaviour
{
//AudioSourceを扱うためにimpactAudioSourceとrollingAudioSource宣言
AudioSource impactAudioSource = null;
AudioSource rollingAudioSource = null;
bool rolling = false;
void Start()
{
// AudioSource componentを追加し設定[1]
impactAudioSource = gameObject.AddComponent<AudioSource>();
impactAudioSource.playOnAwake = false;
impactAudioSource.spatialize = true;
impactAudioSource.spatialBlend = 1.0f;
impactAudioSource.dopplerLevel = 0.0f;
impactAudioSource.rolloffMode = AudioRolloffMode.Logarithmic;
impactAudioSource.maxDistance = 20f;
rollingAudioSource = gameObject.AddComponent<AudioSource>();
rollingAudioSource.playOnAwake = false;
rollingAudioSource.spatialize = true;
rollingAudioSource.spatialBlend = 1.0f;
rollingAudioSource.dopplerLevel = 0.0f;
rollingAudioSource.rolloffMode = AudioRolloffMode.Logarithmic;
rollingAudioSource.maxDistance = 20f;
rollingAudioSource.loop = true;
// 各AudioSourceに音源を設定
// impactAudioSourceはImpactという音源をならす。
impactAudioSource.clip = Resources.Load<AudioClip>("Impact");
// rollingAudioSourceはRollingという音源をならす。
rollingAudioSource.clip = Resources.Load<AudioClip>("Rolling");
}
// 他のcollider/rigidbodyに触れたときに呼び出される。
// OnCollisionEnter(触れたオブジェクト)
void OnCollisionEnter(Collision collision)
{
// 衝突した 2 つのオブジェクトの相対的な速度が0.1以上だった場合
if (collision.relativeVelocity.magnitude >= 0.1f)
{
// impactAudioSourceを鳴らす
impactAudioSource.Play();
}
}
// 他のrigidbody/colliderに触れている間呼び出される。
void OnCollisionStay(Collision collision)
{
// Rigidbodyを使いためにrigidに代入
Rigidbody rigid = gameObject.GetComponent<Rigidbody>();
// 球が十分に速く転がっている場合、回転音を再生。
// ! は、論理演算子の「否定」
// rigid.velocity.magnitude は転がりスピード
if (!rolling && rigid.velocity.magnitude >= 0.01f)
{
// 転がっているフラグを立てる
rolling = true;
// rollingAudioSourceを鳴らす
rollingAudioSource.Play();
}
// 玉が転がっていなかったら
else if (rolling && rigid.velocity.magnitude < 0.01f)
{
// 転がっていないフラグを立てる
rolling = false;
// rollingAudioSorceを止める
rollingAudioSource.Stop();
}
}
// 他のcollider/rigidbodyと触れ合うのをやめたときに呼び出される。
void OnCollisionExit(Collision collision)
{
// rollingがtrueだったら
if (rolling)
{
//rollingをfalseにして音楽を止める
rolling = false;
impactAudioSource.Stop();
rollingAudioSource.Stop();
}
}
}
[1]
impactAudioSource = gameObject.AddComponent<AudioSource>();
:ゲームオブジェクトにAudioSourceコンポーネントを取り付け、impactAudioSourceに代入。
impactAudioSource.playOnAwake = false;
:True に設定した場合、 AudioSource は自動的に Play On Awake を開始します。falseなのでスタートしても音は鳴らない。
impactAudioSource.spatialize = true;
:空間化を有効か無効にします。trueなので有効。
impactAudioSource.spatialBlend = 1.0f;
:0.0 は音を完全に 2D にし、1.0 は完全に 3D になります。1.0より3D化に設定。
impactAudioSource.dopplerLevel = 0.0f;
:ドップラースケールを設定します。あたいは0-5で0なのでドップラー効果は出ない設定。
impactAudioSource.rolloffMode = AudioRolloffMode.Logarithmic;
:AudioSource が距離とともにどのように減衰するかを設定し取得します。オブジェクトの距離で変わる。
rollingAudioSource.maxDistance = 20f;
:音が減衰を停止する距離。20mで聞こえなくなる?
rollingAudioSource.loop = true;
:音源をループ再生させるかどうか。rollingAudioSourceはtrueなのでループさせる。
#さいごに
間違えているところは教えてほしいです。