[iPhone] cocos2dを静的ライブラリ化して使う
[イントロダクション]
cocos2d for iPhoneはiPhoneアプリケーション製作用のフレームワークで、主に2Dゲームを作る際の基盤に用いられているようである。UIKitより使い勝手が良いと思える点は以下の通り。
- オブジェクトの扱い方がFlashの感覚にかなり近い。Cocoa touch特有のframeやboundsを気にしなくてよく、-[Sprite addChild:]などおなじみの操作も行える。
- メモリ管理をほとんど気にしなくてよい。cocos2dオブジェクトに関しては内部で適切に処理されるようになっている(ようである)。
- シーン遷移機能が標準装備されている。エフェクトの種類も豊富。
個人的にはFlash的に操作できるという点が一番の恩恵だと思っており、最近はXcodeのプロジェクト作成時にcocos2d用テンプレートしか使っていない。テンプレートを使うのであれば例えばCocos2d – 2D OpenGL for the iPhone made Easy. Part 1.に簡素なものがある。自分で作るのであれば【Xcode】設定しておくと便利なカスタマイズいろいろが参考になる。
[追記: 2009/09/02]
cocos2d for iPhone v0.8.1-betaよりプロジェクトテンプレートが用意されている。詳しくはrelease_notes:0_8_1_beta [cocos2d for iPhone]のTemplateの項参照。
[本題]
このCocos2d for iPhoneだが、ライブラリの状態で配布されていない。そのまま使用するのであれば大量のcocos2dソースファイルをプロジェクトに追加する形になる。これはコンパイルに結構時間がかかるし、ソースファイルに誤って書き込んでしまう可能性がある。そこでcocos2dを静的ライブラリ化することにする。以下で使用したのはcocos2d for iPhone v0.8。
[手順0. その前に]
実は、単純に静的ライブラリを作ってもiPhoneシミュレータとiPhone実機両方で使用することができない。というのはiPhoneシミュレータとiPhone実機のアーキテクチャ(ハードの構造)が異なるためである。したがって両者で動くようなライブラリを作るのには一工夫必要となる。
[手順1. iPhoneシミュレータ用静的ライブラリを作る]
- とりあえずiPhoneシミュレータ用を作る(iPhone用が先でもかまわない)。cocos2dプロジェクト内に新規Static Libraryを追加(ターゲット追加 > 新規ターゲット > Cocoa touch Static Library)、名前を”cocos2d-simulator”とする(名前はシミュレータ用であると自分でわかればなんでもよい)。
- 必要なファイル(cocos2dだけであればcocos2dフォルダ内のファイル一式)をターゲット配下に追加する。具体的には「ヘッダをコピー」にヘッダファイルを、「ソースをコンパイル」にソースファイルを、
「バイナリをライブラリにリンク」に使用フレームワーク(Foundation, UIKit, CoreGraphics, OpenGLES, QuartzCoreの5種類)を追加する。 - ターゲット情報のアーキテクチャの項目のベースSDKを”iPhone Simulator 3.0″にする(バージョンはお好みで)。
- ターゲットを”cocos2d-simulator”にしてビルドするとbuild配下に”libcocos2d-simulator.a”が出来ている(シミュレータ用静的ライブラリ完成)。ReleaseバージョンやDebugバージョンなどはお好みで。
(追記 2010/05/05: フレームワークの追加は不要。これらは必要に応じてプロジェクトに追加する方が適切だと思う)


