2011年10月31日月曜日

Worrrm Preview動画

現在、アプリ申請中(リリースされました!)の iPhone ゲーム「Worrrm」
Wormにrが2個追加されちゃったのがゲーム名になっています。

プレイヤーが標的の中に居続けると得点が加算されるという、シンプルだけどついついハマってしまうゲームです。
よろしくお願いいたします。m(_ _)m

プレイ動画を作ってみましたが、なんかホームムービーチックになってしまった。(汗)

カスタムジェスチャーを作ろう!

iPhone アプリでジェスチャーを使うというと、特殊なアプリを想像しがちですが、メインで使うのではなく、ゲームの裏コマンド入力や、隠し機能、デバッグモードのオープンなどに積極的に使ってみるのはどうでしょうか。

オリジナルのジェスチャーを作るには、UIGestureRecognizer のサブクラスを作成して実装します。UIGestureRecognizer の詳細については、Apple の Event Handling Guide for iOS ドキュメント、またはこちらの日本語PDFをご覧ください。

UIGestureRecognizer は状態マシンとして機能する抽象クラスです。
ユーザーがこのクラスに関連付けられたビューをタッチすると、そのイベントがこのクラスにリダイレクトされます。そのタッチイベントを読み取って、ジェスチャーの進行中・失敗・成功の状態を遷移させるのがこのクラスの役目となります。

UIGestureRecognizer のサブクラスは、以下の5つのメソッドをオーバーライドします。

- (void)reset;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
reset 以外は、UIResponder のタッチメイベントそのままなので、馴染み深いでしょう。

ジェスチャーには大きく分けて、単発的なジェスチャーと、連続的なジェスチャーがあります。今回は、ジェスチャーの中でも比較的簡単な単発的なジェスチャーを扱います。
単発的なジェスチャーでは、タッチイベントの中でジェスチャーが確定したと判断できたら、state プロパティを UIGestureRecognizerStateRecognized に設定します。逆に失敗したと判断したら、UIGestureRecognizerStateFailed を設定します。
非常に簡単です。

公式ドキュメントには、チェックマーク(✔)ジェスチャーを実装する例がありますので、ここではもうちょっと複雑に以下のような三角旗ジェスチャーを作ってみました。

ヘッダファイルは以下のような変数を定義しておきます。
@interface FlagGesture : UIGestureRecognizer
{
 unsigned int actMode;  // ジェスチャー管理用ワーク
 CGPoint startPoint;      // ジェスチャー開始座標
 CGPoint topPoint;        // 三角旗の天井座標
}
touchesBegan:メソッドは以下のようになります。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
 [super touchesBegan:touches withEvent:event];
 if ([touches count]!=1) {
   // マルチタッチを感知したら失敗とする
  self.state = UIGestureRecognizerStateFailed;
  return;
 }
 // ジェスチャー開始座標格納
 startPoint = [[touches anyObject] locationInView:self.view];
}

touchesMoved:メソッドは以下のようになります。
actMode が 0 の時は、ジェスチャーが開始座標から旗の天井に向かうところを判定します。ここは垂直に上に伸びるジェスチャーなので、15°以上傾いたら失敗させるようにしています。
actMode が 1 の時は、ジェスチャーが旗の右端突端に向かうところを判定します。
actMode が 2 の時は、右端突端からジェスチャー開始地点まで戻るところを判定します。
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
 [super touchesMoved:touches withEvent:event];
 
 CGPoint nowPoint = [[touches anyObject] locationInView:self.view];
 CGPoint prevPoint = [[touches anyObject] previousLocationInView:self.view];
 
 switch (actMode) {
  case 0:  // 旗の天井に向かうアクション
   if (nowPoint.y<prevPoint.y) {
    float rot = atanf(fabsf(nowPoint.x-startPoint.x)/(startPoint.y-nowPoint.y));
    if (rot>=15) {
     // 15°以上傾いたら失敗
     self.state = UIGestureRecognizerStateFailed;
     return;
    }
   } else {
    if (nowPoint.x>prevPoint.x) {
     CGRect rect = self.view.bounds;
     // 垂直線が画面の高さの3分の1あるかを判定材料とする
     if ((startPoint.y-nowPoint.y)>=(rect.size.height*0.33f)) {
      // 旗の右端突端に向かうジェスチャーになったと仮定する
      actMode = 1;
      topPoint = nowPoint; // 旗の天井座標格納
      break;
     }
    }
    self.state = UIGestureRecognizerStateFailed;
   }
   break;
   
  case 1:  // 旗の右端突起に向かうアクション
   if ((nowPoint.x>=prevPoint.x)&&(nowPoint.y>=prevPoint.y)) {
    // OK
   } else if ((nowPoint.x<prevPoint.x)&&(nowPoint.y>=prevPoint.y)) {
    float dy = startPoint.y-topPoint.y;
    if (((topPoint.y+dy*0.35f)>nowPoint.y)||((startPoint.y-dy*0.35f)<nowPoint.y)) {
     // 突端のY座標が旗の垂直線の中央にないと失敗とする
     self.state = UIGestureRecognizerStateFailed;
     return;
    }
    actMode = 2;
   }
   break;
   
  case 2:  // 開始座標に戻るアクション
   if ((nowPoint.x<=prevPoint.x)&&(nowPoint.y>=prevPoint.y)) {
    // OK
   } else {
    self.state = UIGestureRecognizerStateFailed;
   }
   break;
 }
}

