でらうま倶楽部

バカってゆうか、ゲームを作る事しか能の無いプログラマの、面白おかしな日々を綴ってみる実験。

2010年07月

Objective-C プロトコルを最短で理解するプログラム例

imagesおひるごはん買いに行くのにも命がけです。

暑すぎ!!

そんな暑い中、今回はObjective-Cのプロトコルについてー。まだうろ覚えなんで、指摘とか頂けると嬉しい限りです。

これとカテゴリを覚えとくとサブクラス地獄から解放されるかもしれないので覚えておいて損は無いと思います。

今制作中のアプリはテキスト処理メイン。んで、アドベンチャーゲームとかでよくある、以下のような演出も要求されていました。
  1. テキスト表示中に背景画像を表示したり動かしたり
  2. テキスト表示中に効果音を鳴らしたり
  3. 選択肢を表示して選ばせたり
んで、これをいい感じに「実装をクラス別にしよう」と考えたのが事の発端。文字クラスとか背景クラスとか効果音クラスとか選択肢入力クラスとか。

この時、ルートクラスを何にします?NSObject?UIView?う~ん。なんかケースバイケースで微妙。今後追加される機能も考えるとうかつに決められないし。でもObjective-Cのバヤイ、こんな感じなクラス定義が出来ないんだよね。
@interface Hoge : UIView, NSArray {}
@end

多重継承っていうのかな?でもここで、デリゲートは幾つも採用出来る事に気付いた。
@interface Hoge : NSObject <UITextViewDelegate, UIScrollViewDelegate> {}
@end

みたいな。これがプロトコルと呼ばれとる機能な訳だ。

これなら、全クラスの共通のインターフェイスをプロトコルで決めてやれば、あとは機能に応じてUIViewだったりUIImageViewだったりNSObjectだったりすればよい訳です。たとえばこんな感じ。
@protocol HogeProtocol

-(BOOL)isCharactor;
-(BOOL)isDisplay;
-(void)start;
-(void)abort;
-(BOOL)step;

@end


@interface Hoge : UIView<HogeProtocol> {}
@end

@interface Fuga : NSObject<HogeProtocol> {}
@end

あ~なんかルートクラスを決めなくていいので、一気に楽になった。これで適材適所なクラスが選べます。効果音を扱うクラスがUIViewである必要はないかんね。

プロトコルは「これだけのメソッドを責任持って実装しといてね」という宣言です。で、それを採用したクラスでは責任持ってメソッドを実装するのです。上の HogeProtocol だと5メソッド。これを利用した結果、こんなコードで表示処理を書く事ができました。
NSMutableArray *display;       // 処理されるオブジェクトが格納されている

for(id<HogeProtocol> obj in display)
{
    BOOL res = [obj step];      // 1回のループで実行される処理
    if([obj isCharactor])
    {
        // 一文字表示
    }
    if(!res) [obj abort];      // 終了処理
}

おー!

けっこうスッキリ書けたよね。自作クラスは全て、HogeProtocol でメソッドの存在を保証してるので、実際にどのクラスかを知る必要なく、id 型のオブジェクトに対してメソッドを実行できる訳なんどす。コンパイラでもワーニングは出ません。素敵!


いまさらObjective-Cとかいう意見もまぁありますが…iPhoneアプリ開発だとやっぱしObjective-Cで書いた方が何かと都合がよいと思うのです。色々な機能がObjective-Cで用意されとるしね。

プログラミング言語の優劣を比べるのはとてもナンセンスで、その言語が持つ独自の考え方や手続きを柔軟に取り入れて、質のよいコードを書けるかどーかが部長的には重要だと思うのです。それが出来とる人ほど、いろんな場で活躍しとる気がする。

Objective-Cが嫌なら全部、C++でiPhoneアプリ書いたらいいし。

さらに、往々にして開発言語が選べない環境もあるという事も、頭の片隅にとどめておいてほしい。仕事でプログラムを書くって事は、そーいう時でも100%能力を発揮して取り組むって事なんだ。あとGCC禁止な環境とか(笑)いやほんとにあるんだって。

週末はライブ三本立て!まさにライブ三昧。

暑いのとか暑いのとか暑いので、昼間まったく作業に身が入りませぬ。

こうなったら戦略的夜型だ!

とゆーわけで昼夜逆転型の生活です。ブログの更新もこんな時間だしツイッターでも訳判らん時間につぶやいてます。でもほんとはだらしないだけです。

photo01んで、誰もいない事務所の机の上に見慣れんDVDが。

ああ!5末のパラレルスリップのDVDね!当日受付だったから、ほとんど観られてないんだよね。でもこれでようやく観られる!嬉しいがね。

その前の日は同じ場所で楠田FCイベントだったなー。そういや、さかもとえいぞうさんがゲストに来てくれたよなー。

59527b51-sそう!実は土曜日。そのさかもとさんがボーカルを務めるアンセム25周年ライブ@川崎クラブチッタ。そのライブを高濱社長のご厚意に甘えて観てきた訳ですハイ。

アンセムはやっぱ凄かったです。演奏技術の高さも素晴らしいし、その演奏を知り尽くした照明&ステージ演出も素晴らしい。んで、そんなド迫力のステージもさることながら、なんと歴代のメンバーもゲストで参加する超豪華内容!このバンドをずっと追っかけてるファンにはたまらん瞬間だったんだろうなーとおもいながら観てました。部長はアンセムを知ってまだ日が浅いのですが、でも知ってる曲は盛り上がるよねー!あとライブ観た後、ギターの練習にも力が入ります(笑)

photo02でー!

日曜日。これまたパラレルスリップやミステリーガールズのイベントに参加してくださった、山田茉莉さん、田中まやさん、木ノ下ひよ子さん、桜咲ちよさんがライブに出演されとる、という事でちょいとイベントを観てきましたー。山田さん、わざわざお招き頂き恐縮です。

