クマテクブログ

日々のメモ

ARKitでどこでもドア

これを見て一ヶ月前に真似て作ったサンプル

いろんなひとがどこでもドアを作ってたのでせっかくだから自分もあげてみる。

ソースコード自体は前回のものを流用し、そこにいくつかのShaderを追加で実装しただけのインスタント実装です。

ちなみにAppleがiOS11で一番推したい機能がARKitだそうです。
iOS11正式リリースに合わせて何か面白いARKitアプリを作ってリリースしてみるのも面白そうですね。

Unix Time

このままいくと明日Unix Timeが1,500,000,000を迎えそうです。

Countdown to Unix Time 1,500,000,000


f:id:Cyario:20170713145029p:plain

iOS11のARKitをUnityから使用してみる

iOS11が発表されiOSにARKitという新しいARサポートが入りました。
こちらはUnityやUnreal EngineからPluginを通して使用する事できるそうなのでUnityを使ってオブジェクトを表示してみました。

必要なもの

  • iOS11にUpdateしたiPhone 6s以上の端末(ARは実機じゃないと確認できません)
  • XCode 9(現在β)
  • Unity 5.6.1p1以上
  • Unity ARKit Plugin

STEP

#1. Unity ARKit Pluginをプロジェクトにインポート
#2. Player SettingsのCamera Usage Descriptionを設定(これを設定しないとアプリがクラッシュします。)
#3. Main CameraにUnityARVideoコンポーネントを追加し、UnityARVideoのClear MaterialにYUVMaterialを設定
#4. UnityARGeneratePlane Componentをシーンのオブジェクトに追加(これを追加すると平面を検知してくれるようになります。)

これで準備は整いました。
あとは下記のようなソースを追加してみます。
平面の検知後、画面をタップした所に平面があればオブジェクトを生成するというスクリプトになります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.iOS;

public class Spawner : MonoBehaviour
{
	public GameObject catPrefab;
	public float createHeight;
	private MaterialPropertyBlock _props;

	void Start()
	{
		_props = new MaterialPropertyBlock();
	}

	void CreateCat(Vector3 position)
	{
		Instantiate(catPrefab, position, Quaternion.identity);
	}

	void Update()
	{
		if (Input.touchCount > 0 )
		{
			var touch = Input.GetTouch(0);
			if (touch.phase == TouchPhase.Began)
			{
				var screenPosition = Camera.main.ScreenToViewportPoint(touch.position);

				ARPoint point = new ARPoint {
					x = screenPosition.x,
					y = screenPosition.y
				};

				List<ARHitTestResult> hitResults = UnityARSessionNativeInterface.GetARSessionNativeInterface().HitTest (point, 
				ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent);
                
				if (hitResults.Count > 0)
				{
					foreach (var hitResult in hitResults)
					{
						Vector3 position = UnityARMatrixOps.GetPosition (hitResult.worldTransform);
						CreateCat (new Vector3 (position.x, position.y + createHeight, position.z));
						break;
					}
				}
			}
		}
	}
}

無事カメラ空間の中にオブジェクトが生成されました。

https://raw.githubusercontent.com/wiki/cyario/ARKitSample/images/cats.gif

RigitBodyで生成したオブジェクトに重力をつけ検知した平面と衝突判定をつけてあげればこのようにテーブルから転げ落ちる表現も簡単に実装できます。

またStencil Shaderを使ったりして平面検知したPlaneの後ろに回ったオブジェクトは表示させないようにしたりすると綺麗に見えると思います。

Meshを更新した際にPrefabが壊れてしまう問題を解決する

Unityを使用しているときによく使う機能でPrefabというものがあります。
私も普段よくPrefabを利用していますが先日このような事が起こりました。

とあるMesh(TestModel.fbxとします)を参照しているPrefab(TestModel_Prefabとします)があり。
Meshにいくつかの修正を加えMeshを入れ替えるとPrefabの表示がとんでもない事になってしまいました。


このPrefabが参照しているMeshを更新します。
f:id:Cyario:20160831164521p:plain


すると、とんでもない事に....
f:id:Cyario:20160831164526p:plain



