おもちゃラボ

Unityで遊びを作ってます

【Unity】iOS13で写真やスクショをカメラロールに保存する

Unityでスクリーンショットを撮影するのはとても簡単なのですが、これをiOSのカメラロールに保存するのは案外手間がかかります。ここではその方法を簡単に紹介したいと思います。

スクリーンショットをカメラロールに保存する流れは次のとおりです。

Unityでスクリーンショットを撮影する

Unityで画面のキャプチャするにはScreenCapture.CaptureScreenshotメソッドを使います。(少し前まではApplication.CaptureScreenshotでしたが、機能は同じです)

このCaptureScreenshotメソッドの引数にファイル名を渡すことで、Application.persistentDataPathが指すフォルダにスクショ画像が保存されます。

iOSでApplication.persistentDataPathは、作成中のアプリ内のDocumentフォルダを指しています。なので、カメラロールに保存したい場合には、保存したフォルダからカメラロールに画像をコピーする必要があるわけです。

f:id:nn_hokuson:20180501200226p:plain:w380

スクショをカメラロールにコピーするPluginを作る

ただし、Unity標準のAPIを使って、アプリ内のフォルダからカメラロールに画像を移すことはできません。そこで、上の工程を行うiOS用のPluginを作成する必要があります。

それではPluginを作っていきましょう。Assetフォルダの下にPluginsフォルダを作成し、その下にiOSフォルダを作成してください。そしてiOSの中にScreenShot.mmという名前でファイルを作成します。.mmの拡張子はObjective-C++のファイルを表します。

f:id:nn_hokuson:20180501200456p:plain:w400

作成したScreenShot.mmファイルに次のプログラムを入力してください。

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <Photos/Photos.h>

extern "C" void SaveToAlbum (const char* path)
{
    // アルバム名を取得する
    PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
    fetchOptions.predicate = [NSPredicate predicateWithFormat:@"title = %@", @"アルバム名"];
    PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAny options:fetchOptions];

    // ファイルパスをURLにする
    NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path]];

    // 写真を保存する
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        PHAssetChangeRequest *assetChangeRequest;
        assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url];
        PHAssetCollectionChangeRequest *assetCollectionChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:fetchResult.firstObject];
        [assetCollectionChangeRequest addAssets:@[[assetChangeRequest placeholderForCreatedAsset]]];

      } completionHandler:^(BOOL success, NSError *error) {
      }];
}
||< 

このプログラムでは引数で受け取ったパスにある画像ファイルを、カメラロールに保存し直しています。カメラロールに保存するにはPHPhotoLibraryクラスを利用します。「アルバム名」の部分はiPhoneに保存されているアルバム名を指定します。<b>デフォルトで用意されている「最近の項目」のアルバムは指定できないようなので、新規にアルバムを作るほうが良い</b>と思います。

このPluginではPhotosをインポートしているので、<b> Photos Frameworkをプロジェクトに追加しておかないとXcodeのリンク時にリンカエラー</b>が出ます。

Photosを追加するにはScreenShot.mmファイルを選択して、インスペクタの「Platform settings→Rarely used frameworks→ Photos」にチェックを入れます。

[f:id:nn_hokuson:20200226221814p:plain:w700]

*Pluginでスクショをアプリ内からカメラロールにコピーする
画像ファイルをカメラロールに保存するPluginが出来たので、UnityからこのPluginを呼び出す部分を作りましょう。ScreenShot.csファイルを作成(別にPluginのファイル名と対応する必要はありません)して、空のGameオブジェクトにアタッチします。

[f:id:nn_hokuson:20180501200805j:plain]

次にScreenShot.csファイルに次のスクリプトを入力してください。

>|cs|
using System.Collections;
using UnityEngine;
using System.IO;
using System.Runtime.InteropServices;

public class ScreenShot : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void SaveToAlbum(string path);

    IEnumerator SaveToCameraroll(string path)
    {
        // ファイルが生成されるまで待つ
        while(true)
        {
            if(File.Exists(path))
                break;            
            yield return null;
        }
        
        SaveToAlbum(path);
    }

    void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
#if UNITY_EDITOR
#else
        string filename = "test.png";
        string path = Application.persistentDataPath + "/" + filename;

        // 以前のスクリーンショットを削除する
        File.Delete(path);

        // スクリーンショットを撮影する
        ScreenCapture.CaptureScreenshot(filename);
        
        // カメラロールに保存する
        StartCoroutine(SaveToCameraroll(path));