Dream Catch Plemium vol.11@新宿RUIDOK4

年頃のムスメさんらがワイワイきゃっきゃと踊ったり歌ったりというのはたのしいですね。おっさん発言。

山田さん、田中さん、木ノ下さんの参加する*pinksugar*は今回初ライブだったらしいのですが、これかなかなか。堂々としたもんだ。でも肝心な告知で噛んだよね?気のせいじゃないよね?まぁそれは置いといて…今後の活躍が楽しみでござる。楽しみにしとりまーす。

photo03んで、その夜は、タカさーん!

ブリザードの元ギタリストでもあり、今や売れっ子音楽ライターなタカさん率いるkuliealorのライブを観てきたっす。@下北沢CaveBe。

タカさんカッコいい。今夜もいっぱい写真を撮りました(写真係なのだ)

どんなジャンル?とかってのを上手く説明できないのだが、部長的に超好きな音楽。メンバー三人の優しい人柄がよっく出とる、そんなロックなのです。

はっ!はやくCDをゲットせにゃ。


さいきん煮詰まってたかんねー。ホント良い気分転換になりました。お招き頂いた皆さんにはホント感謝です!いろいろ頑張るゼ。

iPhone 少しだけメモリの負担を減らせるかもしれないOpenALのAPI

imagesiPhoneに特化したサウンドライブラリ!

…というわけではないけど、ゲームみたく処理速度重視なアプリでもわりと…いやけっこう?導入されとるOpenAL。WindowsでもMacでも動くコードが書けるし、APIが単純だからゲーム機に移植しとるデベロッパもいるのかしらん?みたいな。

んで、一個だけ気になってたのが、このAPI。

alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq);

みんなも必ず使うであろうこのAPI。読み込んだ波形データをサウンドバッファとして登録する関数です。でもこれ一つだけ弱点があって、新しくメモリを確保してそこにデータをコピーする関数だという事。つまりこの後

free(data);

しなきゃならない。つまり、最大風速でバッファサイズの2倍分メモリが消費されるのです。

波形データを非圧縮状態でメモリに置いとかにゃならないOpenALでこれはしんどい。ますますリッチなアプリが増えてく中で、iOS4でマルチタスクが導入され、使えるメモリ量が意外と少ない状況がありえるし、古いiPod touch や iPhone3Gあたりもちょっとつらいよね。メモリはなんとか余ってそうなのに、初期化の途中でアプリが落ちる、的な。

んで、これを解決する策をようやく見つけました。

typedef ALvoid AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);
static alBufferDataStaticProcPtr alBufferDataStaticProc;

まず上のコードをコードに追加。

alBufferDataStaticProc = (alBufferDataStaticProcPtr)alcGetProcAddress(nil, (const ALCchar *)"alBufferDataStatic");

続いて、上のコードをOpenAL初期化の最後に追加。alcMakeContextCurrent()した後とか。

alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);

で、最後。alBufferData()の代わりに上の関数を使う。こんだけ。新しくメモリを確保しないので、free(data)する必要はなし。イェス!

よーするに、OpenGLみたくOpenALの拡張APIを取ってくる訳ですな。んでそれが使えますよ、と。でもまだこの作戦を実行したアプリを申請しとらんのでだれか人柱になってください。

今んとこiOS4.0.1実機でもきちんと動作しとります。

Xcode SCMを使ってプロジェクトの初期段階からマスターまでの状態を保存する。

photoようやく作業に目処がついたー。

ので、ちょい久々にブログを書くよ!

今回は、XcodeのSCMの使い方について書きます。
SCMはSource Code Managementの略でいいのかな?

photo01事の発端は、DropOutのコード管理で使っていた『スナップショット』機能がイマイチ日本語に対応しとらん!から。こんな感じで、(差分を検出中…)となったまま、差分が検出されません。

なんでイマイチなのかというと、正しく動作する時もあるから…ホント頼りないなぁ、スナップショットさん。

この現象、こないだバージョンアップしたXcode3.2.3でも変わらず。たまらずAppleにバグレポを提出しましたが、部長のつたない英語力では今世紀中に問題が解決されるかも怪しいので、とりあえず運用でカバーする事にしてみます。


XcodeのSCMを使うには、まずリポジトリを用意する必要があります。部長はとりあえずローカルマシン上に用意。XcodeはCVS、Subversion、Perforceの3つのバージョン管理システムに対応しとりますが、今回はSubversionを選択。コンソール上で作業しました。

>cd ~
>mkdir _SubversionReopsitory
>svnadmin create _SubversionReopsitory

はい、オッケィ!

続いて、Xcode上で作業。

photo02「SCM->SCMリポジトリ構成」で設定画面を出して、左下の「+」を押す。
んで、左のように入力。

photo03続いて表示される設定画面、「スキーム」と「パス」を入力して「適用」を押す。下の方で緑色のランプが灯って「認証されました」と表示されればオッケィ!

そういや、よくアカウント名を隠しとる人がおるけど、それってやっぱ必要なのかしらん…(汗)



ほい、これでXcodeのSCM機能が使えるようになりました。ではさっそく使ってみましょう。おっとその前に。追加するXcodeプロジェクトの中にあるbuildフォルダとか、余計なファイルを削除しておいてください。

photo04で、「SCM->リポジトリ」で設定画面を開き、「読み込む」ボタンを押して、プロジェクトのあるフォルダを指定。こんな感じでプロジェクトをリポジトリに追加します。
(さすがに進行中のプロジェクトは隠した)

これでDropOutはSCMで管理されるようになった!

でもまだ作業は不十分で、リポジトリからプロジェクトを取り出さないといけません。これをチェックアウトといいます。でもこの時、いきなり元と同じ位置にチェックアウトすると色々面倒な事になりかねないので、元のプロジェクトを別の場所に移動してください。部長の場合、一旦デスクトップに移動しました。

