ARスタンプラリーアプリとは何か?
ARスタンプラリーアプリとはAR機能を使ったスタンプラリーを行うアプリです。
典型的なスタンプラリーの形式は
- スタンプカードを持ったユーザーが特定の場所へ行く
- その場所にあるスタンプを自身のスタンプカードに押すことでポイントを取得する
- スタンプカードにスタンプを集めることでポイントを貯める
だと思います。それに対してARスタンプラリーアプリでは、
- スマホを持ったユーザーが特定の画像をカメラで写す
- カメラが画像を認識することで特定の3Dモデルが表示され、そのモデルをタッチすることでポイントを取得する
- 取得したポイントはユーザーごとにWebサーバーで貯められ、スマホから確認することができる
という機能になっています。
両者の比較を図にするとこんな感じです。
作ったもの
下の映像は今回私が作成したARスタンプラリーアプリのデモです。
デモでは以下の3つを実行しています。(デモが分かりにくくてすいません)
- ユーザーを新規登録する
- PCの画像をカメラに写すことで3Dモデルが表示される(左の桃のようなキャラクターは自分の大学のマスコットキャラクターを模して作りました)
- 3Dモデルをタッチすることで、モデルのアニメーションと共に右上のユーザーのポイントを表す
Point
が0から1に加算される
この記事で知れること
この記事では以下の二つについて紹介していきます。
- Unityでスマートフォンからオブジェクトを表示するスクリプトの書き方
- UnityでAPI連携をするスクリプトの書き方
バックエンドの処理やモデルのアニメーションについてはWebサイトなどで簡単に知れることであり、この記事で紹介すると記事自体が長くなってしまうため省きます。
実装について
開発&動作環境
ツール | バージョン |
---|---|
Unity | 2021.3.6f1 |
Android | 10 |
構成図
前提条件
UnityでARを行うのに必要なAR Foundation
のインストールやHierarchyにAR Session
とAR Session Origin
の追加の方法はネットで調べればたくさん出てきますのでこの記事では触れません。もし探すのが面倒な方は以下のサイトを参考にしてみてください。また、現在Unity Hubからプロジェクトを作る際にAR テンプレート
を選択することですぐにARが始められるようになっていますので、今回私は使用していませんがAR テンプレート
を使ってAR環境を整えるのもアリです。
ARでオブジェクトを表示する機能のスクリプト
スクリプトを書く前準備
- Asset内で
左クリック→Create→XR→Reference Image Library
を選択して、Reference Image Library
を作成する - Hierarchy内の
AR Session Origin
を右クリックし、先ほど作成したReference Image Library
をadd component
-
Reference Image Library
に認識したい画像を追加(画像によっては使用できないものもあります。使用できる画像の条件についてはこちらの記事を参考にしてみてください) - ARでオブジェクトを表示する機能のスクリプトが書かれたファイルを
AR Session Origin
にadd component
- 追加したコンポーネントには
Placeable Prefabs
というプロパティがあるので、そこに認識したい画像と同じ名前を持つ3Dモデルを追加
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
[RequireComponent(typeof(ARTrackedImageManager))]
public class ImageTracking : MonoBehaviour // 名前は何でも良いよ
{
[SerializeField]
private GameObject[] placeablePrefabs;
private Dictionary<string, GameObject> spawnedPrefabs = new Dictionary<string, GameObject>();
private ARTrackedImageManager trackedImageManager;
// すべての前準備
private void Awake()
{
trackedImageManager = FindObjectOfType<ARTrackedImageManager>();
foreach (GameObject prefab in placeablePrefabs)
{
GameObject newPrefab = Instantiate(prefab);
spawnedPrefabs.Add(prefab.name, newPrefab);
newPrefab.SetActive(false);
}
trackedImageManager.trackedImagesChanged += ImageChanged;
}
// 3Dモデルを表示するイベントを解除
private void OnDisable()
{
trackedImageManager.trackedImagesChanged -= ImageChanged;
}
// 3Dモデルの表示
private void ImageChanged(ARTrackedImagesChangedEventArgs eventArgs)
{
foreach (ARTrackedImage trackedImage in eventArgs.added)
{
UpdateImage(trackedImage);
}
foreach (ARTrackedImage trackedImage in eventArgs.updated)
{
UpdateImage(trackedImage);
}
foreach (ARTrackedImage trackedImage in eventArgs.removed)
{
spawnedPrefabs[trackedImage.name].SetActive(false);
}
}
// 表示される画像によって表示するオブジェクトを変更させる
private void UpdateImage(ARTrackedImage trackedImage)
{
string name = trackedImage.referenceImage.name;
var imageMarkerTransform = trackedImage.transform;
GameObject prefab = spawnedPrefabs[name];
var markerRotation = imageMarkerTransform.rotation * Quaternion.Euler(0f, 0f, 180f);
prefab.transform.SetPositionAndRotation(imageMarkerTransform.position, markerRotation);
prefab.transform.SetParent(imageMarkerTransform);
prefab.SetActive(trackedImage.trackingState == TrackingState.Tracking);
}
}
スクリプトを書く上で参考にした記事
どのような処理が行われているのかをメソッドごとに見ていきます。
宣言部分
placeablePrefabs
にはスクリプトを書く前準備の5で追加した3Dモデルが格納されています。
spawnedPrefabs
は表示する3Dモデルの名前をキー、3Dモデルのオブジェクトを値とする配列で、Awake
メソッドで使用します。
Awake
このメソッドでは3Dモデルを表示する準備を行っています。
行っていることは以下の2つです。
-
PlacePrefabs
で格納した3Dモデルをコピーしたオブジェクトを生成し、spawnedPrefabs
に3Dモデルの名前をキーとして格納します。 -
trackedImageManager
にあらかじめセットされているtrackedImagesChanged
イベントに後に紹介するImageChanged
メソッドを登録します。trackedImagesChanged
イベントとはスマホに画像が新たに写る時・別の画像が映る時・画像が映らなくなった時に発火し、イベントの結果をImageChanged
メソッドに引数として渡します。
参考
OnDisable
Awake
メソッドでtrackedImagesChanged
イベントに登録したImageChanged
メソッドを解除します。今回は一つのイベントに一つのメソッドしか登録していないので解除をしなくても動作するのですが、もし一つのイベントに複数のメソッドを登録しなくてはいけない時、解除をしないと参照されないメソッドが出てきてしまうので、本番を意識してイベントの解除処理を書いています。
ちなみにOnDisable
メソッドはUnityのスクリプトのライフサイクルにおける最後の方に実行されるメソッドです。
参考
ImageChanged
このメソッドでは3Dモデルの表示の実行を行っています。
trackedImagesChanged
イベントの登録時に受け取ったARTrackedImagesChangedEventArgs
の値によって実行内容を切り替えています。ARTrackedImagesChangedEventArgs
の値は以下の三つがあり、発火するイベント内容によってそれぞれの値はList<ARTrackedImage>
を保持します。(ARTrackedImage
オブジェクトは認識する画像の情報を含んでいます。)
1 | 2 |
---|---|
added | 画面上で画像の追加があった時にその画像の情報を保持する |
updated | 画面上で画像の変更があった時にその画像の情報を保持する |
removed | 画面上で映らなくなった画像があった時にその画像の情報を保持する |
added
・updated
の場合はtrackedImagesChanged
イベントの切り替えによるARTrackedImagesChangedEventArgs
の値の要素(ARTrackedImage
オブジェクト)をUpdateImage
メソッドに渡し、removed
の場合は3Dモデルを見えなくします。(このアプリではupdated
の場合の処理しか実行されなかったので、added
・removed
がどのときに画像の情報を保持するタイミングは公式資料などから自分なりに読み取った内容をただ載せているだけで、確証はありません。)
参考
UpdateImage
このメソッドでは3Dモデルの表示の実装をしています。
行っていることは以下の3つです。
-
ARTrackedImage
オブジェクトに含まれる画像の名前・位置・向きを取得します。 - 画像の情報をもとに表示する3Dモデルの表示位置を調整します。
- 画像が追跡可能な範囲まで画面にはっきりと表示されている場合、3Dモデルを表示する。
参考
ユーザーの動作をバックエンドに伝えるスクリプト
今回紹介するのは画像を認識することによって表示された3Dモデルをユーザーがタッチした時にユーザーのポイントを追加するスクリプトです。
スクリプトを書く前準備
- このスクリプトが書かれたファイルを表示する3Dモデル(プレハブ)に
add component
- 追加したコンポーネントに
Target Renderer
というプロパティが表示されるので、3Dモデルのパーツの一つをそこへ追加
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class TouchTranstion : MonoBehaviour // 名前は何でも良いよ
{
[SerializeField] Renderer targetRenderer;
// オブジェクトが表示時の処理の実行
void Update()
{
if (targetRenderer.isVisible && Input.touchCount > 0)
{
StartCoroutine(IncrementPointApi());
}
}
// ポイントの追加
IEnumerator IncrementPointApi()
{
string inputText = PlayerPrefs.GetString("Name");
WWWForm formData = new WWWForm();
formData.AddField("name", inputText);
// usingを使用する理由はメモリリーク対策
using UnityWebRequest www = UnityWebRequest.Post("API(URL)", formData);
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.ConnectionError || www.result == UnityWebRequest.Result.ProtocolError)
{
// API通信が失敗した時の処理
}
else
{
// API通信が成功した時の処理
}
}
}
宣言部分
3Dモデルが表示されているかの判定用に使用するtargetRenderer
を用意しています。targetRenderer
には前準備で追加した3Dモデルのパーツの一部(今回は胴体パーツ)がMesh Render
として格納されています。
Update
このメソッドでは、targetRenderer
で格納したパーツが表示されるかつユーザーが画面にタッチした時にユーザーのポイントを追加するIncrementPointApi
メソッドを実行しています。StartCoroutine
を使用してIncrementPointApi
メソッドを実行する理由は、StartCoroutine
がないとUnityWebRequest
クラスを利用したAPI通信が伴うメソッドの実行ができないからです。
targetRenderer
が表示されているかの判断はRenderer
クラスの変数isVisible
を使用します。
参考
IncrementPointApi
このメソッドで行っていることは以下の三つです
-
WWWForm
クラスを使用してバックエンドへ送るデータの作成します。WWWForm
のAddField
メソッドの第一引数にフィールド名、第二引数に値を入れることでフォームデータの追加ができます。 -
UnityWebRequest
クラスを使用し、APIを通してバックエンドへデータを送信します。UnityWebRequest
クラスのPost
メソッドの第一引数にAPI、第二引数にフォームデータを入れることでUnityWebRequest
オブジェクトをHTTPのPOSTが行える状態にし、SendWebRequest
メソッドを実行することでバックエンドとの通信を行っています。UnityWebRequest
オブジェクト生成時にusing
を書いている理由は、これがないとメモリリークでエラーが生じてしまうからです。また、SendWebRequest
メソッドにyield
を書く理由は、バックエンドこれがないとバックエンドの結果を取得できないからです。 - API通信が成功or失敗した際の処理の実行をします。今回の処理ではバックエンドとの通信エラーが起きるか、バックエンドから4xxor5xxエラーが返されたら失敗の処理が行われます。
参考
終わりに
UnityとC#について筆者が初心者であることから、処理の説明がところどころいい加減になってしまいました。
今後UnityとC#の学習を深めることで、両者の理解を深めるとともにARスタンプラリーアプリの完成度を高めていきたいです!