【SwiftでUnityを使う】Unity as a Library入門

Swift

こんにちは。新人プログラマーの岩本です。

今回はUnity as a LibraryをiOSで使う方法を解説します。

UaaLを使えるようになると、3D描画や物理シュミレーションなどSwiftだけでは実装が難しいことができるようになります。

ぜひ最後までご覧ください。

この記事は新卒エンジニアが執筆しています。
そのため内容に間違いや不備がある場合があります。
もし間違いを発見しましたら、どんどん指摘していただけると幸いです。

Unity as a Libraryとは

Unity as a Library(以下UaaL)とは、ネイティブアプリにUnityの3Dモデルや画面を表示させる技術のことです。

特に3Dモデルと相性が良く、MirrativRealityなどのアプリでも使われているそうです。

iOSとAndroidの両方に対応しているため、同じUnityプロジェクトを使って、両方のプラットフォームで使用することも大きな利点です。

UaaLをiOSアプリで使用するまで

開発環境

  • Unity: 2022.3.4.0f1
  • Xcode: 15.4

UaaLをiOSアプリに導入して、ビルドするまでの方法を解説します。

今回作るアプリは以下のような、Unityの画面を表示するだけの簡単なアプリです。

UnityプロジェクトからiOS向けビルドを作成

まずはUnityプロジェクトからiOS向けのビルドを作成するまでを解説します。

UnityHubを起動し、適当なプロジェクトを作成します。

今回はテンプレートから「3D」を選び、「UaaLSample」という名前にしました。

プロジェクトが立ち上がったら、左のプラスボタンから「3D Object」→「Cube」を選択し、立方体を表示させます。

Screenshot

次に画面上部のナビゲーションから「File」→「Build Settings」を選択し設定画面を開きます。

PlatformからiOSを選択し「Switch Platform」をクリックしてください。

処理が終わったら「Build And Run」を実行します。

単に「Build」を実行するだけでは後にビルドエラーが発生したため、「Build And Run」を選んでください。

これでUnityプロジェクトから、iOS向けのビルドを作成することができました。

UnityFrameworkを作成する

次にネイティブアプリに取り込む用のUnityFrameworkを作成していきます。

前の手順で「Build And Run」を実行すると、自動でXcodeが立ち上がり、Unity-iPhone.xcodeprojが開かれます。

まずはLibraryフォルダにPlugins/iOSフォルダを作り、その中に NativeCallProxy.hNativeCallProxy.mm を作成します。

NativeCallProxy.h
// [!] important set UnityFramework in Target Membership for this file
// [!]           and set Public header visibility

#import <Foundation/Foundation.h>

// NativeCallsProtocol defines protocol with methods you want to be called from managed
@protocol NativeCallsProtocol
@required
- (void) showHostMainWindow:(NSString*)color;
// other methods
@end

__attribute__ ((visibility("default")))
@interface FrameworkLibAPI : NSObject
// call it any time after UnityFrameworkLoad to set object implementing NativeCallsProtocol methods
+(void) registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi;

@end
NativeCallProxy.mm
#import <Foundation/Foundation.h>
#import "NativeCallProxy.h"

@implementation FrameworkLibAPI

id<NativeCallsProtocol> api = NULL;
+(void) registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi
{
    api = aApi;
}

@end

extern "C" {
    void showHostMainWindow(const char* color) { return [api showHostMainWindow:[NSString stringWithUTF8String:color]]; }
}

次にTarget Membershipの設定をしていきます。

まずはNativeCallProxy.hのTarget Membershipの「UnityFramework」にチェックをいれ、publicにしてください。

同じように、DateフォルダのTarget Membershipの「UnityFramework」にもチェックを入れておいてください。

Screenshot

最後にSchemeから「UnityFramework」を選択しビルドしてください。

Screenshot

ビルドが終わるとUnityFramework.frameworkが作成されます。

ProductsからUnityFrameworkを選択し、Full Pathの矢印をダブルクリックするとFinderで場所を確認できます。

Screenshot

UaaLをiOSアプリで動かす

先ほど作成したUnityFramework.frameworkを使って、ネイティブアプリでUnityの画面を表示させます。

まずはXcodeで適当なプロジェクトを作成してください。

作成したら、プロジェクトのフォルダにUnityFramework.frameworkをコピーしてきてください。

次に「TARGETS」→「General」を開き、「Frameworks, Libralies, and Embedded Content」のプラスボタンを押し、「Add Files」を選択して、コピーした「UnityFramework」を追加します。