んで、「SCM->リポジトリ」の設定画面内、「チェックアウト」ボタンを押して、元の位置にチェックアウト。無事チェックアウトが完了すると、プロジェクトを開くかどうか聞いてきます。最後の仕上げをしますのでここは開きます。

photo05最後に「SCM->このプロジェクトのSCMを構成...」で設定画面を開き、右上の「ルートとSCMを構成...」ボタンを押す。そしてリストから作ったリポジトリ構成を選んで…これでようやく任務完了!おつかれさまでした。


んで、何か手を加えて、それをリポジトリに保存したい時は「SCM->プロジェクト全体をコミット」とか、ファイル右クリックから「コミット」とかします。
逆に、間違った編集を元に戻したい時は「SCM->プロジェクト全体をアップデート」とかファイル右クリックから「アップデート」とかします。本来アップデート機能は、多人数でプロジェクトを触ってる時に他の人の変更分を取り込む機能だけど、一人で開発しとる時はこんな感じよね。

photo06色々ファイルを触ってて、いまの変更状況を知りたくなったら「SCM->プロジェクト全体を更新」して「SCM->SCMの結果」。リストに変更されたファイルとか追加されたファイルの一覧が表示されるんで、選んで「右クリック->比較」とすれば、変更箇所がいい感じに表示されてしあわせになれます。こっちは日本語大丈夫っぽいんだよなー!

ソースコード編集は別PC(WindowsXP)のEmacsからという特殊な環境ですが、いまんとここんな感じで使えてるXcodeのSCM。とりあえずソースの手を入れた箇所がちゃんと判るようになったのが収穫。まだまだ便利な機能がありそうですがそれはまた追って。

今回、こちらのページに大変お世話になりました。多謝。

iPhone 彼はいい奴です!Objective-Cのアクセサメソッドとちゃんと向き合ってみた記録。

sdkこのブログを読んでくださってるみなさん。
けっこう会社や事務所とかからアクセスしとる?

まぁそれは置いといて。置いとくんかい!

今回はコードでよく見かけるアレ、

CGRect frame = view.frame;
CGPoint center = view.center;

みたいに書けるヤツ。Objective-Cのアクセサメソッドについてー。あとちょっと長文だから今集中力が足りてない人は注意。

これ、ソースに書かれとる@propertyとか@synthesizeとかがなんか呪文っぽいし、めんどくさそうだし、

CGRect frame = [view frame];

って書けるからいーじゃん。って敬遠しとったんだけど、Objective-Cにおいてはむしろ積極的に使った方が何かと得!てか使わないとハマる…かも。という事がだんだん見えてきました。つまり宣言のめんどくささより使う事で得るメリットが部長的に上回った訳です。

宣言のし方はだいたいこんな感じ。
@interface Hoge : NSObject
{
    int hoge;
    UIView *fuga;
    NSString *fugafuga;   
}
@property (nonatomic) int hoge;
@property (nonatomic, retain) UIView *fuga;
@property (nonatomic, copy) NSString *fugafuga;

@end


@implementation Hoge

@synthesize hoge;
@synthesize fuga;

-(void)dealloc
{
    [fuga release];
    [fugafuga release];
    [super dealloc];
}

@end

はい、終了~!これで、
UIView *view;    // 取得済み
NSString *str = @"hogehoge\n";
Hoge *hogehoge = [[Hoge alloc] init];

hogehoge.hoge = 0;
hogehoge.fuga = view;
hogehoge.fugafuga = str;

と、アクセサメソッドを使って変数のやり取りが出来るようになる。これ、コンパイラ側で
-(int)hoge
-(void)setHoge:(int)aValue;

などのメソッドを自動的に生成してくれとる訳。なので、やっぱり
[hogehoge setHoge:0];

とも書ける。そんな大した事じゃないじゃん!と思うかもしれんけど、自動的に生成されるメソッドの内部処理が非常に重要。

引数がUIViewやNSStringなどのオブジェクト型の場合
呼び出し側でオブジェクトを削除されても整合性が取れるようにする為、@property宣言に retainを追加する。で、その場合はこんな感じのコードが追加されてる。あーなるほどね!みたいな。
-(void)setFuga:(UIView *)aObj
{
    if(fuga != aObj)
    {
        [fuga release];
        fuga = [aObj retain];
    }
}

マルチスレッドで扱われるクラスの場合
@property宣言のnonatomicを外す。すると、いろいろめんどくさい手続きをすべて自動で行ってくれる。

主に文字列の場合の利点
オブジェクトをretainしてると、そのオブジェクトを共用してるって事なんで、よそで書き替えられた内容が反映されます。それじゃ都合が悪いよって時は、@property宣言でretainの代わりにcopyを指定する。すると、いい感じにcopyしてretainしてくれます。

で、忘れがちなのが、@property宣言にretainやcopyがついとるオブジェクトはdeallocでちゃんとreleaseするって事!これ、上の例のようにいきなしreleaseしてしまって大丈夫。Objective-Cはnilポインタに対してメソッドを実行してもエラーで止まる事は無いからね。

こんな感じでホイホイアクセサメソッドを使っとりますが…こんな感じの記述は怒られるんで注意。
UIView *view;   // 初期化済み
view.center.x += 1.0;

あと、見た目メソッド呼び出しっぽくないんで、ついついこんな感じで書くと後でえらい事になるんでコレも注意。
view.center.x = view.center.x + view.center.y * 0.5 + view.freme.size.width / 2.0;

この一行で4回メソッドが実行されとります。

CGPoint center = view.center;
view.center.x = center.x + center.y * 0.5 + view.freme.size.width / 2.0;

予め変数に格納してから計算するとかして、メソッド呼び出しを減らすよう心掛けるのが吉かと。

オマケ
Objective-Cのメソッド呼び出しに対するコストを気にしとる人。こうするとCの構造体と同じ感覚で変数にアクセスできる。恐るべしObjective-C。そのぶん自分で管理する事は増えますが、まあアリでしょう。
@interface Hoge : NSObject
{
@public
    int hoge;
    UIView *fuga;
    NSString *fugafuga;   
}
 
