概要
少し前にAndroid向けのネイティブプラグインを書くための記事を書きました。
今回はiOS向けのプラグインです。
が、今回書くのはごく簡単なプラグイン機能の実装です。
つまりmm
ファイルを作成してそれを利用するだけの簡単な実装です。
発端はARKitアプリで画面をキャプチャする必要があったのでそのための準備です。
こちらの記事を参考にさせていただきました。
marunouchi-tech.i-studio.co.jp
ネイティブ側の処理を「.mm」ファイルに書く
Plugins/iOS以下にファイルを置く
なにはなくともネイティブ側で動作する処理を書かないとなりません。
UnityではPluginsフォルダ以下にiOS
というフォルダを作ることで、自動的にXcodeプロジェクトにファイルがエクスポートされるようになっています。
今回実装した例では以下のようにファイルを配置しています。
CaptureCallback
とScreenshotPlugin
がプラグイン用のファイルです。今回は.h
と.mm
ファイルを配置しています。
今回はこの.mm
ファイルにネイティブ側の処理を書いていきます。
Xcodeプロジェクトにエクスポートされるため、普通のiOS開発と同じ感覚で処理を記述することができます。(つまりObjective-Cをそのまま書ける)
※ エクスポートされた状態↓
参考にさせていただいた記事から引用させてもらうと、以下のような形で書くことでネイティブ側の処理を書くことができます。
Cの関数として定義
// ScreenshotPlugin.mmの一部 extern "C" void _PlaySystemShutterSound() { AudioServicesPlaySystemSound(1108); } // ... 後略
Objective-Cも当然使える
// CaptureCallback.h #import <Foundation/Foundation.h> @interface CaptureCallback : NSObject @property (nonatomic, copy) NSString *objectName; @property (nonatomic, copy) NSString *methodName; - (id)initWithObjectName:(NSString *)_objectName methodName:(NSString *)_methodName; - (void)savingImageIsFinished:(UIImage *)_image didFinishSavingWithError:(NSError *)_error contextInfo:(void *)_contextInfo; @end
要はiOS開発でやっていることをUnity上で管理させてエクスポート、ビルド時に結合する、という形です。
なので、iOS開発をしたことがある人であれば新しいことはほぼないと思います。
Unityとネイティブ側で連携する
さて、ネイティブ側の処理は上記のように記述することで対応できます。
あとは今実装したネイティブ側の処理とUnity側で連携させれば完成です。
作成したネイティブ側の機能を利用するには以下のようにC#で記述します。
using System.Runtime.InteropServices; // DLLImport public class Screenshot : MonoBehaviour { [DllImport("__Internal")] static private extern void _PlaySystemShutterSound(); // ... 以下略 }
DllImport
アトリビュートを使って、そのメソッドが外部で定義されていることを伝えます。
また、static extern
を付ける必要があります。
関数名とメソッド名が同じになっていることを確認してください。
あとは通常のUnity開発と同様に、上記コードのように宣言だけを行ったメソッドを実行してやればXcodeプロジェクトのビルド時に自動的にリンクが行われ、正常に動作するようになります。
ハマった点
Photos.frameworkを追加する
今回のプラグインではスクリーンショットを撮ってそれをカメラロールに保存する、というものです。
そのためPHPhotoLibrary
を使っているのですが、必要なPhotos.framework
はデフォルトで追加されないので、Xcode上で追加してやるかポストプロセスで自動化する必要があります。
既存のフレームワークを追加するだけなので、以下のようにポストプロセス用のエディタスクリプトを書くだけで簡単に追加することができます。
using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEditor; using UnityEditor.Callbacks; using UnityEditor.iOS.Xcode; public class XcodePostProcess : MonoBehaviour { [PostProcessBuild] static public void OnPostProcessBuild(BuildTarget buildTarget, string path) { if (buildTarget != BuildTarget.iOS) { return; } string projPath = PBXProject.GetPBXProjectPath(path); PBXProject proj = new PBXProject(); proj.ReadFromString(File.ReadAllText(projPath)); string target = proj.TargetGuidByName("Unity-iPhone"); // フレームワークを追加する proj.AddFrameworkToProject(target, "Photos.framework", false); // 反映させる File.WriteAllText(projPath, proj.WriteToString()); } }
NSPhotoLibraryUsageDescription keyを追記する
上記でも書いたように、カメラロールへのアクセスが必要です。
いつかのupdateでiOSでは、明確に、権限を求める理由を記載する必要があります。
UnitySendMessageのためのstring変換
Objective-Cでは通常、stringはNSString
を利用します。
しかし、UnitySendMessageはchar *
型を受け取るため、変換が必要です。
また、今回は写真を保存したあとにコールバックを呼ぶ関係で、オブジェクト名をネイティブ側に渡す必要があるため、相互の変換が必要となります。
相互変換
// NSString* -> char* NSString *hogeStr = @"hoge"; char *hogeChar = [hogeStr UTF8String]; // char* -> NSString* const char* hogeChar = "hoge"; NSString *hogeStr = [NSString stringWithCString:hogeChar encoding:NSUTF8StringEncoding];
実際に定義、呼び出す場合は以下のようになります。
extern "C" void _AnyFunction(const char* objectName, const char* methodName) { NSString* objName = [NSString stringWithCString:objectName encoding:NSUTF8StringEncoding]; NSString* metName = [NSString stringWithCString:methodName encoding:NSUTF8StringEncoding]; // do something. }
UnitySendMessage
を安全に使うために、というこちらの記事も合わせて読んでみてください。