[手順2. iPhoneOS用静的ライブラリを作る]
上記シミュレータ用ライブラリ作成手順とほぼ同じ。
- cocos2dプロジェクト内に新規Static Libraryを追加、名前を”cocos2d-iphoneos”とする(名前は適宜)。
- ターゲット配下に必要なファイルを追加する(内容は上記シミュレータ用のものと同様)。
- ターゲット情報のアーキテクチャの項目のベースSDKを”iPhone Device 3.0″にする(バージョンはお好みで)
- ターゲットをビルドして”libcocos2d-iphoneos.a”を作成(iPhone用静的ライブラリ完成)。
[手順3. 上記二種類の静的ライブラリをマージする]
“lipo”と呼ばれるコマンドラインツールがマージをしてくれる。詳細はiPhone OS用のほぼFrameworkの作り方やHow to (almost) create your own iPhone OS frameworkが詳しい。
- cocos2dプロジェクト内に新規Shell Script Targetを追加する(ターゲット追加 > 新規ターゲット > Other > Shell Script Target)、名前を”cocos2d-0.8″とする(名前は自分で識別できれば何でもかまわない)。
- ターゲット配下の”スクリプトを実行”に以下のコマンドを記述する。コマンドを注意深く見てもらえばわかると思うが、シミュレータ・iPhone用のライブラリ名は自分が作成したものに適宜変更する必要がある。マージ後のライブラリ名はお好みで。
- ターゲットを”cocos2d-0.8″に設定してビルドするとマージされた静的ライブラリが作成される。上記のコマンドの例だと例えば”build/Release-iphoneos/libcocos2d-iphoneos.a”と”build/Release-iphonesimulator/libcocos2d-simulator.a”のファイルをマージして”build/Release-iphoneos/cocos2d-0.8-Release.a”が作成されることになる。マージされたライブラリはシミュレータでもiPhone実機でも使うことができる。

rm -rf build/${BUILD_STYLE}-iphoneos/cocos2d-0.8-${BUILD_STYLE}.a
lipo -create "build/${BUILD_STYLE}-iphoneos/libcocos2d-iphoneos.a" \
"build/${BUILD_STYLE}-iphonesimulator/libcocos2d-simulator.a" \
-output "build/${BUILD_STYLE}-iphoneos/cocos2d-0.8-${BUILD_STYLE}.a"

[実際の使用方法]
上記手順でシミュレータでもiPhone実機でも使える静的ライブラリ”cocos2d-0.8-Release.a”ができているとする。このファイルだけでcocos2dが使えるのかと思いきや、ヘッダーファイルの実態への参照を用意しなくてはいけない。具体的には以下の二点の作業を行えばプロジェクト内でcocos2dが動作する。
- プロジェクトに”cocos2d-0.8-Release.a”を(例えばFrameworksの項目に)追加。
- プロジェクト情報のヘッダ検索パスにcocos2dプロジェクト全体を追加(ファインダーからドラッグでok)。ヘッダーファイルのあるcocos2dフォルダだけでもよい(要はどこにcocos2dに関係したヘッダーファイルがあるかXcodeに教える必要があるというだけである)。ヘッダ検索オプションの”再帰的”にチェックを忘れずに。