@end

hoge->hoge = 1;
hoge->fuga = view;
hoge->fugafuga = str;

なお、今回の件。こちらの記事がたいへん優秀です。てかこれを読んだら部長の解説は要らない(笑)

あとAppleのドキュメントも。ただ部長的には若干訳がわかりにくい。

そして、こちらこちらの記事にもたいへん助けられました。さりげなくいつもお世話になっとります。多謝。

iPhone たった数行で「どこでもセーブ」を実現する魔法の呪文(おおげさ)

夜明けとともに体内時計をリセット!

って世の中的に言われとるようですが、部長ももちろんろリセット!そしてオヤスミナサイ……って逆だがね!いやなんか、太陽の光を浴びると眠くなるのよねぇ。それもわりと昔から。

異常?

sdkまぁそれは置いといて。今開発中のアプリ。割と複雑なデータ(自作クラス)の保存/復帰処理が必要だったので、自作クラスを NSKeyedArchiver/NSKeyedUnarchiver というクラスを使ってシリアライズするようにしてみました。こないだの記事も絡んでくる内容ですが…さて、この方法でイケてるかどーか、皆さんの意見もお伺いしたい所でありんす。


さて。NSKeyedArchiver/NSKeyedUnarchiver の使い方は至って簡単。

「アプリ専用のDocumentsフォルダのパス+ファイル名」を取得する
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"hoge.dat"];

ファイルへ保存
id obj;     // 初期化済み
[NSKeyedArchiver archiveRootObject:obj toFile:path];

ファイルから復帰
id obj = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

はい!こんだけ。たったこれだけのコードで、アプリのDocumentsフォルダの中のhoge.datを介してデータの保存/復元が実現できます。

しかも対象となるオブジェクトがNSCodingプロトコルをサポートしてれば、どんなクラスだって保存/復元できちゃうという超便利さ!NSArrayだろうがNSDictionaryだろうがNSStringだろうがUIViewだろうがドンと来い!複雑な親子関係になっててもお構いなし!って感じで、あーなんか幸せ。って思う瞬間。それは部長だけか。でもエレガントな仕様って心地いいと感じる感性は大事よ。

で、これが自作クラスであってもオッケィ!っていうのが今回の話。

要はNSCodingプロトコルをサポートすればよいだけで、それはこんな感じで実装します。
@interface Hoge : NSObject <NSCoding>
{
    int intValue;
    float floatValue;
    id object;
    CGPoint point;
    CGRect rect;
}
@end


@implementation Hoge

- (void) encodeWithCoder:(NSCoder *)coder
{
    [coder encodeInt:intValue forKey:@"intValue"];
    [coder encodeInt:floatValue forKey:@"floatValue"];
    [coder encodeObject:object forKey:@"object"];
    [coder encodeCGPoint:point forKey:@"point"];
    [coder encodeCGRect:rect forKey:@"rect"];
}

- (id) initWithCoder:(NSCoder *)coder
{
    intValue = [coder decodeIntForKey:@"intValue"];
    floatValue = [coder decodeFloatForKey:@"floatValue"];
    object = [[coder decodeObjectForKey:@"object"] retain];
    point = [coder decodeCGPointForKey:@"point"];
    rect = [coder decodeCGRectForKey:@"rect"];
    // あと、何か初期化処理が必要ならここに書くもよし。
}

@end

Objective-Cに不慣れな人にはなんのこっちゃコードですが…自作クラスのインスタンス変数を仕様に沿って保存/復帰するようにしたげればよいだけー。そうそう、こないだ説明した、CoreGraphics由来の構造体もなにげに拡張APIで対応されとるんで、よっくヘルプを読んでみるのが吉かと。


はい!これを駆使すればアドベンチャーゲームからアクションゲームまで、頑張り次第で「どこでもセーブ機能」が実現しますので、みんなも機会があれば挑戦してみてちょ。

「RockinRoll」というアプリがもしかして、この機能を使ってる…かも?

iPhone OpenAL上でのループ再生ノイズに苦しめられた

お久しぶりです。

いやいやいや…

iPhoneアプリの完成が近いとブログもツイッターも途絶えがちだがね。でもこうしてネタを提供→生存確認。みたいな。ま、稚拙な文章とは思いますが、最後まで付き合って頂けたら何よりです。

部長はアプリのサウンド再生にはOpenALを使っとりますが、今回は「まさかのループでプチノイズ現象」でハマった顛末を報告します。結論から言うと、対症療法で切り抜けたんですが…是非皆さんの意見も聞きたい結果でもあります。

OpenALでのサウンド再生は
  1. サウンドデータをwav形式で用意
  2. afconvert -f caff -d ima4 hoge.wav としてcaf形式(IMA4圧縮)に変換
  3. Appleのサンプル「oalTouch」を元にした読み込みプログラムで読み込み→再生
みたいな感じでやってます。

OpenALを使ってる都合上、読み込んだサウンドファイルを一旦メインメモリに展開して使います。まぁ、IMA4圧縮されたcafファイルを直接再生できないのが唯一歯がゆい感じですな(メインメモリをそれだけ消費する)。

幾つもアプリをリリースしてきた経験上、今回もこれですんなりいくハズだったんですが…今回とある効果音をループで再生すると「プチッ」「プチッ」とノイズが入る問題が発覚しました。

問題となったサウンドファイルはWindowsやOSXでループ再生する分にはまったく問題がありません。でもなぜかシミュレーターやiPhone実機でループ再生すると、ループした瞬間に「プチッ」とノイズが入ります。なんでやねん!

photo01まず最初にコードを疑ったんですが…アレコレ試してみるも進展なし。そこで問題となるサウンドファイルをAppleのサンプル「oalTouch」でループ再生させてみたら…これもノイズが入るんだよね。マジッすか…。