#endif
    }
}

このプログラムでは、上で紹介したCaptureScreenshotメソッドを使ってスクリーンショットを撮影しています。撮影したパスをPluginのSaveToAlbumメソッドに渡すことで、アプリ内にあるスクリーンショットをカメラロールにコピーしています。

注意点として、CaptureScreenshotはコールしたタイミングで画像を保存するわけではなく非同期に画像を保存します(ヤヤコシイ・・・)従って、画像の保存が完了するまでは、Pluginで画像をコピーするのを待つ必要があります。

これをやっているのがSaveToCamerarollコルーチンです。File.Existsメソッドを使って毎フレームスクショ画像が保存されたかをチェックして、保存されていればPluginを使って画像をカメラロールにコピーします。

Photo Library Additions Usage Descriptionを追加する

iOS11以降では、Photo LibraryにアクセスするためにはInfo.plistに「Privacy - Photo Library Additions Usage Description」を追加する必要があります。

これを設定し忘れると、フォトライブラリにアクセスしようとした時点でアプリがクラッシュします(クラッシュさせなくても、コンパイルエラーで良いと思うのですが・・・)

Unityでビルドしたプロジェクトファイルを開き、Info.plistに「Privacy - Photo Library Additions Usage Description」追加してからXcodeでビルドしましょう。

f:id:nn_hokuson:20180501201158j:plain:w600

これで、Unityで撮影したスクリーンショットを、iOSのカメラロールに保存することができるようになります。

注意点

また、Xcodeからデバッグ中だとカメラロールに正しく保存されずに、実行時エラーになることがあります。その場合は、一度Xcodeのデバッグを停止して、iPhoneのアプリ画面からアプリを起動してみてください。

【Unity】VuforiaでARアプリを作るときのTIPS 10選

Unity2017からARライブラリのVuforiaがUnityに統合され、ますます簡単にARアプリケーションを作ることができるようになりました。ここではUnityでVuforiaを使う時に使えるTIPSとハマりやすいポイントを合わせて10個紹介したいと思います!

Vuforiaのセットアップ方法から、マーカの上にARで立方体を出すまでの入門講座はこちらをご覧ください。
nn-hokuson.hatenablog.com

UnityでVuforiaを使う

Vuforiaを使用するには、メニューバーからEdit→Project Settings→Playerを選択し、インスペクタのXR Settings→Vuforia Augmented Reality Supportedにチェックを入れるだけです。

f:id:nn_hokuson:20180404203034p:plain:w300

注意点として、この設定はPC/iOS/Androidごとに分かれているので、iOSやAndroidビルドするときにはモバイル用のSettingsにも忘れずにチェックを入れないと次のようなエラーが出ます。

