こんにちは。新人プログラマーの岩本です。
今回はUnity as a LibraryをiOSで使う方法を解説します。
UaaLを使えるようになると、3D描画や物理シュミレーションなどSwiftだけでは実装が難しいことができるようになります。
ぜひ最後までご覧ください。
Unity as a Libraryとは
Unity as a Library(以下UaaL)とは、ネイティブアプリにUnityの3Dモデルや画面を表示させる技術のことです。
特に3Dモデルと相性が良く、MirrativやRealityなどのアプリでも使われているそうです。
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」を選択し、立方体を表示させます。
次に画面上部のナビゲーションから「File」→「Build Settings」を選択し設定画面を開きます。
PlatformからiOSを選択し「Switch Platform」をクリックしてください。
処理が終わったら「Build And Run」を実行します。
これでUnityプロジェクトから、iOS向けのビルドを作成することができました。
UnityFrameworkを作成する
次にネイティブアプリに取り込む用のUnityFrameworkを作成していきます。
前の手順で「Build And Run」を実行すると、自動でXcodeが立ち上がり、Unity-iPhone.xcodeproj
が開かれます。
まずはLibraryフォルダにPlugins/iOSフォルダを作り、その中に NativeCallProxy.h
と NativeCallProxy.mm
を作成します。
// [!] 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
#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」にもチェックを入れておいてください。
最後にSchemeから「UnityFramework」を選択しビルドしてください。
ビルドが終わるとUnityFramework.frameworkが作成されます。
ProductsからUnityFrameworkを選択し、Full Pathの矢印をダブルクリックするとFinderで場所を確認できます。
UaaLをiOSアプリで動かす
先ほど作成したUnityFramework.frameworkを使って、ネイティブアプリでUnityの画面を表示させます。
まずはXcodeで適当なプロジェクトを作成してください。
作成したら、プロジェクトのフォルダにUnityFramework.frameworkをコピーしてきてください。
次に「TARGETS」→「General」を開き、「Frameworks, Libralies, and Embedded Content」のプラスボタンを押し、「Add Files」を選択して、コピーした「UnityFramework」を追加します。
次にBridging-Headerファイルを作成します。
Bridging-HeaderとはSwiftからObjective-Cのコードを呼び出すための橋渡し的なファイルです。
プロジェクト名-Bridging-Header.h を作成し、書いてあるコードを削除して、以下のコードを記述します。
#include <UnityFramework/NativeCallProxy.h>
次にTargetのBuild Settingsを開き、Swift Compiler – GeneralのObjective-C Bridging-Headerに追加したBridging-Headerを追加します。
ここからはコードを書いていきます。
Unity.swift
、UnityView.swift
、AppDelegate.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!) {
}
}
import SwiftUI
struct UnityView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
return Unity.shared.view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
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に以下のコードを追加します。
import SwiftUI
@main
struct UaaLSampleApp: App {
@UIApplicationDelegateAdaptor var appDelegate: AppDelegate // 👈追加
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
次にContentViewでUnityViewを表示させるようにします。
import SwiftUI
struct ContentView: View {
var body: some View {
UnityView()
}
}
最後に、Schemeから「Edit Scheme」を選択し、「Run」→「Diagnostics」→「Runtime API Checking」の「Thread Performance Checker」のチェックを外してください。
これがオンになっていると、アプリ起動時にエラーになってしまいます。
これで実機でアプリを起動してください。Unityの画面が表示されているはずです!
まとめ
今回はUnity as a LibraryをiOSアプリで動かすまでを解説しました。
UaaLを使えるようになると、アプリの表現の幅が一気に広がります。
ぜひご自身の手で色々と試してみてください。
ここまでのご閲覧ありがとうございました!
参考にした記事
https://dhero.hatenablog.com/entry/2021/06/25/Unity_as_a_Libraryと_SwiftUI_で作るARアプリ_(前編)
コメント