Screenshot

次にBridging-Headerファイルを作成します。

Bridging-HeaderとはSwiftからObjective-Cのコードを呼び出すための橋渡し的なファイルです。

プロジェクト名-Bridging-Header.h を作成し、書いてあるコードを削除して、以下のコードを記述します。

UaaLSample-Bridging-Header.h
#include <UnityFramework/NativeCallProxy.h>

次にTargetのBuild Settingsを開き、Swift Compiler – GeneralのObjective-C Bridging-Headerに追加したBridging-Headerを追加します。

Screenshot

ここからはコードを書いていきます。

Unity.swiftUnityView.swiftAppDelegate.swiftをそれぞれ作成し、以下のコードを記述します。

Unity.swift
import Foundation
import UnityFramework

class Unity: NSObject, UnityFrameworkListener, NativeCallsProtocol {
    static let shared = Unity()
    private let unityFramework: UnityFramework

    override init() {
        let bundlePath = Bundle.main.bundlePath
        let frameworkPath = bundlePath + "/Frameworks/UnityFramework.framework"
        let bundle = Bundle(path: frameworkPath)!
        if !bundle.isLoaded {
            bundle.load()
        }
        let frameworkClass = bundle.principalClass as! UnityFramework.Type
        let framework = frameworkClass.getInstance()!
        if framework.appController() == nil {
            var header = _mh_execute_header
            framework.setExecuteHeader(&header)
        }
        unityFramework = framework
        super.init()
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
        unityFramework.register(self)
        FrameworkLibAPI.registerAPIforNativeCalls(self)
        unityFramework.setDataBundleId("com.unity3d.framework")
        unityFramework.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: launchOptions)
    }
    
    func start() {
        unityFramework.register(self)
        FrameworkLibAPI.registerAPIforNativeCalls(self)
        unityFramework.setDataBundleId("com.unity3d.framework")
        unityFramework.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: nil)
    }

    var view: UIView {
        unityFramework.appController()!.rootView!
    }

    func showHostMainWindow(_ color: String!) {

    }
}
UnityView.swift
import SwiftUI

struct UnityView: UIViewRepresentable {
    func makeUIView(context: Context) -> some UIView {
        return Unity.shared.view
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
    }
}
AppDelegate.swift
import UIKit

final class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        Unity.shared.start()        
        return true
    }
}

次にアプリ名App.swiftに以下のコードを追加します。

UaaLSampleApp.swift
import SwiftUI

@main
struct UaaLSampleApp: App {
    @UIApplicationDelegateAdaptor var appDelegate: AppDelegate // 👈追加
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

次にContentViewでUnityViewを表示させるようにします。

ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        UnityView()
    }
}

最後に、Schemeから「Edit Scheme」を選択し、「Run」→「Diagnostics」→「Runtime API Checking」の「Thread Performance Checker」のチェックを外してください。

これがオンになっていると、アプリ起動時にエラーになってしまいます。

Screenshot

これで実機でアプリを起動してください。Unityの画面が表示されているはずです!

まとめ

今回はUnity as a LibraryをiOSアプリで動かすまでを解説しました。

UaaLを使えるようになると、アプリの表現の幅が一気に広がります。

ぜひご自身の手で色々と試してみてください。

ここまでのご閲覧ありがとうございました!

参考にした記事

Unity as a Libraryを使ってみよう ~AndroidとUnityだってチームを組める~ - Qiita
はじめにこんにちは!東海メンターのだすです。この記事はLife is Tech ! Tokai Advent Calendar 2022の1日目の記事です。Android(iOS)とUnit…

https://dhero.hatenablog.com/entry/2021/06/25/Unity_as_a_Libraryと_SwiftUI_で作るARアプリ_(前編)

SwiftUIでUnity as a Libraryを使う最小実装
はじめにSwiftUI で Unity as a Library(以下 UaaL)を使う方法についてです。実際のアプリに組み込むにはもう少し工夫が必要かもしれませんが、とりあえずは表示までできました。UaaL とはネイティブアプリで
Unity as a LibraryをXCFramework化してiOSの実機とシミュレーターの両方で動かせるようにする - Qiita
更新履歴2022/06/12 更新Apple Silicon搭載Macでの動作確認結果について追記UnityとXcodeのバージョンを更新Unity 2021.3.4f1Xcode 13.…

コメント

タイトルとURLをコピーしました