UnityでARカメラアプリを作る

こんにちは。本来の投稿日であれば[東京理科大学アドベントカレンダー４日目](https://qiita.com/advent-calendar/2018/tokyo_university_of_science)です。

今回は先日Unityを使ったARカメラアプリを作ったのでそれについて書いていこうと思います。
AndroidとiOS両対応なものを作ったのですが、主に苦労したのはAndroidの方だったのでそちらについて書いていきます。
注：正直Android開発全然わかっていないのでだいぶゴリ押しです。。

※UntiyからAndroidへのビルドの仕方は以下の記事に掲載されていますので割愛します。
→[UnityからAndroid実機ビルド手順(2017.08版) - Qiita](https://qiita.com/relzx/items/7f8e7817c9edd11c5023)

# 環境
* MacOS High Sierra
* Unity 2017.2.1f1
* Vuforia（Unityに内蔵されているもの）
* Android 7.0（Nougat）


# 作ってくよ
AR部分はVuforiaをぶっこんで表示できれば完成です。ビルドした際にカメラが起動できていれば自動的にARモードになっています。簡単です。
（参考URL：[Unity 2017.2で統合されるVuforiaを試してみる](http://www.noshimemo.com/entry/2017/09/22/001950)）

なのでネイティブ側のカメラアプリ制作について記載していきます。

カメラアプリのざっくりと流れとしてはこんな感じ。
1. Android端末内のディレクトリを読み込んで、オリジナル保存フォルダを生成する
2. スクリーンショットを撮る
3. バイナリファイルに書き込む
4. メディアスキャンをする


##  Android端末内のディレクトリを読み込んで、オリジナル保存フォルダを生成する

```
 void Scan(string fileName)
	{
		#if !UNITY_EDITOR && UNITY_ANDROID && !UNITY_IOS
		using (AndroidJavaClass jcUnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
		using (AndroidJavaObject joActivity = jcUnityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
		using (AndroidJavaObject joContext = joActivity.Call<AndroidJavaObject>("getApplicationContext"))
		using (AndroidJavaObject jcMediaScannerConnection = new AndroidJavaClass("android.media.MediaScannerConnection"))
		using (AndroidJavaClass jcEnvironment = new AndroidJavaClass("android.os.Environment"))
		using (AndroidJavaObject joExDir = jcEnvironment.CallStatic<AndroidJavaObject>("getExternalStorageDirectory"))
		{
		string imageNameDirectory = joExDir.Call<string>("toString") + "/DCIM/Sample/";
		//ディレクトリ内に指定フォルダが存在していなかったら生成する
		if (!Directory.Exists(imageNameDirectory))
		{
		System.IO.Directory.CreateDirectory(imageNameDirectory);
		return;
		}
		imageName = joExDir.Call<string>("toString") + "/DCIM/Sample/" + fileName;
		}
		#endif   
	}
	
```

後述するメディアスキャンの際にも似たようなことをしますが、AndroidのギャラリーのディレクトリはiOSのように統一されていないためルートディレクトリを取得するには上記の方法をとって取得します。（もっといい方法がある気が。。。）

ギャラリーのルートディレクトリを取得したら
```System.IO.Directory.CreateDirectory(imageNameDirectory)```
で指定したフォルダを生成します。今回はSampleというフォルダを生成してその中にスクショデータを保存していきます。


## スクリーンショットをとる

```
public class UnityAndroid : MonoBehaviour
{
  private string originName = "";
	private string imageName = "";
	
	...
	
 IEnumerator Captcha() {

    originName = System.DateTime.Now.ToString ("gyyyyMMddtthhmmssfff") + ".png";
		imageName = originName;

		//Android時パスを追加
		#if !UNITY_EDITOR && UNITY_ANDROID && !UNITY_IOS
		Scan(imageName);
		#endif
		yield return new WaitForEndOfFrame();

		//スクリーンショットをとる
		ScreenCapture.CaptureScreenshot(imageName);
		
		//↑モバイルではうまく動かないようなので保険でFileStreamで保存する
		//（スクリーン画面からテクスチャデータへと保存するためのピクセルデータを読み込む）
		var tex = new Texture2D (Screen.width, Screen.height, TextureFormat.RGB24, false);
		tex.ReadPixels (new Rect (0, 0, Screen.width, Screen.height), 0, 0);
		tex.Apply();
	...
 }
 ...
}
```



## バイナリファイルに書き込む

```
public class UnityAndroid : MonoBehaviour
{	
	...
	
 IEnumerator Captcha() {
  	...
		using (FileStream BinaryFile = new FileStream (imageName, FileMode.Create, FileAccess.Write)) 
		{
    		using (BinaryWriter Writer = new BinaryWriter (BinaryFile)) 
    		{
        		Writer.Write (tex.EncodeToPNG ());
    		}
		}

	
		yield return new WaitForEndOfFrame ();
		//保存まで待機
		float latency = 0, latencyLimit=2;
		while (latency < latencyLimit) {
			//ファイルが存在していればループ終了
			if (System.IO.File.Exists (imageName)) {
				break;
			}
			latency += Time.deltaTime;
			yield return null;
		}

		...
	}
	...
}
 
```



## メディアスキャンをする
メディアスキャンとはなんぞと思って調べましたが、つまり画像保存をギャラリーに反映させるために実行することだそうです。
ここで最初に生成したスクショ保存用フォルダのディレクトリを取得して保存してギャラリーに反映させます。

```
void ScanMedia(string fileName)
	{
		if (Application.platform != RuntimePlatform.Android)
			return;
		//メディアスキャン
		#if !UNITY_EDITOR && UNITY_ANDROID && !UNITY_IOS
		using (AndroidJavaClass jcUnityPlayer = new AndroidJavaClass ("com.unity3d.player.UnityPlayer"))
		using (AndroidJavaObject joActivity = jcUnityPlayer.GetStatic<AndroidJavaObject> ("currentActivity"))
		using (AndroidJavaObject joContext = joActivity.Call<AndroidJavaObject> ("getApplicationContext"))
		using (AndroidJavaObject jcMediaScannerConnection = new AndroidJavaClass ("android.media.MediaScannerConnection"))
		using (AndroidJavaClass jcEnvironment = new AndroidJavaClass ("android.os.Environment"))
		using (AndroidJavaObject joExDir = jcEnvironment.CallStatic<AndroidJavaObject> ("getExternalStorageDirectory")) 
		{
		jcMediaScannerConnection.CallStatic ("scanFile", joContext, new string[] { fileName }, new string[] { "image/png" }, null);
		}
		Handheld.StopActivityIndicator();
		#endif
	} 
```

これをCaptchaメソッドの一番最後で実行してAndroidカメラアプリの巨大メソッドは完成です。
あとはuGUI（ボタンなど）で実行してもらうためのメソッドを以下のような感じで作成します。


	public void Cap()
	{
		#if UNITY_ANDROID
			canvasGroup.alpha = 0; 
			StartCoroutine ("Captcha");
		#endif
	}



"canvasGroup.alpha"はCanvasGroupコンポーネントをつけてあるCanvasの子オブジェクトのuGUIの透明度を操作することができます。
これを利用して写真をとる瞬間だけUI非表示にしてあります。


# iOSについて
iOSは基本的にこちらのサイト参考にすれば同じようなものができると思います。
→[Unity+iOSでカメラロールにスクショを保存するまで - Qiita](https://qiita.com/Ryopon/items/a2cf618eb570637deffb)
しかし、これを使用するにあたってUnityのPlayerSettings（プラットフォームはiOSの状態で） -> Other Settings -> Configurationの中の"Camera Usage Description"を記入しておかないとカメラの使用許可が出せず永遠に画面が真っ黒状態になりますのでそこだけ注意してください。

# 最後に
ほとんど参考サイトで実装可能ですが、今回はギャラリーに新たなフォルダを自動生成しそのフォルダに保存したかったので色々調べて実装してみました。
何かの参考になれば幸いです。
もっといい実装方法があればコメントによろしくお願いします。良いARライフを！

# 参考
[Unityでスクリーンショットを撮って保存してメディアスキャン](https://thattown.wixsite.com/thattown/single-post/2017/08/08/cUnity%E3%81%A7%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%E3%82%92%E6%92%AE%E3%81%A3%E3%81%A6%E4%BF%9D%E5%AD%98%E3%81%97%E3%81%A6%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2%E3%82%B9%E3%82%AD%E3%83%B3)
[Unity4.6でAndroidに書きだした時にCaptureScreenshotが正常に動作しないことへの対処方法](http://marunouchi-tech.i-studio.co.jp/1190/)
[Unityで画像をAndroid本体に保存する](https://blog.narumium.net/2016/12/09/unity%E3%81%A7%E7%94%BB%E5%83%8F%E3%82%92android%E6%9C%AC%E4%BD%93%E3%81%AB%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B/)