Assets/Vuforia/Scripts/DefaultInitializationErrorHandler.cs(10,7): error CS0246: The type or namespace name `Vuforia' could not be found. Are you missing an assembly reference?

f:id:nn_hokuson:20180404203046p:plain:w600

オートフォーカスを使いたい

iOSでは基本的にオートフォーカスが効くようですが、Androidの一部端末ではオートフォーカスがかからないことがあります。

この場合は次のスクリプトをARCameraにアタッチすることでオートフォーカスが効くようになります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Vuforia;
using UnityEngine.UI;

public class FocusController : MonoBehaviour {
    private bool mVuforiaStarted = false;

    void Start () 
    {
        VuforiaARController vuforia = VuforiaARController.Instance;
        if (vuforia != null)
            vuforia.RegisterVuforiaStartedCallback(StartAfterVuforia);
    }

    private void StartAfterVuforia()
    {
        mVuforiaStarted = true;
		CameraDevice.Instance.SetFocusMode(CameraDevice.FocusMode.FOCUS_MODE_CONTINUOUSAUTO)
    }

    void OnApplicationPause(bool pause)
    {
        if (!pause && mVuforiaStarted)
        {
			CameraDevice.Instance.SetFocusMode(CameraDevice.FocusMode.FOCUS_MODE_CONTINUOUSAUTO)
        }
    }
}

参考サイト
Camera Focus mode - Android - Unity | Vuforia Developer Portal

シーン再読み込みでライティングがおかしくなる

Vuforiaを使ったシーンを再読み込みすると、ライトが消えたようなおかしな画面になってしまうことがあります。マテリアルにStandardシェーダを使っている場合は特に気になります。

f:id:nn_hokuson:20180404203104p:plain

その場合は手動でライトのベイクを一度行うことで解決するようです。手動でベイクするにはメニューバーのWindow→Lighting→Settingsで表示されるライトの設定の一番下にあるDebug SettingsのAuto Generateのチェックを外してGenerate Lightingのボタンを押します。

f:id:nn_hokuson:20180404203117p:plain:w400

アプリ起動時にはVuforiaを起動したくない

XR SettingsのVuforia Augmented Reality Supportedにチェックを入れると、ARを使用したくないシーンでも強制的にカメラが起動してしまいます・・・(AR Cameraオブジェクトを配置していないにも関わらず!)このままではアプリ起動時はメニュー画面を表示してから、AR画面に遷移する・・・のようなことができないのです。

解決策は、とても意味不明なのですが、ARを使用したくないシーンのカメラ(ARを使うシーンのAR Cameraじゃなくて!!!)にVuforia Behaviourコンポーネントをアタッチし、チェックを外します(非アクティブにする)

f:id:nn_hokuson:20180404203124p:plain:w400

これでARカメラを起動しないシーンができます。あとはSceneManagerのLoadSceneなどを使ってARのシーンに移動すれば、ちゃんとARカメラが起動します。Vuforia Configurationの中にあるDelayed Initializationなどは全てトラップです。勘弁してください・・・

あれ?モデルが表示されない?

使用したいARマーカーのデータベースがアクティベートされていない可能性があります。一度チェックを入れても、マーカーをシーンビューに追加したタイミングなどでちょくちょく外れます(なんでやねん!)

Vuforiaの設定画面(AR CameraにアタッチされているVuforia BehaviourスクリプトのOpen Vuforia Configurationボタンをクリック)を開き、Databasesの欄をみて、使用したいマーカーのデータベースにチェックが入っているかを確認してください。

f:id:nn_hokuson:20180404203150p:plain:w400

あれ?モデルが表示されない2?

マーカーの上に表示したいオブジェクトはマーカー(VuforiaのImage)の子要素にする必要があります。ヒエラルキービューで表示したいオブジェクトをマーカーにドラッグ&ドロップして下さい。

f:id:nn_hokuson:20180404203250j:plain

ARオブジェクトをタッチしたい

ARカメラからRayを飛ばしてオブジェクトに当たったかどうかを判定します。まずはARで表示するオブジェクトにCollisionを設定してから、次のスクリプトを作成してAR Cameraにアタッチしてください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LockMaster : MonoBehaviour
{
    void Update () 
    {
        if (Input.GetMouseButtonDown(0)) 
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit = new RaycastHit();
            if (Physics.Raycast(ray, out hit, 1000)) {
                Debug.Log(hit.gameObject.name);
            }
        }
    }
}

Updateメソッドの中で、タップされた位置からカメラの向いている方向にRayを飛ばして、そのRayがヒットしたオブジェクトを探しています。あとは煮るなり焼くなり・・・です。

ARオブジェクトにこっちを向かせたい

ARオブジェクトが常にカメラを向くように(ビルボード)にするためにはLookAtメソッドを使います。次のスクリプトをARオブジェクトにアタッチしてください。

using UnityEngine;

public class LookAtCamera : MonoBehaviour 
{
    public GameObject camera;

    void Update () 
    {
        transform.LookAt(camera.transform);
    }
}

アタッチしたスクリプトのcameraの欄にヒエラルキービューのARCameraをドラッグ&ドロップしてください。このスクリプトではUpdateメソッドの中でLookAtのターゲットとしてAR Cameraの場所を指定しています。これによりカメラが移動した場合でも常にカメラに対して正面を向くようになります。

ARで表示したキャラクタの頭だけをこちらに向かせたい、といった場合はIKを使う必要があります。次の記事を参考にしてください。

nn-hokuson.hatenablog.com

なんかアプリの起動時間が長い

それはVuforiaのせいではないかもしれません(Vuforiaのせいにしていた時代がありました、ごめんなさい)。大きなテクスチャを使用している場合、シーンのロードに時間がかかります。テクスチャのインスペクタからMax Sizeを制限してみてください。

f:id:nn_hokuson:20180404203309p:plain:w350

あとSubstanceで作られたMaterialを読み込むのにも、かなり時間かかります。こちらもインスペクタから「Bake and discard substance」に設定するとシーンのロード時間が速くなります。

f:id:nn_hokuson:20180404203359p:plain:w500

UnityEditorで実行するとクラッシュする

プロジェクトフォルダを置いているパスに日本語が含まれているとクラッシュするようです(何時代だよ・・・)