jarinosuke blog

about software engineering, mostly about iOS

タッチイベントの取得と当たり判定処理

前置き

こんにちは!こんにちは!

みなさんには関係無いかもしれないですが、一度書いていたものが吹っ飛んだのでかなり雑に書きます!
折れない心を褒めて下さいね!

iPhoneAppって

最近iPhoneでゲーム作ってます。まだテクスチャも満足に整っておらず、ただただゲームの中身を作っているだけですが面白いです。早く絵とか書いてそれに命を乗せたいですね!

iPhoneでゲームを作ると言っても、様々な方法があるとは思うのですが、やはりOpenGLESを使うのが一番主流なのかなということで僕もそれで開発しています。
初めてのゲーム制作ということで前途多難でしたが、少し慣れてきた && アウトプット皆無な状態なのでブログに起こそうと思いました。今回もそうなのですが、iPhoneAppはプロジェクト毎にソースコードがかなり分かれていて全て載せてしまうと、全体像を把握するのにとても労力を要してしまうので、なるたけ重要な部分だけを載せました。

とは言いつつも、一応全体像を把握しておかないと分からない部分も出てきてしまうので言葉でOpenGLESプロジェクトの全体を説明したいと思います。大まかに分けてゲームを制作する際に、必要なシステムは以下の3つになるんではないかと思います。上記二つについてはプロジェクトを作成した際にテンプレートとして入っているので心配ないですね!では簡単に説明していきます。

  • ビュー

一言でいえばユーザから情報を受け取る窓口になります。UIです。これがダメだとすぐにホームボタンを押されてしまいます。

  • レンダラー

名前の通りテクスチャを描画する部分になります。

  • ゲームコントローラー

上記二つのテンプレートだけでゲームとして成り立ちそうな気がしますよね。でもそれだとプレーヤーの座標であったり、各種フラグの管理がとても大変になります。そこで一つゲーム全体を管理するクラスとして、僕はこれを入れています。

もう少し分かりやすく言うと、こんな感じになりますね!
f:id:jarinosuke0808:20100403174949j:image

では全体像が把握できた所で、タッチイベントの取得、そして当たり判定処理について順々に見ていきますよー!

タッチイベントの取得

まずはじめにタッチイベントの処理はどう行われているかご存知でしょうか?
恐らくiPhoneユーザーの人は、ピンチやスワイプなどのアクションは知っているかと思います。これらのアクションは上位のオブジェクトなどで実装されていますよね。これらを作っているのは、下位のオブジェクトで全てを支えているUIViewとそのスーパークラスUIResponderなのです。(お前ら感謝しろよ!)UIResponderはiPhone上で行われるタップやドラッグなどの基礎的な入力を処理するメソッドを持っているのです。
なので、テーブルだったりボタンなどはその低位のイベントを受け取りラップしてスワイプだったりの高次なイベントにしているわけですね!何が言いたいかって云うと、Appleがここまで低いAPIを提供してくれているのだからx(x>=3)連続タップ受信とかも技術的に実装可能だし、未来は僕らの手の中ってことです!

話がそれましたが、そういうことです。
では実際に書いたコードを見てみましょう。

まずはソースソース!
EAGLView.mm

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{UITouch *touch = [touches anyObject];
CGPoint oldLocation = [touch previousLocationInView:self];
CGPoint location = [touch locationInView:self];

float powX = (location.x - oldLocation.x) / 1000;
float powY = -(location.y - oldLocation.y) / 1000;
     [gameControl getPower:powX powerY:powY];

}

上記のソースコードはEAGLViewといテンプレートに書きました。名前からも察するように、これはビューです。ここで場合によっては何かボタンを付けたり、ピンチジェスチャを実装したりします。
書かれているメソッドはtouchesMovedですね。用意されているタッチイベント受信用のメソッドは四つあって他にtouchesBegan, touchesEnded, touchesCanceledがあります。
名前からも分かる通り、指がスクリーンに触れた時、触れたまま移動している時、スクリーンから離れる時、キャンセルした時(良く分からん)になります。
それぞれ引数としてイベントの集合であるNSSetとUIEventの二つが求められています。NSSetは前回のイベントから後に発生したイベント群です。
UIEventってのは、一群のUITouchをまとめて扱いやすくしたもので、これ使わないと一つのビューで起こったイベントをまとめて処理できません。
じゃあUITouchって何かって?簡単にいうと、一つのジェスチャを他に伝えるためのプロパティ群(tapcount、timestamp、発生ビューへのポインタなどなど)です〜。こまけーこたぁー良いんだよ!はい。