-[NSObject performSelector:]の返り値
Tags: Cocoa, iPhone, Objective-C
まとめ
- -[NSObject performSelector:]の返り値として不適切なものが存在する。
- それら不適切なものを扱う場合の対処法について。
イントロダクション
NSObjectのインスタンスメソッド
- (void)performSelector:(SEL) aSelector
はvoid型となっているが、「引数にセットされたメソッドの返り値」を自分の返り値に持つことができる。ただしその場合、-[NSObject performSelector:]の返り値はid型(オブジェクトポインタ)になるので適切にキャストしてやる必要がある。例えば、以下のようなNSStringを返すメソッドを持つTest1クラスを考える。
@implementation Test1 -(NSString *)stringValue{ return @"Hello"; } @end
このTest1クラスに-[NSObject performSelector:]を使って-[Test1 stringValue]を呼び、NSStringにキャストして返り値を受け取るとたしかに@”Hello”が返ってきていることがわかる。
Test1 *test1 = [[Test1 alloc] init]; NSString *stringValue = (NSString *)[test1 performSelector:@selector(stringValue)]; NSLog(@"%@", stringValue);// Hello
本題
-[NSObject performSelector:]の返り値はid型であり、id型はオブジェクトへのポインタであると説明した。つまり、-[NSObject performSelector:]は「オブジェクトではないものを返り値とするメソッド」の返り値を受け取ることができない可能性があることがわかる。例を見てみよう。Test2クラスに以下のようなメソッドがあるとする。どのメソッドの返り値もオブジェクトではない。
@implementation Test2 -(BOOL)boolValue{ return YES; } -(int)intValue{ return 10; } -(float)floatValue{ return 2.0; } -(CGPoint)point{ return CGPointMake(100.0, 100.0); } @end
先ほどと同様にこれらのメソッドを-[NSObject performSelector:]を使って呼び出し、返り値を確認すると以下のような結果になった。
Test2 *test2 = [[Test2 alloc] init]; //warning: cast from pointer to integer of different size BOOL boolValue = (BOOL)[test2 performSelector:@selector(boolValue)]; NSLog(@"%i", boolValue);// 1 int intValue = (int)[test2 performSelector:@selector(intValue)]; NSLog(@"%i", intValue);// 10 //error: pointer value used where a floating point value was expected float floatValue = (float)[test2 performSelector:@selector(floatValue)]; NSLog(@"%f", floatValue); //error: conversion to non-scalar type requested CGPoint point = (CGPoint)[test2 performSelector:@selector(point)]; NSLog(@"%@", NSStringFromCGPoint(point));
結果をまとめると以下のようになる。
- 返り値BOOLについては警告が出たが返り値の受け取りができた。
- 返り値intについては警告は出ず返り値の受け取りができた。
- 返り値floatについてはコンパイルエラーが生じ実行できなかった。
- 返り値CGPointについてはコンパイルエラーが生じ実行できなかった。
対処法
上記のテスト結果から、-[NSObject performSelector:]から「オブジェクトではないものを返り値とするメソッド」の返り値を受け取るためには何かしらの工夫をしなければならないことがわかった(int型・BOOL型などの例外もある)。以下に二種類の対処法を挙げておく。
- -[NSObject performSelector:]で呼び出すメソッドの返り値をオブジェクトにしておく。
- -[NSObject performSelector:]の代わりに[NSInvocation invoke]を用いる。
順に見てみよう。
対処法1
二つ目の対処法より簡単である。返り値int, BOOL, floatについてはNSNumberオブジェクトによるラッピング、返り値CGPoint(CGRect, CGSizeなども含む)についてはNSStringオブジェクトに変換することで簡単に対処できる。以下に、Test2クラスの4メソッドの返り値を全てオブジェクトに変更したTest3クラスを挙げる。
@implementation Test3 -(NSNumber *)boolValue{ return [NSNumber numberWithBool:YES]; } -(NSNumber *)intValue{ return [NSNumber numberWithInt:10]; } -(NSNumber *)floatValue{ return [NSNumber numberWithFloat:2.0]; } -(NSString *)point{ return NSStringFromCGPoint(CGPointMake(100.0, 100.0)); } @end
この場合、各メソッドの返り値はオブジェクトであるため、それらオブジェクトから必要な値を復元してやればよい。
Test3 *test3 = [[Test3 alloc] init]; BOOL boolValue = [[test3 performSelector:@selector(boolValue)] boolValue]; NSLog(@"%i", boolValue2);// 1 int intValue = [[test3 performSelector:@selector(intValue)] intValue]; NSLog(@"%i", intValue2);// 10 float floatValue = [[test3 performSelector:@selector(floatValue)] floatValue]; NSLog(@"%f", floatValue2);// 2.000000 CGPoint point = CGPointFromString([test3 performSelector:@selector(point)]); NSLog(@"%@", NSStringFromCGPoint(point2));// {100, 100}
対処法2
-[NSObject performSelector:]の代わりにNSInvocationを用いると、返り値がオブジェクトであろうとなかろうと受け取ることが可能になる。例えば、-[Test2 floatValue]の返り値を受け取る場合には、以下のような手続きをとる。
Test2 *test2 = [[Test2 alloc] init]; float floatValue; NSMethodSignature *aSigniture = [[test2 class] instanceMethodSignatureForSelector:@selector(floatValue)]; NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSigniture]; [anInvocation setSelector:@selector(floatValue)]; [anInvocation setTarget:test2]; [anInvocation invoke]; [anInvocation getReturnValue:&floatValue]; NSLog(@"%f", floatValue);// 2.000000
NSInvocationを用いると汎用性は高くなるが、一般的にソースが煩雑になる。
参考
[ActionScript] モンハンでAbstract Factory
Tags: ActionScript, DesignPatterns
剣士装備・ガンナー装備
モンスターハンターの武器は大きくわけて二種類に分類できる。それは大剣やハンマーといった近接武器(剣士専用)と弓やボウガンなどの遠距離武器(ガンナー専用)である。面白いのは防具にも剣士専用のものとガンナー専用のものがあるところで、武器と防具の関係に以下のような制約が付けられているのが他のRPG(例えばドラクエ)と大きく異なる点であろう。
- 剣士専用武器を装備している時は剣士専用防具しか装備できない。
- ガンナー専用武器を装備している時はガンナー専用防具しか装備できない。
剣士・ガンナー武器の違いは予想できるとして、剣士・ガンナー防具の違いは何だろう。答えは防御力だ。ガンナー専用防具の防御力は剣士専用防具のそれに比べて著しく低い。これは妥当な設定だ。崖の上でスコープを構えて銃撃するハンターよりも、モンスターの足下すれすれで剣を振り回しているハンターの方がモンスターからダメージを受ける可能性が高いからであろう。ダメージを受ける頻度が異なる者同士をより平等に扱うためには防御力による差別化をはかったという風に考えれば、上記のような剣士・ガンナー防具の制約はとてもリーズナブルだ。
裸のハンター
さて剣士・ガンナー防具の制約、面白い仕組みなのだが、少しだけストレスになることがある。着替えをする時だ。モンハンをやったことがある人なら経験があると思うが、今例えば剣士専用装備(近接武器+近接武器用防具)をしているとしよう。この状態で武器をガンナー専用であるボウガンに替えたとするとどうなるか。答えは防具が全部外れて裸になる、である。なぜならボウガンを装備した時点でガンナー武器を装備していることになり、上記の剣士・ガンナー防具の制約から剣士防具が装備できなくなるからである。ボウガンだけを装備した裸のハンターはガンナー専用防具をイチから装備し直さないといけないのだ。そしてこの問題は剣士・ガンナー間の装備変更にはいつもつきまとうことになる。
便利な機能
そこでゲーム内に用意されているのが着替えを一括で行うマクロ機能だ。自分のお気に入りの武器と防具の組み合わせをあらかじめ登録しておくことで、次回からボタン一つで全身の装備コーディネートができてしまう。もちろん登録時の装備は自分が今現在装備しているものから選ばれているため、剣士・ガンナー防具の制約も必ず満たすことになる。登録スロットも20もあり(MHP2Gの場合)、好きなときに好きなスロットの装備にボタン一つで変身できるようになる。
本題
前置きが長くなったが、このモンハンの装備一括変更のマクロ機能、デザインパターンで言うところの何に相当するのか、ということで少し考えた。いくつか候補があったものの結局これに落ち着いた。Abstract Factoryパターンである。全く同じ操作でユーザーを煩わせることなく機能する、というところに重点を置いた結果である。Abstract Factoryパターンの解説は以下リンク先へどうぞ。簡単なActionScriptコード付き。
[iPhone][App] KanjiName - Your Japanese Name

完全外国人向けアプリケーションをリリースしました。ユーザーの漢字名が載った和風な名札を作成、フォトライブラリの写真上に載せてオリジナルフォトを作成します。漢字には青柳衡山フォント2を使用、イラストレーションは毒乙女に作成いただきました。両氏に感謝。
アプリケーション詳細はこちら。
» KanjiName - Your Japanese Name