touchesEnded:メソッドは以下のようになります。
タッチが離れた時、actMode が 2 であり、且つ開始座標に近ければ、ジェスチャーが成功したとします。
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
 [super touchesEnded:touches withEvent:event];
 
 if ((self.state == UIGestureRecognizerStatePossible)&&(actMode==2)) {
  CGPoint nowPoint = [[touches anyObject] locationInView:self.view];
  CGPoint delta = CGPointMake(nowPoint.x-startPoint.x,nowPoint.y-startPoint.y);
  float k = delta.x*delta.x + delta.y*delta.y;
  if (k<(50*50)) {
   self.state = UIGestureRecognizerStateRecognized;
  }
 }
}

touchesCancelled:メソッドは以下のようになります。
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
 [super touchesCancelled:touches withEvent:event];
 self.state = UIGestureRecognizerStateFailed;
}

reset メソッドは以下のようになります。
ジェスチャーが失敗・成功後に自動的に呼ばれるメソッドです。管理用ワークを初期化しておきます。
- (void)reset
{
 [super reset];
 actMode = 0;
}

カスタムジェスチャークラスができたら、実際にビューに登録してみます。
以下の例では、ビューコントローラーの viewDidLoad メソッドから登録しています。 viewDidUnload メソッドでは、登録したジェスチャーを取り除いています。
ジェスチャーの登録・削除は、各アプリで必要な箇所に組み込む必要があるでしょう。
- (void)viewDidLoad
{
 [super viewDidLoad];
 FlagGesture *gesture = [[[FlagGesture alloc] initWithTarget:self action:@selector(handleGestureFlag:)] autorelease];
 [self.view addGestureRecognizer:gesture];
}

- (void)viewDidUnload
{
 [super viewDidUnload];
 NSEnumerator *e = [self.view.gestureRecognizers objectEnumerator];

 while (UIGestureRecognizer *gesture = (UIGestureRecognizer*)[e nextObject]) {
  [self.view removeGestureRecognizer:gesture];
 }
}

- (void)handleGestureFlag:(UIGestureRecognizer*)sender
{
 NSLog(@"FlagGesture OK! : state = %d",sender.state);
}

今回は、ちょっと長くなってしまいました。ぜひ、ご自分用の楽しいジェスチャーを作ってみて下さい。

2011年10月26日水曜日

Blocksを使ってUIViewアニメーション

Children's Blocks by lobo235
Children's Blocks, a photo by lobo235 on Flickr.
iOS 4.0 から Blocks が使えるようになって、UIView アニメーションにも Blocks が使えるようになりました。
UIView アニメーションにBlocks が使えると、とても便利なのですが、iOS 3 デバイスをサポートする関係上、なかなか使用する機会がありませんでした。
今回初めて使用してみたので、その使い方を書いてみたいと思います。

Blocks の詳細については、ドキュメントが日本語化されているので、そちらをご覧下さい。