続いてサイン波の波形を作ってテスト。これもダメ。ループ前後の音量が0に近いとノイズは無くなりますよ、という助言も頂きましたが、そんな音源ばかりじゃないのよね…。この画像のような、ちょっと意地悪なループデータでもちゃんと綺麗にループ再生できないとアカンのですわ。

と、ここでまず1つ気付いたのが、caf形式(IMA4圧縮)のサウンドファイルが非可逆性だという事。これは画像に例えるとjpeg形式と同じで、復元した時に完全には元通りにならないという事です。つまりwav形式からcaf形式時に圧縮した結果、ループ時に波形が綺麗に繋がらなくなってて、それがノイズに繋がっているんじゃないか?という理屈です。

なので、オリジナルのサウンドファイルなら文句ないやろ!と、caf形式ではなく、wav形式のファイルを読み込んでテスト。本来ならここで問題が解決するハズだったのですが…それでも相変わらず発生するプチノイズ。サイン波もダメ。な、なんですとー!

この時点で部長かなりテンション下がってます。

ここで2つめ気付いたのが、QuickTimePlayerでは、読み込んだサウンドをwav形式にエクスポートする時に若干サンプル数が変わるという事。wav形式の場合のみ、内部的に都合のよいサイズに切り捨ててるっぽい…??前からそうだったっけ?みたいな。

という訳で、ダメもとでファイル形式をaiffに変更してみた。今度は大成功!あれほどノイズに苦しめられたのが嘘のように、綺麗にループ再生されたよ。いやいやいや~。wav形式だとダメでaiff形式だと問題ないなんて…なんかキツネにつままれたような結果。

ループ再生されるサウンドファイルは非圧縮でリソースに含める事になるので、AppStoreでの配布ファイルサイズが若干大きくなりますが…まあ結果オーライ!

同じ問題で困ってる人、試してみる価値はあると思います。

諦めないでよかった。

「米国サイバー軍の紋章」に隠された暗号

「米国サイバー軍の紋章」に隠された暗号

photo
アメリカでいよいよ本格稼働されるサイバー司令部。紋章カッコいい!軍のコンピューターをハッカーたちから守るのが目的なんだそうな。
で、目を惹くのが、その紋章内側にあるこのコード。

「9ec4c12949a4f31474f299058ce2b22a」

どっかで見た事ある数字の羅列…!

これ、MD5 のハッシュ値っすね。

という訳で、手持ちのツール&グーグルで解読してみた。


9ec4c12949a4f31474f299058ce2b22a = USCYBERCOM plans, coordinates, integrates, synchronizes and conducts activities to: direct the operations and defense of specified Department of Defense information networks and; prepare to, and when directed, conduct full spectrum military cyberspace operations in order to enable actions in all domains, ensure US/Allied freedom of action in cyberspace and deny the same to our adversaries.


グーグル先生最高!つーわけで解読完了…なのかな?これでTシャツゲット!な訳ないか(笑)

iPhone NSValueのもう1つの…いや5つくらいの使い道。CGSizeやCGRectをラップする。

毎週月・火に非常勤講師で通っとる学校に傘を忘れた!

10013101ビミョーな距離感(2駅)なんで、来週回収したらいいか…と思ったのが間違いでした。その日の夜から雨です。めちゃめちゃ梅雨です。

メガネっ子は雨に濡れるの苦手なんですよ。前が見えへんくなるから!だからずっと折りたたみ傘を持ち歩いとった生活から一転、これは不便。据え置き型の傘に不慣れなのもあるんだけどさ…

探すと見つからないコンビニ。追うと実らない恋。まあそんなもんです。

それは置いといて。

配列NSArrayにはNSStringとかNSNumberとかUIViewとか種類を気にせずいろいろ突っ込めて便利なんだけど、はて、CGRectとかCGSizeとかの…CoreGraphics由来の構造体は扱えんの?と思ったのが事の発端。

ちょっと前の記事でNSDataでラップする方法を書いたけど、もっと手軽で簡潔な手法をみつけたのでご紹介。

それは『NSValue』を使います。

「ヘルプ見たけど、そんな感じの事書いてなくね?」と思った人。それ正解。

でも、もすこしヘルプを探すと「NSValue UIKit Additions Reference」ってのが見つかるんです。フツーは気づかないよね!なんとそこに、CoreGraphics由来の構造体を扱う拡張が書かれとるという。

使い方はこんな感じ。
CGRect rect;
NSValue *value = [NSValue valueWithCGRect:rect];

rect = [value CGRectValue];

超楽!CGPoint、CGRect、CGSize、CGAffineTransFormあたりが使えナス。これでいちいちNSDataでラップする事無く、CGRectやCGSizeをNSArrayに突っ込む事ができるよーになりました。


こちらこちら(英文)でさらに詳しい説明があるんで、NSDataでの方法にうんざりしとったひとは読んでみてちょ。


んで、さいきん作ったiPhoneアプリはこんな感じです。

iOS4 ムービー再生ができないよ!という時に慌てない為の対策。

potoiOS4の仕様変更にさっそくやられました。

iOS4だと MPMoviePlayerController を使ったムービーが再生されんがね!

音は出るんだけど画面は闇夜のカラス。まじっすか。

ウェブを調べると、MPMoviePlayerViewController を使ったらいいよ。とか、OS3.2以降で追加されたプロパティを設定してみたら?とか NSNotificationCenter の設定を変えるんだよ!とかけっこう解決策がバラバラ。

んー。

アプリ起動時のスプラッシュムービーの処理なんで、あんましリソースを追加したくないっちゅーか、処理の流れを変えたくないんだけど、どーしよっか。

という訳で、極力手を入れず、なおかつ互換性を最大限に保った方法を部長なりに編み出してみたよ。

