iOS アプリケーションの一部に Godot のコンテンツを設置する:Godot as a Library 最小実装
Godot ライブラリのヘッダーファイルを公開して iOS アプリケーションのコードから定義を参照するによって、Swift で書かれたアプリケーションのコードから Godot ライブラリの定義が見えるようになりました。しかし、記事の最後にも書いたように、いざ Godot ライブラリに定義されているクラスを参照してアプリケーションをビルドしようとすると、リンカエラーが発生してしまいました。
今回は、このリンカエラーを解消して、自前の iOS アプリケーションのビュー上で Godot エンジンを動作させるところまでをやってみます。
iOSアプリケーション上のビューでGodotエンジンが動いているようす
iOSアプリケーション上のビューでGodotエンジンが動いているようす(ビュー階層)
0. 修正のステップと作成した確認用のブランチ
今回は、本家のリポジトリを Fork し、修正のステップに応じたブランチで動作を確認できるようにしています。
-
reproduce-godot-library-linker-error ブランチ
- iOS プロジェクトビルド時のリンカエラーを再現する
-
fix-godot-library-linker-error ブランチ
- リンカエラーのみを解消する
-
min-godot-as-a-library ブランチ
- Godot を操作して、アプリケーションの一部のビューに埋め込む
1. リンカエラーを調べる
Godot ライブラリのヘッダーファイルを公開して iOS アプリケーションのコードから定義を参照するの最後にリンカエラーのスクリーンショットを掲載しました。以下に再掲します。
GodotApplicalitionDelegate
のインスタンス化のコードを追加すると発生するエラー
argc
, argv
という名前から分かるように、この変数は main 関数に渡されるコマンドライン引数に関するものです。外部から参照された際に発生するこのリンカエラーを解消するだけであれば、extern
の変数宣言を実装ファイル(.m)からヘッダーファイル(.h)に移動し、定義を外部から見えるようにするだけです。
リンカエラーの解消(最小)
さて、上記で修正した gargc
と gargv
は、Godot ライブラリの内部で定義された main 関数が引数として受け取った値を格納してあとで使うために用意された変数です。
iOS アプリケーションの一部として Godot エンジンを動作させる場合、main 関数の制御を iOS アプリケーションが奪う必要がありますから、 gargc
と gargv
の扱いを根本的に変える必要があります。
gargc
と gargv
を Gogot の外部から渡すように修正する
2. Swift で iOS アプリケーションを作成する場合、main 関数に渡される argc
, argv
に相当する値は、それぞれ CommandLine.argc
と CommandLine.unsafeArgv
から参照できます。
gargc
と gargv
は Godot の内部にあるファイル platform/ios/app_delegate.mm
で使用されています。
元の gargc
と gargv
の定義は削除して新しくインスタンス変数を定義し、外部から値を指定するためのメソッドを用意しておきます。
@implementation AppDelegate {
int gargc;
char **gargv;
}
// ...
- (void)setLaunchArguments:(int)_gargc argv:(char **)_gargv {
gargc = _gargc;
gargv = _gargv;
}
外部から定義が見えるように、ヘッダーファイルに宣言を追加することを忘れないようにします。
3. アプリケーションの主導権を奪い、Godot のクラスを呼び出す
Godot iOS アプリケーションの main 関数のありかを探るでは、Godot ライブラリの内部に定義されている main 関数がアプリケーションのエントリポイントとして振る舞い、結果としてアプリケーション側のコードに main 関数を定義しなくても動作が開始されるようになっていることを書きました。
このような状況で、さらにアプリケーション側に main 関数を定義すると、アプリケーション側の main 関数が実行され Godot ライブラリの main 関数は実行されなくなります。これによって、プログラムのエントリポイントがアプリケーションに移ります。
import UIKit
exit(
UIApplicationMain(
CommandLine.argc,
CommandLine.unsafeArgv,
nil,
NSStringFromClass(ApplicationDelegate.self)
)
)
上のコードでは、アプリケーション側で定義された ApplicationDelegate
クラスのインスタンスを UIApplicationDelegate
として使用するように指定をしています。少し長いですが、ApplicationDelegate
の実装も記載しておきます。
import CoreGraphics
import Foundation
import Godot
let godotApplicalitionDelegate = Godot.GodotApplicalitionDelegate()
final class ApplicationDelegate: UIResponder, UIApplicationDelegate {
/// Godot Framework 内で動的に `window` プロパティへのアクセスが発生するらしく、この定義は必須。
var window: UIWindow?
var godotWindow: UIWindow? {
GodotApplicalitionDelegate.services.compactMap({ $0 as? Godot.AppDelegate }).first?.window
}
var godotViewController: UIViewController {
Godot.AppDelegate.viewController
}
var godotView: Godot.GodotView {
Godot.AppDelegate.viewController.godotView
}
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
if let godotAppDelegate = GodotApplicalitionDelegate.services.compactMap({ $0 as? Godot.AppDelegate }).first {
godotAppDelegate.setLaunchArguments(CommandLine.argc, argv: CommandLine.unsafeArgv)
}
godotApplicalitionDelegate.application(application, willFinishLaunchingWithOptions: launchOptions)
return true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
godotApplicalitionDelegate.application(application, didFinishLaunchingWithOptions: launchOptions)
let screenBounds = UIScreen.main.bounds
let viewController = MainViewController()
window = UIWindow(frame: screenBounds)
window?.rootViewController = viewController
window?.makeKeyAndVisible()
viewController.loadViewIfNeeded()
viewController.containerView.addSubview(godotViewController.view)
godotViewController.view.frame = CGRect(
x: 0,
y: (viewController.containerView.bounds.height - godotViewController.view.bounds.height) / 2,
width: viewController.containerView.bounds.width,
height: screenBounds.height
)
return true
}
func applicationWillResignActive(_ application: UIApplication) {
godotApplicalitionDelegate.applicationWillResignActive(application)
}
func applicationDidBecomeActive(_ application: UIApplication) {
godotApplicalitionDelegate.applicationDidBecomeActive(application)
}
}
ApplicationDelegate
クラスの内部で Godot.GodotApplicalitionDelegate
クラスのインスタンスを生成し、必要な処理を呼び出しています。また、Godot.AppDelegate.viewController
を取得し、画面に view
を設置しています。この view
プロパティには GodotView
クラスのインスタンスが格納されていて、結果として画面中央に Godot のコンテンツが描画されることになります。
iOSアプリケーション上のビューでGodotエンジンが動いているようす(再掲)
4. 今後の展望
ここまで、Godot as a library の最小実装を試すことも目標に、数回に分けて記事を書きました。
- Godot エディタをソースコードから macOS 向けにビルドする
- Godot の iOS カスタムテンプレートを作成して Xcode プロジェクトをエクスポートする
- Godot iOS アプリケーションの main 関数のありかを探る
- Godot ライブラリのヘッダーファイルを公開して iOS アプリケーションのコードから定義を参照する
- この記事
この記事で当初想定していた最小実装はできたのですが、実用を考えた場合、iOS アプリケーションと Godot ライブラリ間のコミュニケーションを確立して、より柔軟に Godot のコンテンツを操作できるようになる必要があり、次の課題だろうと思っています。
Discussion
SwiftアプリにGodotを埋め込めるSwiftGodotKitとSwiftからGodot APIを呼び出せるSwiftGodotを組み合わせる方法もあります