e.blog

主にUnity/UE周りのことについてまとめていきます

簡易的なiOS向けネイティブプラグインを書いてみる

概要

少し前にAndroid向けのネイティブプラグインを書くための記事を書きました。

edom18.hateblo.jp

edom18.hateblo.jp

今回はiOS向けのプラグインです。
が、今回書くのはごく簡単なプラグイン機能の実装です。

つまりmmファイルを作成してそれを利用するだけの簡単な実装です。
発端はARKitアプリで画面をキャプチャする必要があったのでそのための準備です。

こちらの記事を参考にさせていただきました。

marunouchi-tech.i-studio.co.jp

ネイティブ側の処理を「.mm」ファイルに書く

Plugins/iOS以下にファイルを置く

なにはなくともネイティブ側で動作する処理を書かないとなりません。
UnityではPluginsフォルダ以下にiOSというフォルダを作ることで、自動的にXcodeプロジェクトにファイルがエクスポートされるようになっています。

今回実装した例では以下のようにファイルを配置しています。

f:id:edo_m18:20180430050855p:plain

CaptureCallbackScreenshotPluginプラグイン用のファイルです。今回は.h.mmファイルを配置しています。

今回はこの.mmファイルにネイティブ側の処理を書いていきます。
Xcodeプロジェクトにエクスポートされるため、普通のiOS開発と同じ感覚で処理を記述することができます。(つまりObjective-Cをそのまま書ける)

※ エクスポートされた状態↓
f:id:edo_m18:20180509093631p:plain

参考にさせていただいた記事から引用させてもらうと、以下のような形で書くことでネイティブ側の処理を書くことができます。

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を安全に使うために、というこちらの記事も合わせて読んでみてください。

kan-kikuchi.hatenablog.com