同じ現象で困っとる人、参考にしてみてちょ。
- (void)myMoviePreloadDidFinish:(NSNotification *)_notification
{
    MPMoviePlayerController *player = [_notification object];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerContentPreloadDidFinishNotification object:nil];
    [player play];
}

- (void)myMovieFinishedCallback:(NSNotification *)_notification
{
    MPMoviePlayerController *player = [_notification object];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
    if([player respondsToSelector:@selector(view)])
    {
        [player.view removeFromSuperview];
    }
    [player release];

    //
    // ここで正規の開始処理を行う
    //
    [window addSubview:viewController.view];
}

-(void)startSplashMovie
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"hogehoge" ofType:@"mov"];
    MPMoviePlayerController *player;
    player = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL fileURLWithPath:path]];
    player.scalingMode = MPMovieScalingModeNone;
    if([player respondsToSelector:@selector(view)])
    {
        player.controlStyle = MPMovieControlStyleNone;
        [player.view setFrame:CGRectMake((320.0 - 480) / 2.0, (480.0 - 320.0) / 2.0, 480.0, 320.0)];
        CGAffineTransform transform = CGAffineTransformMakeRotation(DegToRad(90.0));
        [player.view setTransform:transform];
        player.view.backgroundColor = [UIColor clearColor];
        [window addSubview:player.view];
        [player prepareToPlay];
        [player play];
    }
    else
    {
        player.movieControlMode = MPMovieControlModeHidden;
        player.backgroundColor = [UIColor clearColor];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myMoviePreloadDidFinish:) name:MPMoviePlayerContentPreloadDidFinishNotification object:player];
    }
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myMovieFinishedCallback:) name:MPMoviePlayerPlaybackDidFinishNotification object:player];
}


- (void)applicationDidFinishLaunching:(UIApplication *)application
{   
    [self startSplashMovie];
    [window makeKeyAndVisible];
}

iPhoneOS3.2以降は自力でaddSubviewしてね!という感じです。あと、これは条件がよくわからんのだけど、ムービーの再生が縦画面モードで行われるんで、これも自分でビューを90度回転しとります。

iOS4とそれ以前のバージョンでの処理の切り分けは respondsToSelector で行ってみました。バージョン番号を調べるよりスマートかな?


MPMoviePlayerController、かなり手動寄り?に仕様変更された印象ですが…これでムービーはかなり扱いやすくなったとも言えまする。ムービーを扱っとるアプリ製作者には福音ですな。

Xcode iPhoneSDK3.1.3 から iPhoneSDK4.0(iOS4) へ乗り換える時の注意点。

今朝Appleからメールが届く。
以下要約。

「アプリケーションのiOS4対応はちゃんとしてねー。今後リリース&アップデートされるアプリはiPhoneSDK4でビルドされてる事!そうそう、AppSoreではそのうちOS2.xのサポート外すから!」

そ、そっすか。

photoこれまでなるべく最大限の互換性を!という事でOS2.xでのビルドが出来る最後のバージョン、iPhoneSDK3.1.3を使ってましたが…いよいよiPhoneSDK4へ移行する決断をしました。実はアプリを1つ、来月中旬にもリリース予定なのでこの決断はかなり不安を伴いますが、エイヤっとインストール。


ちなみにダウンロードしてきたSDKのファイルサイズ。

SDK3.1.3  3GB
SDK3.2    2.5GB
SDK4.0    2.2GB

順調に減っとる的な。


で、無事にインストールは完了。結論として、古いバージョンからの乗り換えではだいたい以下の部分に気をつけてればよいかと。

  1. プロジェクトのベースSDKを4.0にする
  2. GCCのバージョンは4.2
  3. 外部ライブラリも忘れず同じ手順で再コンパイル
  4. iPhone OS Deployment Target を変更してOS3.1.3な実機に対応
  5. Xcode左上タブ「アクティブな実行可能ファイル」設定は「iPhone Simulator 4.0」にする

以下その格闘記。暇な人は読んでみてちょ。


現在進行中のプロジェクトを起動すると左上のタブには「Base SDK Missing」と表示されてる。ので、ビルド設定内「ベースSDK」を「iPhoneデバイス4.0」に変更。なんかこのへんルールが若干変わってるのね。

んで、ビルド。失敗。
なんかリンクで沢山エラーが出る…。

はっと思い立って「プロジェクト->アクティブターゲット"hoge"を編集」からGCCのコンパイラのバージョンを4.2に変更。今度は大丈夫。と思いきや、リンクしてるライブラリ(TouchJSON1.0.8)をリンクしようとしてエラー。ので、そっちも同じようにSDK4.0でコンパイル。これでようやくコンパイルが通るようになった!

じゃあシミュレーターで実行…あれ?シミュレーターがiPadモードで立ち上がる…。なんでやねん!これ、Xcode左上のタブで「hoge - iPad Simulator3.2」にチェックが入ってたのが原因。これを「hoge - iPhone Simulator4.0」に変更して実行。オッケィ!今度はシミュレーターがiPhneモードで立ち上がって、開発中のプロジェクトが無事動作した。ふぅ。

さて最後に実機での動作確認。手持ちのiPhone3GSはOS3.1.3。これは、プロジェクトの設定「iPhone OS Deployment Target」を変更する事で対応。いまんとこOS3以降で追加されたAPIは使わないように実装しとるので、ここの設定を「iPhone OS 3.0」にしてビルドして実機に転送したら無事動作しました。ここが最新のOSのバージョンになっとると、アプリ転送時にXcodeに怒られます。でもこれでいいのかな?不安だ。

Xcodeから実機のスナップショットも問題なし!(先日のOSアップデートあたりから出来なくなっとった)

そんなかんじで部長の開発環境は無事iPhoneSDK4.0に切り替わった。作業時間はだいたい2時間程度。いや~OS3.1.3で引っ張った引っ張った(笑)

iPhone4のRetina対応とかiPad対応とかはおいおい調べます。