UIView アニメーション Blocks 以前
複数のアニメーションを直列につなげて実行させたい場合、beginAnimations:context: と commitAnimations メソッドを使用すると、以下のように一つのアニメーション毎にメソッドを用意してやったりします。これだと見通しが悪いし、繋げるアニメーションが多いと絶望的に面倒くさいです。
// 一番最初のアニメーション開始場所
- (void)startAnimation
{
  someView.frame = CGRectMake(0,-40,480,40);

  CGContextRef ctx = UIGraphicsGetCurrentContext();
  [UIView beginAnimations:nil context:ctx];
  [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
  [UIView setAnimationDuration:0.5];
  [UIView setAnimationDelegate:self];
  [UIView setAnimationDidStopSelector:@selector(endAnimation1)]; // 次のアニメーション開始メソッド

  someView.frame = CGRectMake(0,0,480,40);
 
  [UIView commitAnimations];
}

// 前のアニメーションの終わりに呼ばれるメソッド
// ここが次のアニメーションの開始場所
- (void)endAnimation1
{
  CGContextRef ctx = UIGraphicsGetCurrentContext();
  [UIView beginAnimations:nil context:ctx];
  [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
  [UIView setAnimationDuration:3.0];
  [UIView setAnimationDelegate:self];
  [UIView setAnimationDidStopSelector:@selector(endAnimation2)];

  someView.frame = CGRectMake(0,1,480,40);

  [UIView commitAnimations];
}
// ・・・以下続く

UIView アニメーション Blocks 以後
対して、animateWithDuration:animations:completion: メソッドだとどうでしょうか?
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
animations: の後にアニメーション処理をブロックで、completion: の後にアニメーション後の処理をブロックで指定します。
複数のアニメーションを直列につなげる場合は、以下のようにします。
UIView *someView = ビューをインスタンス化してローカル変数に退避;
someView.frame = CGRectMake(0,-40,480,40);

[self.view addSubview:someView];

[UIView animateWithDuration:0.5
     animations:^{someView.frame = CGRectMake(0,0,480,40);}
     completion:^(BOOL finished) {
[UIView animateWithDuration:3.0
     animations:^{someView.frame = CGRectMake(0,1,480,40);}
     completion:^(BOOL finished) {
[UIView animateWithDuration:0.5
     animations:^{someView.frame = CGRectMake(0,-40,480,40);}
     completion:^(BOOL finished) {
[someView removeFromSuperview];
}];  }];  }];
ずいぶん見通しが良くなりました。
まあ、最後の括弧が見ため悪いですが、一連のアニメーションを一つのメソッド内で記述できてしまうのは楽です。

さらに特筆すべきは、メソッド内のローカル変数(someView)にブロック内からもアクセスできるという点です。
詳しい仕組みは Blocks のドキュメントに譲りますが、大雑把に言うとブロック内からアクセスするローカル変数(スタック上にある変数)はコピーされて保存され、そのブロックが生きている限り保持されるようです。更にヒープ領域にあるオブジェクトには、retain して保持するようです。(ブロックの消滅とともに release するもよう)

最後に、上記2つ目アニメーションで、Y座標を1だけ動かすのに3秒を割り当てているのは、ウエイトの代わりです。ここを仮に CGRectMake(0,0,480,40)のままにしたり、何も命令を記述しないと、3秒を待たず直ぐに completion ブロックが起動されてしまいます。

2011年10月24日月曜日

Xcode4.2でのDistributionビルド

Worrrm という iPhone/iPod touch 用ゲームを Apple に申請しました。
シンプルにして、短時間で遊べます。
ついついもう一回プレイしたくなるところを狙っていますが、さてどうなることでしょう?
今回、初の Xcode 4シリーズでのアプリ登録だったので、ビルド設定等でつまづいたところをチェックしてみます。

  • Architectures に armv6 を追加し忘れないようにする。デフォルト設定では $(ARCHS_STANDARD_32_BIT) と変数が使われていて分かりづらい。これが armv7 ということらしいですが、では、armv6 は何と記述すればいいのかと悩んでみれば、単に armv6 で良かったというオチ。

  • 静的ライブラリ (cocos2dなど) を別ターゲットでビルドしている場合、その静的ライブラリの Deployment の Skip install を「YES」にする。これを忘れると Validate 前に「does not contain a single–bundle application or contains multiple products」と怒られる。

  • Distribution 用ビルドは、Product → Archive で行う。ビルドが成功すれば オーガナイザーが起動され、Archives タブで Validate チェック及び、Submit が行える。


Xcode 3 と比べて特に注目すべきところは Application Loader を使わなくなった点と、Distribution 用ビルドが Archive という名前にまとめられて、分かりやすくなったところでしょうか。
あとはドキドキしながら Review を待つという点では、以前から何も変わりありません。

2011年10月23日日曜日

libSystem.B.dylibってなんだろう

これまで作ってきた iPhone アプリは、何とか iOS 3 デバイスでも動くように作ってきました。
しかし、現在開発中のアプリを iOS 3 デバイスでテストしてみると、以下のようなメッセージがコンソールに出て停止してしまいます。
dyld: Symbol not found: __NSConcreteStackBlock
  Referenced from: 〜省略〜
  Expected in: /usr/lib/libSystem.B.dylib
NSConcreteStackBlock ってなんですか?
なんだろうと思って調べたら、Blocks が原因だと分かりました。

Blocks 引用
ブロックオブジェクトは、C言語レベルの構文によるランタイム機能です。標準Cの関数に似ていま すが、実行可能なコードのほかに、自動(スタック)メモリまたはマネージド(ヒープ)メモリに バインドされている変数を含むことができます。したがって、ブロックでは、実行時の動作に影響 を与える状態(データ)セットを維持できます。
Blocks は iOS 4 から導入されました。
今回、初めて自アプリで GameKit を使用する際に Blocks を使っていました。

GameKit フレームワークは iOS 3デバイスには無いので、その際、無視出来るように、リンク設定で Optional (Xcode 3時代のWeakと同じ意味らしい) に設定していましたが、 どうやらこの libSystem.B.dylib も Optional に設定しないといけないらしいです。
設定方法は、下記画像を参考にして下さい。

libSystem.B.dylib を Optional にすると、iOS 3デバイスでも実行することができました。

とは言え、もうそろそろ iOS 3 が乗っている古いデバイスでは、処理速度的に対応が難しくなってきました。次回のアプリからは iOS 4 以上を対象にしたいと思っています。

2011年10月21日金曜日

Filter Forge 2.0

みんな大好き、パンダ!

Filter Forge というグラフィックにフィルターをかけるツールがあります。
Photoshop プラグインとして利用できますが、単体アプリとしても使用出来ます。
このツールは単にグラフィックにフィルターをかけるだけでなく、そのフィルターを作成できるエディターがついています。
そのフィルターの作り方が、Apple の QuartzComposer に似ていて、ちょっと面白いです。
エディター画面
エディターは、小さな機能をパイプで繋げて、最終的に複雑なフィルターを作成できるようになっています。複雑なものはそれだけレンダリングにも時間がかかります。

Filter Forge には web上にある沢山のフィルターをダウンロードする仕組みがあって、それを使って見るだけでも結構楽しめます。

その中から、いくつかを紹介したいと思います。
一つ目は、Mess Painter というフィルター。
油絵風な効果がステキです。ちなみに写真の枠もフィルターで作成されています。

同じく Mess Painter のパラメータ変化バージョン。
ガラっと雰囲気が変わりました。

おなじみ LOMO カメラ風フィルター。
暗い所がつぶれて、青味がかった写真になります。

これは Bad Trip というフィルター。
まさに悪夢…。

Shark Tank! というフィルター。
パンダが深海にダイビングしています。

写真に付加するフィルターだけでなく、いわゆるジェネレーター系のフィルターも充実しています。

これは Spiral Galaxy。
銀河中央のボケ具合がすばらしい。

変わりダネとしてはこれ。その名も Filter Force (笑)
あなどってはいけません。ソードの柄の部分もなんとフィルターで作られています。
エディターでみると、その努力に敬服します。どうやって作られているかは、ご自身の目でお確かめ下さい。



Filter Forge 2.0 は 30日間のトライアル版が、サイトよりダウンロードできます。
トライアル期間中の機能は、無制限に解放されているようです。

また、僕の環境だけかもしれませんが、Mac版の Filter Forge 2.0 をインストールしたところ、アプリケーションフォルダーに作られる Filter Forge フォルダのアクセス権が無く、インストールできてもツールを立ち上げるすべがありませんでした。その場合、Filter Forge フォルダを読み書きできるように、Finder でアクセス権を変更する必要があります。(最近、こんなのばっかり…)

インストール直後、フィルターは最小限のものしかインストールされていないので、サイトからダウンロードする必要があります。
Filter Forge サイトから、Filters タブを選び、自分の好きなフィルターのページへ行きます。そこから「Open this filter in Filter Forge」をクリックして下さい。
最初はメッセージが表示され、「Click 〜」というところをクリックすると Filter Forgeが起動され、フィルターのダウンロードを開始します。
この辺は、環境によって動作が変わると思いますが、メッセージを読んで対応していけば問題ないと思います。

Filtersのページ 

この Filter Forge というソフト、3D モデルのテクスチャーを作成したり、画像処理プログラムの勉強をしたりと、いろいろな使い道がありそうです。

Related Posts Plugin for WordPress, Blogger...