🐼はじめに
UnityのAR Foundationを使用して平面を検出し、平面にオブジェクトを出すまでの一連の流れについてです!
まずAR Foundationの導入方法については以下の記事をご覧ください!
UnityにAR Foundationを入れて基本のセットアップとビルドのセッティングをするまで書いてあります!今回の記事はその続きから書きます。
www.wwwmaplesyrup-cs6.work
今回のサンプルプロジェクトはこちら↓↓
github.com
🐼開発環境
・Unity 2019.3.15f1
・AR Foundation 2.1.8
・ARKit XR Plugin 2.1.9
🐼平面を検出する
まずは平面の検出をできるようにします。
ヒエラルキーのAR Session Origin
にインスペクタからAR Plane Manager
コンポーネントを追加します。
AR Plane Managerの追加
Ditection Modeは平面検知の種類を水平方向、垂直方向、両方、から選択します。
使用用途で選びましょう。今回は水平方向だけにします。
Ditection Mode
ちなみに、AR Foundationのドキュメントには以下のように書かれています。
Some platforms require extra work to perform vertical plane detection, so if you only need horizontal planes, you should disable vertical plane detection.
参照元:AR plane manager | AR Foundation | 4.0.2
いくつかのプラットフォームでは垂直平面の検知をするために特殊な追加作業が必要になるため、垂直平面の検知が必要出ない場合は水平方向の検知のみにしておくと良いとのことです。
🐼平面検出のプレーンを出す
せっかくなので平面検出のプレーンをだしましょう!(出したくない場合はこの工程はとばしてください!)
AR Default Planeの追加
ARのプレーンはGameObject>XR>AR Default Planeを選択します。
ヒエラルキーに追加されたら次にこれを、プロジェクトウィンドウにドラッグ&ドロップでプレファブ化し、ヒエラルキーにある元のやつを削除します。
プロジェクトウィンドウにドラッグアンドドロップ
次にこれを先ほどのAR Session Originに追加したAR Plane ManagerのPlane Prefabのところにセットします。
Plane Prefabにセット
ちなみにこれはプレファブ化しなくても使えるのですが、AR Foundationのドキュメントではプレファブ化することを推奨しています。シーンにプレーンを残すとゼロスケールのプレーンアーティファクトが残ってしまうとのこと。アーティファクトってなんだろう🥺 ぴえん(古物って意味らしい)
It is recommended to save the AR Default Plane as a prefab first, delete the AR Default Plane GameObject, and then use that in the prefab field as leaving the plane in your scene will leave a zero scale plane artifact in the scene.
参照元:AR plane manager | AR Foundation | 4.0.2
これで平面検知の設定完了!
これで一旦ビルドしてみると、こんな感じで床を認識しました。
床認識してる!
🐼タップした場所にオブジェクトを出す
床認識したら次はオブジェクトを出したいですね!
タップしたらその平面にオブジェクトを出しましょう。
まず、
AR Raycast Managerの追加
つづいてプロジェクトウィンドウで右クリックで Create>C# Script からスクリプトを作成します。
ここでは ObjectDeployer
という名前にします。
以下のスクリプトを入力します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
[RequireComponent(typeof(ARRaycastManager))]
public class ObjectDeployer : MonoBehaviour
{
[SerializeField] GameObject _objPrefab;
private ARRaycastManager _raycastManager;
private List<ARRaycastHit> hits = new List<ARRaycastHit>();
void Start()
{
_raycastManager = GetComponent<ARRaycastManager>();
}
void Update()
{
if (Input.GetMouseButtonDown(0 ))
{
if (_raycastManager.Raycast(Input.GetTouch(0 ).position, hits, TrackableType.All))
{
Vector3 pos = new Vector3(hits[hits.Count - 1 ].pose.position.x,
hits[hits.Count - 1 ].pose.position.y + _objPrefab.transform.position.y,
hits[hits.Count - 1 ].pose.position.z);
Instantiate(_objPrefab, pos, Quaternion.identity);
}
}
}
}
スクリプトがかけたらこれをAR Session Originにセットします。
スクリプトの解説は後に書きます。
AR Session Originにセットする
先ほどセットしたスクリプトに、出したいオブジェクトのプレファブ をインスペクタからセットします。
キューブとかをプレファブ化してそれをセットするとかでも大丈夫です。
プレファブのセット
これでビルドしてみます!
わーい!タップしたとこにオブジェクトでましたん!
わーい
※でないよ!って人はもしかしたらオブジェクトがでかすぎて自分がうもれている可能性があるので、オブジェクトは小さめにしておくとよかです。
🐼コードの解説
コードの解説をしていきます!
[RequireComponent(typeof(ARRaycastManager))]
RequierComponent
でARRaycastManager
を指定しています。
今回の記事では手動でARRaycastManager
コンポーネントをつけましたが、ここでこう書いておくとこのスクリプトをセットしたときに一緒にARRaycastManager
もセットされるので付け忘れ防止になります。
つづいて、
if (_raycastManager.Raycast(Input.GetTouch(0 ).position, hits, TrackableType.All))
{
Vector3 pos = new Vector3(hits[hits.Count - 1 ].pose.position.x,
hits[hits.Count - 1 ].pose.position.y + _objPrefab.transform.position.y,
hits[hits.Count - 1 ].pose.position.z);
Instantiate(_objPrefab, pos, Quaternion.identity);
}
タップした位置にオブジェクトを出すため、AR Raycast Manager
クラスのRaycast
メソッドを使用します。 引数は以下の通りです。
public bool Raycast(Vector2 screenPoint, List<ARRaycastHit> hitResults, TrackableType trackableTypeMask = TrackableType.All);
public bool Raycast(Ray ray, List<ARRaycastHit> hitResults, TrackableType trackableTypeMask = TrackableType.All, float pointCloudRaycastAngleInDegrees = 5f );
画面上のタップした座標からRayを飛ばし平面とぶつかった座標をARRaycastHitのリストに格納して返してくれます。
このARRaycastHitのリストに格納されている衝突情報は距離によってソートされます。
0番目が最も近い場所でヒットした情報になります。
今回は、
hits[hits.Count - 1 ].pose.position
とすることで一番遠い位置(高さが一番下の位置)を平面としています。つまり、机の上と床をよみこむと机の上にだそうとタップしても床の位置にオブジェクトがでます。
逆にhits[0]
とすると一番近い位置を平面とする ので、その場合机と床を読み取ったら机の高さにオブジェクトがでることになります。
また、オブジェクトの出すポジションについて、hits[hits.Count - 1].pose.position.y
に_objPrefab.transform.position.y
を加算しているのは、オブジェクトの原点が中心にあるとプレーンの位置に出した時に半分下が埋もれてしまうので、プレファブ側で少し高さを上げておいてそれをプレーンのy軸に加算することでプレーンの上にうまくでるようにしています。
原点がまんなかなのでプレーンにうもれちゃう
ちなみに、プログラムがわかる方はこの部分をもうすこし簡潔に書きたい場合Linqを使って以下のようにかくこともできます。hits.Last()
と書けるので読みやすいです。
こちらの書き方をする場合 using System.Linq
を忘れずに。
Vector3 pos = new Vector3(hits.Last().pose.position.x,
hits.Last().pose.position.y+_objPrefab.transform.position.y,
hits.Last().pose.position.z);
第三引数で指定しているTrackableTypeはRayが当たる対象の指定です。
他にも以下のものから選択できます。
[Flags]
public enum TrackableType
{
None = 0x0 ,
PlaneWithinPolygon = 0x1 ,
PlaneWithinBounds = 0x2 ,
PlaneWithinInfinity = 0x4 ,
PlaneEstimated = 0x8 ,
Planes = 0xF ,
FeaturePoint = 0x10 ,
Image = 0x20 ,
Face = 0x40 ,
All = 0x7F
}
以上です!🐼
【めも】
平面検出のプレーンを消したい時はSetActive
をfalse
にするのが良いみたいです。
ARPlainManager
が有効になっている間はDestroy
するべきでないとAR Foundationのドキュメントに書かれていました。
foreach (var plane in planeManager.trackables)
{
plane.gameObject.SetActive(false );
}