そうそう。弟は無事バージョン3以降にアップデート出来たんやろか。ヤツはPCを持ってないからアップデートできへん!って言ってたのよね。

iPhone インスタンス化するクラスをその場で決める1命令。

photo01iPhoneアプリ起動時、インスタンス化するUIViewControllerを動的に指定できないかと調べてみたら…あっさり見つかったでござる。


実装は至って簡単。
NSString *name = @"hogeViewController";
Class newClass;
newClass = NSClassFromString(name);
viewController = [[newClass alloc] initWithNibName:name bundle:nil];

なんと便利なObjective-C。どんなクラスをインスタンス化するか、その場できめられるなんて!たぶんC++でもサクッと実装可能だと思うけど、この手の実装はCの時メンドクサかったからなぁ…。

あとは、IB側でデータを手直ししたり、UIApplicationDelegateでの変数の持ち方とか直せばかなり自由自在!エンディング画面とか作ってる時に、いちいちゲームクリアしないとデバッグできないなんてイマドキナンセンス。でもたとえば、テーブルビューを表示して、そっから起動するUIViewControllerを選べるようにすれば、タイトル画面とかゲーム本編とかエンディング画面とか、必要に応じて好き勝手起動できるようにできる。

これ、ちょっと応用すれば、シューティングゲームで敵を生成したりする処理を完全にデータで切り分けられる。しかも、他にプログラムを直さず、クラスを追加するだけってのがよい。「クラス追加→いきなりデータ調整」みたいな。

久々スッキリ!

薄型テレビは「(まだ)いらない」と言っている人です。

ブログネタ
薄型テレビに買い換えましたか?【地デジ完全移行まであと1年】 に参加中!
薄型テレビ=液晶・プラズマテレビとの前提でー。

supermario1
まだしばらくブラウン管です。だってファミコンとかでちゃんと遊べないんだもーん。薄型テレビに映すと画質が悪いし、ガンコン系も軒並みNGやし…。

DVD解像度程度ならブラウン管の方が断然映りがいいと思うのよ。これは薄型テレビでは克服するのが難しい問題(解像度に柔軟性が無い)が絡んでる。同じ理由で、最新の1080pとかの薄型テレビではDVDの映りが宜しくない感じ。ハイテクいたちごっこ(笑)

たぶん1080pのブラウン管が画質的にいちばんいいんだろうな…。安く手に入るなら是非欲しい!

iPhone ようやくゴールが見えてきた縦書き処理。iPadやiPhone4とかに対応してるのかは疑問。

いまだかつてないほど大真面目にiPhoneプログラミングに取り組んどるんじゃなかろうか。きっと読む方も大変だよね。内容の精度の高さは置いといて…(汗)


という訳で、前回の続き。こちらの大変秀逸なOpenType解説を参考に、ジリジリと実装。ついでに、Microsoftの解説ページも目を通すと出てくる用語の意味がなんとなくわかるんじゃなかろうかと。

まずは、Coverageテーブルを解釈するサブルーチン。NSArrayにして返却するようにしてみた。
-(NSArray *)coverageArray:(const UInt8* const)bytes ofs:(UInt16)CoverageOfs
{
    NSMutableArray *array = [[[NSMutableArray alloc] init] autorelease];

    UInt16 CoverageFormat = OSReadBigInt16(bytes, 0 + CoverageOfs);
    if(CoverageFormat == 1)
    {
        UInt16 GlyphCount = OSReadBigInt16(bytes, 2 + CoverageOfs);

        for(int j = 0; j < GlyphCount; j++)
        {
            unichar glyph = OSReadBigInt16(bytes, j * 2 + 4 + CoverageOfs);
            [array addObject:[NSNumber numberWithInt:glyph]];
        }
    }
    else
    if(CoverageFormat == 2)
    {
        UInt16 RangeCount = OSReadBigInt16(bytes, 2 + CoverageOfs);
        for(int j = 0; j < RangeCount; j++)
        {
            unichar start = OSReadBigInt16(bytes, j * 6 + 4 + CoverageOfs);
            unichar end = OSReadBigInt16(bytes, j * 6 + 6 + CoverageOfs);
            for(int k = start; k <= end; k++)
            {
                [array addObject:[NSNumber numberWithInt:k]];
            }
        }
    }
   
    return array;
}

つづいて、GSUBテーブルから縦書き用のグリフ変換テーブルを引っ張り出す本丸。前回のプログラムに追加する方式。でもこれ、なにも考えず線形に実装した程度なんで、データの取り出し方の参考にする程度にしてちょ。

#define TAG_KANA (('k' << 24) | ('a' << 16) | ('n' << 8) | 'a')
#define TAG_VERT (('v' << 24) | ('e' << 16) | ('r' << 8) | 't')