この時Meshの構造、ボーンの構造に特に変更はない簡単な変更のはずなのにこのような事態になりました。
Prefabの参照元のMeshを更新できないとなると毎回Prefabを作成しないといけなくなり、参照しているPrefabが多いと大変面倒でヒューマンエラーの原因にもなりかねないです。
なんとか解決しようと詳しく調べてみるとどうもMeshの頂点番号に変更があり、UnityではMeshのもつ情報からボーンとの接続を行うらしくそれが原因らしいと分かりました。

そして色々調べて試しにこのようなScriptを作成してみると

public class ResetBoneMap : MonoBehaviour
{
	public GameObject original;

	public void Convert()
	{
		SkinnedMeshRenderer[] skinnedMeshs = this.GetComponentsInChildren<SkinnedMeshRenderer>();

		foreach ( var s in skinnedMeshs )
		{
			Reset( s.GetComponent<SkinnedMeshRenderer>(), s.name );
		}
	}

	void Reset( SkinnedMeshRenderer targetRenderer, string name )
	{
		SkinnedMeshRenderer originalRenderer = getChildGameObject( original, name ).GetComponent<SkinnedMeshRenderer>();

		if (targetRenderer == null || originalRenderer == null ) return;

		Transform[] newBones = new Transform[targetRenderer.bones.Length];

		for ( int i = 0; i < originalRenderer.bones.Length; i++ )
		{
			for ( int j = 0; j < targetRenderer.bones.Length; j++ )
			{
				if ( targetRenderer.bones[j].name == originalRenderer.bones[i].name )
				{
					newBones[i] = targetRenderer.bones[j];
					continue;
				}
			}
		}

		targetRenderer.sharedMesh = originalRenderer.sharedMesh;
		targetRenderer.bones = newBones;		
	}

	GameObject getChildGameObject(GameObject fromGameObject, string withName)
	{
		Transform[] ts = fromGameObject.transform.GetComponentsInChildren<Transform>();
		foreach (Transform t in ts) if (t.gameObject.name == withName) return t.gameObject;
		return null;
	}
}

なんと・・うまく出力されました!!
f:id:Cyario:20160831170159p:plain


これでMeshの更新がなんとか楽になりそうです。


こちらのScriptはGithubにて公開しました。
github.com


使い方は簡単で壊れたPrefabにアタッチして元のMeshをOriginalに入れて・・・
f:id:Cyario:20160831164530p:plain


あとはボタンを押すだけ。
f:id:Cyario:20160831164534p:plain

処理が終われば綺麗な状態に直ります。

Vertex Shaderで草原を再現

こちらの草原シェーダーを参考にUnityに移植してみました。
jsdo.it



https://raw.githubusercontent.com/wiki/cyario/GrassShader/images/grass-shader.gif

.unitypackageの格納場所

Asset Storeからダウンロードした展開前の.unitypackageがどこに格納されているのか気になって調べてみました。

Macでは下記に入っているみたいです。

~/Library/Unity/Asset Store
~/Library/Unity/Asset Store-5.x

Windowsでは確認していません

uGUIでスナップスクロール

調べてみたところuGUIにはスナップスクロールの機能がないようです。
スナップスクロールとはiPhoneのホーム画面などで見かけるスクロールすると次のページでピタッと止まるスクロールの事です。


よく使う機能だと思うので調べてみたらこちらの方が同様の機能を作成してくれていました。
qiita.com


しかし使ってみたところ画面の半分程度スクロールさせないと次のページにスクロールしてくれずサクサクスクロールさせるのが難しかったのでスクロール量を指定できるように改善してみました。

https://raw.githubusercontent.com/wiki/cyario/SnapScroll/images/snap-scroll.gif


こちらのプロパティでどれだけの距離をスワイプすれば次のページに行くのかコントールができます。

public float scrollWeight = 0.1f;


またScrollToを使ってScriptからスクロールさせることも可能です。

public void ScrollTo( int x, int y)


ほとんど上記の方のコードを流用してますが不具合や要望があればご連絡お願いします。