ARKit用サンプルアプリを作る2つの方法
はじめに
ARKit用サンプルアプリの作成する以下の2つの手順を紹介します。
1. Xcodeのみを使ってアプリを作成
2. Unityでアプリを作成し、Xcodeを使ってビルド
1はiOSのネイティブコードでARKitを直接利用する方法、2はUnity ARKit Pluginを使用してUnityでアプリを作成し、最後にXcodeを使ってビルドする方法です。
ARKitについては前回の記事で紹介したため、そちらも読んでください。
korechipostit.hatenablog.com
どちらを使うかは作りたいアプリによって使い分けると良いと思います。この記事では両方の方法でサンプルアプリを作る方法を紹介します。
①Xcodeのみを使ってアプリを作成
まずはAppleの公式ドキュメントでも紹介されている、Xcodeのみを使ったサンプルアプリの作成方法について説明します。
アプリの実行結果
宙に飛行機オブジェクトが浮かんでいるのが確認できるアプリとなっています。
必要なもの
アプリの作成
- Xcode-betaを起動し、Create a new Xcode projectを選択します。
- Augmented Reality Appを選択→Nextをクリック
- アプリの初期設定を行う
- 任意の値を入れてください。
- 「Product Name」と「Organization Identifier」から成る「Bundle Identifier」は、世界中のアプリの中から自身のアプリを識別するIDとなります。主に、ドメインを逆さまから並べたものをOrganization Identifierに設定することが推奨されています。
- Content Technologyは、「Scene Kit」「Sprite Kit」「Metal」の3つから選べますが、今回はとりあえず「Scene Kit」を選択しました。
アプリのビルド
無事、宙に飛行機が浮かぶサンプルアプリがiPhone上で実行されるはずです。 コードの中身が気になる方は各自Xcodeを確認してください。
②Unityでアプリを作成し、Xcodeを使ってビルドする方法
こちらはUnityを使ってARKit用アプリを作成する方法を紹介します。
何となくこっちの方がTangoでの開発に近い気もします。
アプリの実行結果
必要なもの
Unityでの操作
- 新規Unityプロジェクトの作成
- unity-arkit-plugin.unitypackageをインポート
Projectビューは以下のようになっています。
- Build Settings -> PlatformをiOSに切り替える
- No iOS module loadedとなっている方は、横にある「open download page」ボタンをクリックしてください(DLが終わるまで1時間弱かかる場合があります)
- Build Settings -> Platform -> iOSを選択し、PlayerSettingsからIdentificationを設定
- Camera Usage Descriptionに何らかしらの文字を入れる(これをやらないとアプリがカメラ利用許可を得られず、起動時に落ちるらしい)
- Assetsフォルダ直下にある、UnityARKitScene.unityをダブルクリックしてシーンを開く
シーンを開くと以下のような画面になります。
- Build Settings -> Add Open Scenes をクリック
注)これをしないと現在のシーンがビルド対象となりませんので注意してください
- UnityParticlePainter.unityのシーンも同様にビルド対象とし、ビルドします
ただし、Unityのパッチが古いと以下のようなエラーが出ます。
Assets/Plugins/iOS/UnityARKit/UnityARVideo.cs(59,31): error CS0117: `UnityEngine.TextureFormat' does not contain a definition for `R8' Assets/Plugins/iOS/UnityARKit/UnityARVideo.cs(66,31): error CS0117: `UnityEngine.TextureFormat' does not contain a definition for `RG16'
エラーが出なかった場合は、Xcode向けにビルドされたプロジェクトが生成されます。
Xcodeでの操作
ARKitUnitySample[3217:1623803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ARFrame displayTransformWithViewportSize:orientation:]: unrecognized selector sent to instance 0x1c42eff80'
調べたところこれは、unitypackageが少し古いために発生した問題みたいです。
もしそのようなエラーが出てアプリが強制終了してしまった方は、unitypackageを使わずにUnity-ARKit-pluginをbitbucketからダウンロードして、それをそのままUnityで開いてください。
それ以外の手順は大体同じです!
最後に
Xcodeのみを使ってアプリを作成する方法と、Unityを使ってアプリを作成する方法を紹介しました。
どちらを選択するかは皆さんに判断をお任せしますが、やはり3Dオブジェクトをごりごり動かしたい場合はUnityの方が良いのではないかとも思います。
反対に、そうでないものを作るのならばXcodeのみで開発した方が良さそうにも思えます。
自分はもう少し両方のパターンで慣れて見てから判断しようと思います!
AppleのARKitについて調べてみた
はじめに
AppleのARKitに最近興味がわいたので、今回調べてみました。
Appleの公式ドキュメントを読んでまとめつつ、補足をつけたりしています。
アプリの制作方法は次の記事でまとめたいと思います。
ARKit とは
OSデバイスのカメラとモーション機能を統合することで、iアプリやゲーム内で拡張現実体験を提供するフレームワークです。
具体的には、デバイスのモーション機能、カメラによるシーンのキャプチャ、高度なシーン処理、便利さの表示を組み合わせて、AR体験の構築を単純化しています。
ARKitを使ったデモ例
こちらはARKitとUnityを使ってロボットが部屋の中で踊るデモです。
床の上を綺麗に動き回っているのが特徴的です。他にも様々なサンプルアプリが世に出回っているみたいです。
ARKitを利用するには(デバイス側)
A9以降のプロセッサを搭載した、iOS11以降のOSがインストールされたデバイスが必要です。
ARKitを利用するためには(アプリ側)
ARKitをサポートしたデバイスのみでアプリを動作させるには、アプリのInfo.plist
のUIReauiredDeviceCapabilitiesセクションの中でarkitキーを使用してください。
もし、AR機能がアプリにとって二次的な機能ならisSupported
プロパティを使用してください。
ワールドトラッキングの仕組み
現実世界と仮想空間の間の対応関係を構築するために、visual-inertial odometryという技術を使用しています。
visual-inertial odometryとは
iOS デバイスのモーションセンサーとカメラから見えた映像の分析結果を結合するプロセス。
ARKitはシーン画像内の特徴点を認識し、その特徴点からのずれを追跡し、モーションセンサーからのデータと比較することで、高い精度でデバイスの位置と動きを得ることができます。
ARKitができること
- 平面検知
- ARHitTestResultクラスを使えば、カメラ画像内にある平面(現実世界における)を検知
- planeDetectionをenableにすることで、ARKitはカメラ内の平面を検知し、その位置とサイズを知らせてくれる
- それ以外はできないのかな?(まだよく分かっていませんが)
- Tangoは平面だけでなく壁や特徴点の3次元座標を取得可能だったがARKitは平面検知しか行えないっぽい
- Tangoは学習した現実空間の座標情報をADFファイルとして保存し、アプリ起動時に読み込むことで以前に学習した空間とのマッチングを行うことができたが、ARKitにその機能はついていないみたいです
ARKitを使用するコツ
- 明るい環境での使用を促す
- ワールドトラッキングはカメラ画像をもとに行われるため、暗い場所や細部が見えない環境では精度が落ちてしまいます。
- トラッキングがうまくいっているかユーザにその都度知らせる
- 時間をかけて平面検知を行い、必要とする平面が検知されたらそれ以降は平面検知をしないようにする
- 平面検知の結果は時間とともに変化する。なぜなら、ARKitはシーン内の情報を常に更新し、その情報をもとに平面の情報を上書きし続けるからです。(これはつまり、一番最初に検知した平面は不正確な場合があるということです)
様々なクラス
ARKitのAR機能を使うためにいくつかのクラスが用意されています。
ARSessionクラス
- 拡張現実体験に必要なデバイスカメラとモーション処理を管理する共有オブジェクト
- ARKitが拡張現実感を作成するために実行する主要なプロセスを調整
- AR機能を使うためには、このARSessionオブジェクトが必要
- ARSCNViewやARSKViewオブジェクトを使用する場合はARSessionオブジェクトが生成されているが、自分でARコンテンツを自作する場合は、ARSessionオブジェクトを生成し、扱う必要がある
ARWorldTrackingConfigurationクラス
- デバイスの動きを6軸自由度(6DOF)を使ってトラッキングする設定
- 3つの回転軸(roll, pitch, yaw)
- 3つの平行移動軸(x, y, zの移動)
- これらの情報とカメラ画像を組み合わせることで、仮想の物体を現実空間に配置することができる
AROrientationTrackingConfigurationクラス
ARConfigurationクラス
- 抽象クラス
- 具象クラスとしてARWorldTrackingConfigurationクラスとAROrientationTrackingConfigurationクラスが存在
まとめ
- ARKitは現実空間にARオブジェクトを配置する技術
- カメラ画像とモーションセンサーのデータを使ってそれを実現
- 現在、カメラ画像内の平面検知が行えるため、平面の上に物を置いたりすることができるみたい
- iPhone6s以降に発売された端末で利用できる
画像の手前にTextMeshを表示していたら見る角度によってTextMeshが消えた
今回見つかった問題
Unityのシーン内でTextMeshを特定の画像の手前に表示しようとした時、特定の角度から眺めるとTextMeshのテキストが消えてしまいました。
こんな感じに。
(gifの撮影はScreen To Gifというアプリを使いました)
ちゃんとTextMeshは画像より手前にあるはずなのに突然消えてしまう!
一方向からしか見ないなら問題はないんだけど、ARとかVRでオブジェクトを表示するといろんな角度から眺めることもあるので対応する必要があります。
そのため今回、この問題について調査してみました。
発生状況
- Unity 5.6.2f1
- OS:Windows10
原因
どうもTextMeshではなく、後ろにある画像オブジェクトのMaterialの設定が良くなかったみたいです。
このように、materialのshaderをUnlit/Transparent
にしていました。それを
Unlit/Transparent Cutout
に変えたところTextMeshが消えなくなりました!
おまけ
ビルトインシェーダー
さっきのmaterialのshader、結局どう変わったの?という疑問が自分にあったので、Unityのシェーダー/マテリアルについてちょっと調べました。
ビルトインシェーダー | 内容 |
---|---|
Standard | 石、木、ガラス、プラスチック、金属など「現実世界」のオブジェクトをレンダリングするのに使用され、広範囲のシェーダータイプや組み合わせをサポート |
FX | ライティングとガラスのエフェクト |
GUIとUI | UI グラフィック |
Mobile | モバイル デバイス用に簡素化されたハイパフォーマンス シェーダー |
Nature | 樹木および地形 |
Particles | パーティクルシステムエフェクト |
Skybox | すべてのジオメトリの裏側でレンダリングする背景環境 |
Sprites | 2D スプライトシステムとともに使用 |
Toon | カートゥーン(漫画)風のレンダリング |
Unlit | すべてのライトとシャドウイングを完全にバイパスするレンダリング |
Legacy | スタンダードシェーダーに取って代わられる古いシェーダーの大きい集合 |
シェーダー
シェーダーとは、モデル表面のピクセルの見え方を決めるための、数学的な計算とアルゴリズムを格納したスクリプトです。
シェーダーが定義するのは、
- オブジェクトを描画する方法です。光源の角度や、視野角、そしてその他の関連性のある計算を含む、コードと数学的な計算です。シェーダーでは、エンドユーザーのグラフィックスハードウェアに依存する、さまざまな方法を使う事もできます。
- テクスチャマップや色、その他数々のマテリアルインスペクターで変更できるパラメータ。
マテリアルが定義するのは、
- マテリアルをレンダリングするために使用するシェーダー
- シェーダー パラメーターの特定の値 - テクスチャマップ、色、数的値など
です。
Unityでアプリを終了させてもデータを残しておく2つの方法
作りたいもの
例えばゲームのMaxスコアを保存したり、フレンドのリストをローカルに保存しておきたい場合があるとします。
それらのデータはアプリを終了して消えてしまったら当然困るので、アプリ終了時にデータをスマートフォン上に保存する必要があります。
その処理を今回紹介したいと思います。
データをローカルに保存する2つの方法
xmlファイルを使う方法
保存したいデータが以下のようなとあるクラスのインスタンス変数の中身だとします。
public class SaveData { [XmlElement("text")] public string m_text; [XmlElement("position")] public Vector3 m_pos; }
これらをxmlファイルに保存する方法は以下のようにします。
// 保存したいデータのインスタンスのリスト private List<SaveData> m_saveData; // xmlファイルに入れるためのデータを作成 List<SaveData > xmlDataList = new List<SaveData > (); foreach (SaveData data in m_saveData) { SaveData temp = new SaveData(); temp.m_text = data.m_text; temp.m_pos = data.m_pos; xmlDataList.Add (temp); } string fileName = "korechi_memo"; string path = Application.persistentDataPath + "/" + fileName + ".xml"; // XmlSerializerのインスタンスを生成 var serializer = new XmlSerializer (typeof(List<SaveData>)); // ファイル生成 using (var stream = new FileStream (path, FileMode.Create)) { serializer.Serialize (stream, xmlDataList); }
そして保存したxmlファイルをロードする方法を以下に示します。
string fileName = "korechi_memo"; string path = Application.persistentDataPath + "/" + fileName + ".xml"; var serializer = new XmlSerializer(typeof(List<PostItData>)); var stream = new FileStream(path, FileMode.Open); List<SaveData> xmlDataList = serializer.Deserialize(stream) as List<SaveData>; if (xmlDataList == null) { Debug.Log("xmlDataList is null"); return; } foreach (SaveData data in xmlDataList) { // 取り出した後の処理 }
PlayerPrefsを使う方法
この方法は以前の記事で紹介した
korechipostit.hatenablog.com
下の方にかかれています。その時使ったコードを載せておきますのでうまいこと参考にしていただければと思います。
Unityで気象情報をWebから取得し、1日の天気を表示してみる
作りたいもの
Unityを使って気象情報を取得し、1日の天気や取得した瞬間の天気を取得する。
用途としては例えば、ダイアリーアプリを作る時に天気の情報を載せたり、Unityで作ったゲーム内の天気を現実の天気に合わせるのにも使えそう。
今回使用するWebAPI
DarkSky.netという気象情報サービスを使用します。
URLはここ。
1日に1000リクエストまで無料でAPIを使用することができます。それ以降は課金が必要となり、適宜請求が来るみたいです。
他の気象情報API
他の気象情報を取得できるサービスとして、例えば
OpenWeatherMap
あたりがあります。
これもフリーで利用できるAPIが提供されており、1日のうち3時間ごとの天気などが取得可能です。
地点はTokyo,jp
のように、大まかな範囲で指定できます。
このサービスは完全にフリーでリクエストごとの課金とかがありません。なので使っていて安心なのですが、緯度経度が指定できたり、1時間ごとの天気を取得できるDarkSky.netを今回使用します。
DarkSky.netのAPIの使い方
このサービスを使用するにはまずサインインする必要があるため、上にあるDevelopersをクリックしてサインアップしてください。
そしてシークレットキーを取得してください。
URLリクエストに使うURLは以下のようになります。
https://api.darksky.net/forecast/<Secret Key>/<longitude>,<latitude>
longitudeには、気象情報を取得したい地点の緯度を、latitudeには経度を入れてください。
このURLリクエストを行うと、JSON形式で気象データが受信できます。
ただしこのままだと非常に見づらいので、自分の欲しいデータだけうまいこと取得する必要があります。
Unityで気象情報を取得してみる
UnityからWebAPIを叩く
WWWクラスを使います。
public class ExampleClass : MonoBehaviour { // 適切な値を埋め込んでください string url = "https://api.darksky.net/forecast/<Secret Key>/<longitude>,<latitude>"; IEnumerator Start() { WWW www = new WWW(url); yield return www; string jsonString = www.test; Debug.Log(jsonString); } }
jsonStringオブジェクトに取得したJSONのテキストが格納されています。
位置情報を取得
これは、こちらのサイトが参考になりました。
[Unity] Unityで位置情報を取得する: ものづくりログ
JSONのパース
UnityにJSONのパーサはあったのですが、リストに対応していなかったりと使い勝手がイマイチっぽかったので、自分でJSONデータをパースしました。
パースとは言うものの、JSONのデータをstring型の変数として格納し、1文字1文字読み進めていき文字列を地道に探すというやり方です(非効率ですみません)。
例えば上のJSONをよーく見ると"summary":"Partly Cloudy"
という部分が天気を表しているらしいことが分かります。
そのためsummaryの次の"“で囲まれている部分のstringを取り出せば天気が取得できたことになります。
サンプルコードは下のようになります。
for (;; ) { // summaryから始まる部分を探す if (jsonString.StartsWith("summary")) { // summaryの次のstringを抜き出す処理 // "summary":" の部分がいらないため11文字Removeする jsonString.Remove(0, 11); string weather = ""; int i = 0; while (jsonString[i] != "\"") { i++; weather += jsonString[i]; } } else { // 1文字目を削除->jsonStringを先に進めていくイメージ jsonString = jsonString.Remove(0, 1); } }
こうしてjsonStringから天気の情報を抜き出すことができます。(正確なコードではないので適宜修正して下さい)
時間については"time":1501596000
の部分に記載されているため上と同様にすることで時間も取得できます。
ただしこれはUNIX時間なので日本時間に直す処理は別途必要となります。
あとは取得した情報をUniyで表示してあげれば終了です。
サンプルコード
緯度経度を取得し、気象情報を取得しログに出力するサンプルコードをのせておきます。
Tangoを使って3D版ARテトリスを作ってみた
今回作ったデモ
www.youtube.com 今回、Tangoを使って3D版ARテトリスを作ってみました。こちらがデモの動画です。
動機
最近ARに興味がありTangoで何か作ってみたいなーと思っていたらふと、今まで2次元でやっていたゲームを3次元に拡張して現実空間でやってみたらどうなるんだろう?と思い、作ってみました。
今回の記事では今回作ったデモのアプリ紹介をメインに行いたいと思います。
実装についてはおいおい記事にまとめます。
ちなみに、まだまだバグや見た目を直していかないといけない部分があるため、完成品とは言えませんが動くもものは出来たので。
動作環境
Lenovo Phab2 Pro (Tango搭載, Android 6.0)
Unity 5.6.1f1
ゲームスタートの流れ
ゲームを起動すると画面中央に、ゲームステージを選択するUIが表示されます。
その後ゲームを開始したい地点を決めたらスタートボタンを押してゲーム開始。
操作方法
ブロック操作は画面左にある上下左右ボタンと、画面右にある矢印下ボタンで行います。(見づらくてすみません)
上下左右ボタンを押すとその方向にブロックが移動します。
画面右のボタンを押すと一番下までブロックが移動します。(ここらへんはスティックなどを使ってもっと分かりやすく出来そうかも)
スコア表示
ステージ上にスコアが表示されます。
同時消しをするほどスコアは増えやすくなります。
気づいたこと
さすがTango。一度ステージを作るとほとんどぶれないです。
また、後ろから回り込んで見てみても問題なくブロックを見ることができます。
それに、Unityを使えば比較的簡単にAR対応アプリを作ることができるます。
反省点
ブロックがどこに落ちるか少し分かりにくく、予想外のところでブロックがひっかかってしまうことがよく起こる。
良かった点
通常のテトリスに縦方向を加えたことによりゲームの難易度が多少あがって面白くはなった(気がする)。
作り込めばもう少し面白くなりそう。
しかし、ARにしただけでは面白さという点で+αとなるものは少ないと感じた。
そのため、もしAR向けゲームを作る方がいらっしゃれば、「ARにするだけではなく、ARの特色を活かしてゲームの面白さを引き出す」ことを心がけていただければ幸いです。僕も心がけます。
最後に
AR向けアプリを作るのは楽しいです。
作ったものが現実世界でグリグリ動いてくれるのはなかなか新鮮ですので。
もしTangoアプリ開発に興味を持った方がいらっしゃれば、
korechipostit.hatenablog.com
に簡単なTangoアプリの作成方法がのせてありますので読んで見てください!
Unityでログインボーナスのようなスクリプトを書いた
ログインボーナスのようなものを今回実装したいと思います。
これはつまり、アプリを起動した時に日をまたいでいたら一度だけ処理を行うものです。何となく便利そうですよね。
これをUnityで実現するためには、
1. 日付の取得
2. 日付をデバイスに保持
3. 取得した日付とデバイスに保持された日付を比較
この3つの処理が必要です。
それぞれの実装方法を紹介したいと思います。
日付の取得
UnityのDateTimeというクラスを使います。
使い方
Datetime now = DateTime.Now;
こうやって時間を取得します。また、
int todayInt = 0; todayInt = now.Year * 1000 + now.Month * 100 + now.Day;
このようにすることで年月日をそれぞれ取得できます。now.Year等の型はintです。
時間も簡単にとれるみたいです。
この例では、例えば2017年7月13日の場合、todayInt=20170713という値が格納されます。
こうやって日付をint型に変換することで、年月日の比較が簡単に出来ますね。
日付をデバイスに保持
値をUnityからデバイス(PCやAndroid)に保存するためにはPlayerPrefsというクラスを使います。
このクラスはデータのセーブ、ロードが行えます。
具体的に保存される場所や形式はここを見れば分かります。
例えば、Android端末では /data/data/pkg-name/shared_prefs/pkg-name.xml に保存されるようです。
保存される値は、KeyとValueのペアです。
例えば、Keyを"Date"とし、Valueを20170713とするペアが考えられます。
使い方
まずは下にPlayerPrefsクラスの使い方の例を書きました。
bool isDateExist; int todayInt; isDateExist = PlayerPrefs.HashInt("Date"); // Dateが保存されているか確認する。あったらtrueが返る PlayerPrefs.SetInt("Date", 20170713); // Dateを保存 todayInt = PlayerPrefs.GetInt("Date"); // Dateの値を取得
これをうまいことコードとして書くとこのように書けたりします。
if (!PlayerPrefs.HasKey ("Date")) { Debug.Log("Dateというデータが存在しません"); PlayerPrefs.SetInt ("Date", todayInt); } else { if (todayInt - PlayerPrefs.GetInt ("Date") > 0) { PlayerPrefs.SetInt ("Date", todayInt); Debug.Log("次の日になりました"); } else { Debug.Log("今日すでにログインしています"); } }
取得した日付とデバイスに保持された日付を比較
これはもうまとめてサンプルコードを載せちゃいます。
こんな感じです。参考になれば幸いです。