NSMutableDictionary *subGlyph = [[NSMutableDictionary alloc] init];
CFDataRef gsubTable = CGFontCopyTableForTag(fontRef, 'GSUB');
if(gsubTable)
{
    const UInt8* const bytes = CFDataGetBytePtr(gsubTable);

    UInt16 ScriptList = OSReadBigInt16(bytes, 4);
    UInt16 FeatureList = OSReadBigInt16(bytes, 6);
    UInt16 LookupList = OSReadBigInt16(bytes, 8);
    {
        // tag が kana なのを探す
        UInt16 num = OSReadBigInt16(bytes, 0 + ScriptList);
        for(int i = 0; i < num; i++)
        {
            UInt32 scriptRecordTag = OSReadBigInt32(bytes, i * 6 + 2 + ScriptList);
            UInt16 scriptRecordOfs = OSReadBigInt16(bytes, i * 6 + 6 + ScriptList);
            if(scriptRecordTag == TAG_KANA)
            {
                UInt16 scriptTableOfs = scriptRecordOfs + ScriptList;
                UInt16 scriptLangOfs = OSReadBigInt16(bytes, 0 + scriptTableOfs);
                scriptLangOfs += scriptTableOfs;
                UInt16 feature_num = OSReadBigInt16(bytes, 4 + scriptLangOfs);
                for(int h = 0; h < feature_num; h++)
                {
                    UInt16 feature_idx = OSReadBigInt16(bytes, h * 2 + 6 + scriptLangOfs);
                    UInt32 tag = OSReadBigInt32(bytes, feature_idx * 6 + 2 + FeatureList);
                    UInt16 ofs = OSReadBigInt16(bytes, feature_idx * 6 + 6 + FeatureList);
                    if(tag == TAG_VERT)
                    {
                        ofs += FeatureList;
                        UInt16 LookupIdx = OSReadBigInt16(bytes, 0 * 2 + 4 + ofs);

                        // LookupCount は1つと仮定して処理を進める
                        UInt16 LookupTableOfs = OSReadBigInt16(bytes, LookupIdx * 2 + 2 + LookupList);
                        LookupTableOfs += LookupList;
                        UInt16 type = OSReadBigInt16(bytes, LookupTableOfs);
                        UInt16 subOfs = OSReadBigInt16(bytes, 6 + LookupTableOfs);
                        if(type == 1)
                        {
                            subOfs += LookupTableOfs;
                            UInt16 SubstFormat = OSReadBigInt16(bytes, subOfs);
                            UInt16 CoverageOfs = OSReadBigInt16(bytes, 2 + subOfs);
                            if(SubstFormat == 1)
                            {
                                UInt16 DeltaGlyphID = OSReadBigInt16(bytes, 4 + subOfs);
                                CoverageOfs += subOfs;
                                NSArray *array = [self coverageArray:bytes ofs:CoverageOfs];
                                for(NSNumber *number in array)
                                {
                                    UInt16 glyph = [number intValue] + DeltaGlyphID;
                                    [subGlyph setObject:[NSNumber numberWithInt:glyph] forKey:number];
                                       
                                }
                            }
                            else
                            if(SubstFormat == 2)
                            {
                                UInt16 GlyphCount = OSReadBigInt16(bytes, 4 + subOfs);
                                CoverageOfs += subOfs;
                                NSArray *array = [self coverageArray:bytes ofs:CoverageOfs];
                                for(int j = 0; j < GlyphCount; j++)
                                {
                                    UInt16 glyph = OSReadBigInt16(bytes, j * 2 + 6 + subOfs);
                                    [subGlyph setObject:[NSNumber numberWithInt:glyph] forKey:[array objectAtIndex:j]];
                                }
                            }
                        }
                        break;
                    }
                }
            }
        }
    }
}

し、死ぬかと思った…。何なんだこの複雑さは?!前回のmortタイプと比べてどんだけコード量増えてんの?前回は一瞬で実装できたのに…みたいな。まぁとにかくこれでOpenTypeフォントから無事縦書き用グリフを引っ張り出す事に成功。

photo01iPhone内蔵のヒラギノフォントで縦書きーな表示を眺めておっさんちょっと嬉しかったぞ。次は禁則処理とかの実装じゃー!

ただ…手持ちのフォントでは、SubstFormatが2で、CoverageFormatが1のタイプしか試せず。もし他の組み合わせ例があるのでしたら、フォント名など教えてもらえると嬉しい限り。

というか、今回の経験を経てフォントデータの複雑さや、製作コストの高さを思い知りました…。レイアウトの良いフォント程、縦書き以外にもあらゆるパターンの文字の並びについての情報が入っとって、これどんだけ手間が掛っとるんだよ!みたいな。そら、完成度の高いフォントは買うと高いわ。的な。これ、みんなも是非知っておいてほしい事だと思ったね。
記事検索
電子書籍発売中

「チュートリアル形式で始めるOpenAL」
サウンド怖くない。C++による8つのチュートリアルで始めるOpenALプログラミング。さああなたも、自作アプリに魅力的な音効を添えてみませんか??
⇒Kindle版 ⇒iBooks版


「iPhoneアプリ『ういろう』のレシピ」
ゲームってどうやって作ってるの?? 拙アプリ『ういろう』の製作過程を本にまとめました。もちろんソースコードつき
⇒Kindle版 ⇒iBooks版


『チュートリアル形式で始めるOpenGL[2D編]』
OpenGL怖くない。C++による16のチュートリアルで始めるOpenGLプログラミング[2D編]。さああなたも、ゲーム作りを始めてみませんか?
⇒Kindle版 ⇒iBooks版
自作ゲーム配信中

『Puzzle & Monarch』
「君主候補となって国作り!! ただし制限時間は90秒。」森を作って道をつないで...あなただけの国を作ってみませんか??
⇒AppStore


『BRICK & TRIP』
咄嗟の判断に、あなたの指先はついてこれるか?! 爽快フリックアクション!! 様々な難関をくぐり抜けて旅の終着点を目指そう!!
⇒AppStore


『ういろう』
名古屋土産ういろうがiPhoneで大活躍?! 白ういろうを守れるのはあなただけ。ひゅーん、ぼよよーん!!
⇒AppStore ⇒LITE版


『こなへん』
ヒマラヤ山脈、大西洋、世界で一番深い湖… それって地球のどこにあるのか知ってるかな?『全方位直感地理クイズ』という新ジャンルに挑戦!あ、それ。地球をくーるくるw
⇒AppStore ⇒LITE版


『GEOSPOT』
ヒマラヤ山脈、大西洋、世界で一番深い湖… それって地球のどこにあるのか知ってるかな?『全方位直感地理クイズ』という新ジャンルに挑戦!あ、それ。地球をくーるくるw
⇒Windows ⇒Mac


『TieGunner』
マウス片手に大宇宙へ飛び立とう!『しっぽシューティング』というジャンルを作って頂きました^^; WinでもMacでも動きます。ソースもあるでよw
⇒Windows ⇒Mac
QRコード
QRコード
  • ライブドアブログ