背景
cocos2d-xはC++を用いてマルチプラットフォームでゲーム開発ができるフレームワークです。
iOS/Androidでのゲーム開発で用いられていることが多く、
最近の有名どころでは「ポケモンマスターズ」がcocos2d-xでできています。
このcocos2d-xですが、1つ爆弾を抱えていました。
それはAppleがOpenGLの利用を非推奨としたことです。
cocos2d-xの描画エンジンはOpenGLに依存しており、Appleが近い将来OpenGLを廃止したタイミングで、それ以降cocos2d-x製のソフトがiOS上で動作しなくなってしまうのです。
OpenGLの代替手段として、AppleはMetalを提供しています。
cocos2d-xでも描画エンジンにMetalを利用できるよう開発が進みました。
そして満を持してMetal対応バージョンとなる cocos2d-x v4.0がリリースされました。
Apple環境においてMetalが利用できるようになり、これまでは遅かったiOSシミュレータ上での動作もMetalによって爆速になりました🎉
v4における大きなアップデート
私はcocos2d-x v4.0の特に大きな変更点は以下の2つだと思っています。
- Metalレンダラのサポート
- 全プラットフォームでCMakeを利用したビルドシステムを利用
Metal対応はめでたいですね!
次点のCMake利用についてですが、
cocos2d-xはv3系後半においてゆるやかにCMake移行が行われてきました。
Androidにおいてはビルド環境がCMakeに移行したことでビルドが安定するようになりました。
v4移行における問題点
CMakeに全体移行されたことにより、iOSをはじめとしたApple環境においてはv4へのマイグレーションにやや難が発生しました。
自動生成が前提とされたxcodeproj
xcodeprojファイルは開発者にはおなじみのプロジェクト設定ファイルです。
これまでは、cocos2d-xプロジェクトの新規作成時にxcodeprojファイルも自動で生成されました。
v4においては、以下のコマンドでxcodeprojファイルを生成できます(iOSの場合。以降暗黙にiOSを前提とします)。
$ cmake <プロジェクトルートへのパス> -GXcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos
ここで生成されるxcodeprojはローカルへの依存が大量に埋め込まれてしまいます。
これは、PCや環境が変化するとそのxcodeprojが容易に壊れてしまうということです。例えば、
プロジェクトのあるディレクトリ位置が変化したときなど。
これはつまり、xcodeprojは環境ごとに毎回生成しバージョン管理はしない。という開発者からの暗黙のメッセージだと受け取れます。
既存設定のCMakeへの移行が難しい
さて、毎回xcodeprojを生成すると何が問題となるでしょうか。
私たちは普段、アプリに関する情報をxcodeprojに保存します。
アイコン画像やビルドスキーマの設定などです。
CMakeでxcodeprojを生成する場合、xcodeproj側で設定した項目は再生成のたびに失われます。
設定を維持するためには、これまでxcodeproj内で表現されていたすべての設定をCMakeLists.txt内に書き直さないといけません。
これがなかなかに難しいです。
移行のためにはxcodeprojにおけるある設定がCMakeではどう表現されるかを知る必要があるのですが、
CMakeのXcodeGeneratorについてのドキュメントをみても何も書いてありませんし、実装を見ても全然わかりません。
どこかに良いドキュメントがあれば教えて下さい。
# 例えば、リソースファイルを設定するための記述
set_target_properties(${APP_NAME} PROPERTIES RESOURCE "${APP_UI_RES}")
ネイティブライブラリとの連携も高難度
iOSでは広告SDKなどの外部ライブラリを導入するためにCocoaPodsを利用することがあると思います。
cocos2d-xの公式ドキュメントにはCMakeとCocoaPodsを連携するための手順が記載されていますが、これは pod install
のたびに手動操作が必要で全く現実的ではありません。
これを解決するためには、CocoaPodsが暗に施すビルド設定を手動で設定する必要があります。
幸いにもCocoaPodsはxcconfig1を吐いてくれますし、Ruby製であるためモンキーパッチをあてて挙動を捻じ曲げることができます。
じゃあ頑張ればなんとかなるかな?と思って頑張りましたが、わたしはここで脱落しました。
xcconfigを設定するだけでビルドが通る状態には持ち込めたのですが、肝心のxcconfigをCMakeから設定する方法がわかりませんでした。
その他、全体的に筋肉で解決させなければいけない課題が多かったためここで方針を変える決意をしました。
- モンキーパッチをあてて出力した怪しいxcconfig
解決策 - cocos2d_libs.xcodeprojの自作
cocos2d_libs.xcodeprojは、これまでのcocos2d-xにおいてプロジェクト新規作成時に埋め込まれていたものです。
cocos2d-xのビルド設定はほぼすべてこの中にありました。
これがあれば、これとソースコードを既存プロジェクトのそれと入れ替えるだけで簡単にv4へ移行できるのです。
CMake移行にともなってv4では削除されてしまいましたが、cocos2d_libs.xcodeprojがないなら自分で作ればいいのです。
cocos2d-xはあくまでC++のライブラリの集合体でしかないので、要はコンパイルしてリンクできてしまえば何でもいいのです。
1からxcodeprojを作る難しさ
大規模プロジェクトのxcodeprojを1から作るのは大変です。
単純にファイルを追加していくのが手間で、ちょっとマウス操作をミスると構造が壊れてUndoもできなくて泣きます。
一度作ったとしても、今後cocos2d-xがさらにバージョンアップしてソースファイルが増減したときにまたxcodeprojを編集するのは骨が折れます。
そこで自動生成ツールを使用します。
XcodeGen
XcodeGenは近年iOS開発の現場において人気を獲得しつつあるxcodeproj生成ツールです。
xcodeproj生成という意味ではCMakeと変わりませんが、よりアプリケーション開発に特化して設定ファイルが簡単に書けるようになっています。
以下、XcodeGenを用いてcocos2d_libs.xcodeprojを生成するための手順を解説していきます。
cocos2d-xのプロジェクト構成
cocos2d-xを構成するファイル群のうち、ビルドに必要なものは3つのディレクトリに絞られます。
cocos2d
├── cocos // 本体となるソースファイル群
├── extensions // 拡張機能群。必要ないことも多い
└── external // 本体が依存しているライブラリと開発に使用できる外部ライブラリ
メインとなるソースコード郡のビルド
cocos2d-xライブラリ( libcocos2d.a
) の本体となる cocos
と extensions
は .h
や .cpp
のソースファイル群であるためそのままコンパイル対象として指定します。
これはXcodeGenのproject.yml2では次のように記述されます。
sources:
- path: ../cocos
includes: ["**/*.{h,hpp,c,cc,cpp,mm}"]
- path: ../extensions
includes: ["**/*.{h,hpp,c,cc,cpp,mm}"]
このように記述することで、XcodeGenは対象ディレクトリの中のファイルを自動でxcodeprojにディレクトリ構造をそのままに登録してくれます。
なお、実際の設定にはこれだけでは足らず、他プラットフォーム向けやOpenGL実装のファイルを除外する必要があります。
最終的なproject.ymlは最後に掲載します。
HEADER_SEARCH_PATHSの設定
libcocos2d.a
のソースコードはいくつかの外部ライブラリに依存しているため、コンパイルのためにはその外部ライブラリのヘッダーファイルを与える必要があります。
これはHEADER_SEARCH_PATHSというビルドオプションで指定できます。
いくつかのビルドオプションを与えるために cocos2d_libs.xcconfig
ファイルを作成します。
なおビルドオプションはproject.ymlから与えることもできるのですが、私はできるだけ公式の仕組みに乗っていたほうが良いと考えており可能な範囲は公式の仕組みであるxcconfigを使用します。
HEADER_SEARCH_PATHS = $(SRCROOT)/../ $(SRCROOT)/../cocos $(SRCROOT)/../cocos/platform $(SRCROOT)/../cocos/editor-support $(SRCROOT)/../extensions $(SRCROOT)/../external $(SRCROOT)/../external/chipmunk/include $(SRCROOT)/../external/tinyxml2 $(SRCROOT)/../external/xxhash $(SRCROOT)/../external/xxtea $(SRCROOT)/../external/clipper $(SRCROOT)/../external/edtaa3func $(SRCROOT)/../external/ConvertUTF $(SRCROOT)/../external/poly2tri $(SRCROOT)/../external/md5 $(SRCROOT)/../external/unzip
SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../external/Box2D/include $(SRCROOT)/../external/chipmunk/include $(SRCROOT)/../external/freetype2/include/ios/freetype2 $(SRCROOT)/../external/bullet/include $(SRCROOT)/../external/bullet/include/bullet $(SRCROOT)/../external/jpeg/include/ios $(SRCROOT)/../external/openssl/include/ios $(SRCROOT)/../external/uv/include $(SRCROOT)/../external/webp/include/ios $(SRCROOT)/../external/websockets/include/ios $(SRCROOT)/../external/curl/include/ios $(SRCROOT)/../external/png/include/ios $(SRCROOT)/../external/glsl-optimizer/include
GCC_PREPROCESSOR_DEFINITIONS[config=Debug] = USE_FILE32API __APPLE__ "CC_ENABLE_CHIPMUNK_INTEGRATION=1" "COCOS2D_DEBUG=1"
GCC_PREPROCESSOR_DEFINITIONS[config=Release] = USE_FILE32API __APPLE__ "CC_ENABLE_CHIPMUNK_INTEGRATION=1"
CLANG_ENABLE_OBJC_ARC = NO
ENABLE_BITCODE = NO
(長々としていますが、単にヘッダがあるディレクトリへのパスを羅列しているだけです。)
ついでに他のいくつかのフラグを与えています。例えば CLANG_ENABLE_OBJC_ARC
はObjective-CのARCを切り替えるオプションで、MRCのcocos2d-xでは無効にしています。
使用するxcconfigファイルはXcodeGenでは次のように指定します。
configFiles:
Debug: cocos2d_libs.xcconfig
Release: cocos2d_libs.xcconfig
(CMakeではこれをどう指定するかが分からなかった・・・)
2種類ある外部ライブラリのビルド
残る external
ディレクトリについてですが、この中の外部ライブラリには2種類あります。
ソースファイルが置かれたものと、事前ビルドされたファイルが置かれたものです。
ソースファイルのあるライブラリの場合
そのライブラリを自前でビルドするためにXcodeGenでTargetを生成します。
設定は unzip
を例に、次のようになります。
unzip:
platform: iOS
type: library.static
sources:
- path: ../external/unzip
group: external
includes: ["**/*.{h,hpp,c,cc,cpp}"]
settings:
HEADER_SEARCH_PATHS: $(SRCROOT)/../external
たったこれだけです。これで unzip
Targetが生成され、 libunzip.a
がビルドできるようになります。
dependenciesにTargetを記載しておくと本体のビルド時に一緒にビルドが走るようになるため追加しておきます。
dependencies:
- target: unzip
事前ビルド済みライブラリの場合
その名の通りビルドが済んでいるため、何もすることがありません。
外部ライブラリをリンクする
ここまでの設定で各種ライブラリのビルドが完了しますが、いざアプリケーション側のコードをビルドしようとするとリンクエラーとなります。
アプリケーションバイナリへのstatic libraryのリンクには ld
コマンドを用います。
static libraryのある場所を -L
オプションで指定し、名前を -l
オプションで指定します。
Xcodeはこれを内部的にやってくれています。
通常、このリンク設定はアプリケーション側のビルド設定に記述する必要があります。
既存のアプリのxcodeprojには libcocos2d iOS.a
への依存が記載されているでしょうが、外部ライブラリの依存については記載されていません。
これはどういうことでしょうか。
XcodeにはライブラリTargetの Build Phases > Link Binary With Libraries
の項目にstatic libraryを追加すると、そのライブラリを使うアプリTargetのビルド時に追加したstatic libraryのリンク設定を自動でしてくれる機能があります。
この機能を活用するために Link Binary With Libraries
のところに外部ライブラリ群を入れます。
XcodeGenでは link: true
の記載をするだけです。
dependencies:
- target: unzip
link: true
- framework: ../external/chipmunk/prebuilt/ios/libchipmunk.a
link: true
また、この機能では本来ライブラリTargetの使用しないリンクプロセス用のビルド引数がアプリTargetに引き継がれる特性があるため、その引数も指定します。
LIBRARY_SEARCH_PATHS = $(SRCROOT)/../external/chipmunk/prebuilt/ios $(SRCROOT)/../external/png/prebuilt/ios $(SRCROOT)/../external/jpeg/prebuilt/ios $(SRCROOT)/../external/webp/prebuilt/ios $(SRCROOT)/../external/freetype2/prebuilt/ios $(SRCROOT)/../external/curl/prebuilt/ios $(SRCROOT)/../external/websockets/prebuilt/ios $(SRCROOT)/../external/chipmunk/prebuilt/ios $(SRCROOT)/../external/openssl/prebuilt/ios $(SRCROOT)/../external/bullet/prebuilt/ios $(SRCROOT)/../external/Box2D/prebuilt/ios $(SRCROOT)/../external/uv/prebuilt/ios $(SRCROOT)/../external/glsl-optimizer/prebuilt/ios
これですべての設定が完了しました。
最終的なファイルは次のようになりました→
cocos2d_libs_generate
xcodeprojの生成
v3時代と似た構造にするために、 build
ディレクトリ下にxcodeprojを配置することにします。
設定ファイルを次のように配置し、XcodeGenを実行します。
cocos2d
├── build
│ ├── cocos2d_libs.xcconfig
│ └── project.yml
$ cd cocos2d/build
$ xcodegen generate
生成したxcodeprojを既存のものと入れ替えて、
無事にアプリケーションTargetをビルドすることができました。
一連のファイルとビルド済みxcodeprojと簡単な操作方法は以下のリポジトリに置いておきます。
cocos2d_libs_generate
おわりに
Metalの世界へようこそ!
シミュレータが爆速なことにとにかく感動しています。
私のプロジェクトではGLSLシェーダなどを特にいじってなかったのでビルドさえできれば簡単にMetal移行できましたが、現実としてカスタムシェーダたくさんつかってる大規模プロジェクトではここからが本番という形になると思います。
cocos2d-x製の商業アプリはどれくらいv4移行するんでしょうかね?
その他にUIWebView廃止の話もあったりして、v4に追従しなければいけないのは明確ですが現実としてバックポートで済ます場面もあると思います。
今後のcocos2d-x製アプリケーションの展開が気になりますね!
AndroidではもともとビルドにCMakeが使われているので移行は簡単でした。
Android.mkから脱却できていない場合はこれを機にCMakeへ移行することをオススメします。