メソッドの中身を見ていきましょう。
touch変数に全てのオブジェクトにおけるタッチイベントを入れ、それらのタッチイベントに対して一つ前のイベントの座標と現在の座標を、それぞれoldLocation、locationに入れています。それらを用いて過去と現在の座標の差分を取って「移動距離」を取っています。1000で割っているのは、これらの値をゲームコントローラーに送るのですが、そのままだと320×480の座標系の値を返してしまいます。それをそのままオブジェクトの移動に用いてしまうと画面外に旅立ってしまうので適当な値で割っています。

ということで、これだけです!簡単ですよね!10行以下で、人の指が移動した値が分かるんですよ!

移動量取得と当たり判定

では次に実際に取得した値を、ゲームコントローラで受け取ってどのようにレンダラーに反映させるのかを見てみましょう。以下がゲームコントローラーのソースコードの片鱗になります。もっとたくさーんのコードがありますが(もっとコード書けますよという自己顕示)、以下にあげるメソッドが主な部分になると思います。


GameControl.mm

- (void)getPower:(float)powX powerY:(float)powY{
ballPower.x = powX;
ballPower.y = powY;
}


- (void)update{ballPosition.x += ballPower.x;
ballPosition.y += ballPower.y;

ballPower.x *= 0.95f;
ballPower.y *= 0.95f;



float distanceBetweenBallAndUp = 1.5f - ballPosition.y;
if (distanceBetweenBallAndUp <= ballRadius) {
ballPower.y = -ballPower.y;
     }
float distanceBetweenBallAndDown = -(-1.5f - ballPosition.y);
if (distanceBetweenBallAndDown <= ballRadius) {
ballPower.y = -ballPower.y;
     }
float distanceBetweenBallAndRight = 1.0f - ballPosition.x;
if (distanceBetweenBallAndRight <= ballRadius) {
ballPower.x = -ballPower.x;
     }
float distanceBetweenBallAndLeft = -(-1.0f - ballPosition.x);
if (distanceBetweenBallAndLeft <= ballRadius) {
ballPower.x = -ballPower.x;
     }
}

まずはさきほどの受信用メソッドをみてみると、ゲームコントローラーが持つballPowerという変数にそれぞれ代入している事が分かりますね。
次にupdateメソッドです。この関数は少し特殊です。名前からも分かるように、この関数はレンダラーの中で実行されるように書かれています。なのでこの関数はコンマ何秒に一回、ぐるんぐるん実行されぐるんぐるん「更新」するために書かれているものです。
中ではどのようなことが行われているかというと、まずはじめに先ほど受信したballPowerをballPositionというゲームコントローラーの変数に足しています。これでボールの座標が変わることになりますね。
ですがこれだけの処理だと、ぐるんぐるんまわるうちにどんどん移動してしまうので、受信した「パワー」を減衰させなければいけません。そのための処理が0.95をかけているところになります。
ただ、これで「パワー」は減衰してくれるのですが、これだけだとスクリーンの向こう側に行ってしまいます。なので、もしスクリーンに触れそうなら「パワー」を逆方向にするという当たり判定&跳ね返し処理が必要になります。
ですが、難しく考える事はありません。
レンダラーで設定した仮想座標である、上下左右の座標系(-1.0<=x<=1.0, -1.5<=y<=1.5)とオブジェクトの座標の差がオブジェクトの半径以下、つまり壁がオブジェクトにめり込む一歩手前になったら、壁と垂直方向にある「パワー」を反転させて跳ね返そう、とコードに書いてあります。

なんと!これだけで!タッチイベントを取得し!それを座標に反映させ!壁に跳ね返す!という処理が完成しました!

あとはこの座標をレンダラーに渡して、マ○オなりカー○ーなり動かしてみて下さい!