スーパーマリオブラザーズクローンゲームの作成(パート1)

画像 私たちの多くにとって、スーパーマリオブラザーズはゲームプレイを本当に魅了した最初のゲームでした。
任天堂の直感的なSMBコントロールと優れたレベルの設計により、配管工とそのパートナーの仮想世界で何時間も過ごすことができました。

Jacob Gundersenによるこの素晴らしいチュートリアルは、独自のプラットフォーマーを作成します。 しかし、メインキャラクターはコアラになるので、ゲームを「スーパーコアリオブラザーズ!」と呼びます。
また、メカニズムを単純化するために、敵の移動を忘れています。 代わりに、床に埋め込まれたスタッド付きブロックを使用します。 これにより、プラットフォーマーの中心である物理エンジンに完全に集中できるようになります。

注意! カットの下では、信じられないほどの量の翻訳されたテキスト、写真、コード(翻訳されていないコード)、および独自の物理エンジンを作成するためのガイド!

このチュートリアルでは、事前にCocos2Dプログラミングの基本に精通していることを前提としています。 それ以外の場合は、最初にRayのWebサイトでいくつかの最初のレッスンを理解することを強くお勧めします。

始めましょう


開始するには、このチュートリアルのスタータープロジェクトをダウンロードしてください。 解凍し、Xcodeで開き、実行します。 同様の何かがエミュレータ画面に表示されるはずです。

画像

そうです-ただ退屈な空白の画面! :]チュートリアルを進めるにつれて、完全に埋めていきます。
必要なすべての画像と音声は、スタータープロジェクトに既に追加されています。 プロジェクトの内容を見ていきましょう。


物理エンジンの基本


プラットフォーマーは物理エンジンに基づいて動作します。このチュートリアルでは、独自の物理エンジンを作成します。
同じBox2DやChipminkを使用しないで独自のエンジンを作成する必要がある理由は2つあります。

  1. 詳細設定。 zenプラットフォーマーを完全に理解するには、エンジンを完全にカスタマイズする方法を学ぶ必要があります。
  2. シンプル。 Box2DとChipmunkにはカスタマイズ可能な機能がたくさんありますが、それは概して私たちにとっては役に立ちません。 はい、リソースがあります。 そして、私たち自身のエンジンは、許す限り正確に食べます。

物理エンジンは、2つの主なタスクを実行します。
画像
  1. 動きをシミュレートします。 物理エンジンの最初のタスクは、重力、運動、ジャンプ、摩擦の相反する力をシミュレートすることです。
  2. 衝突を検出します。 2番目のタスクは、プレイヤーとそのレベルの他のオブジェクトとの衝突を判断することです。

たとえば、ジャンプ中、上向きの力がコアラに作用します。 しばらくすると、重力がジャンプの力を上回り、古典的な速度の放物線状の変化が得られます。
衝突検出の助けを借りて、重力の影響下で床を通り抜けるたびにコアラを停止し、コアラがいつスパイクを踏んだかを判断します(ay!)。
実際にどのように機能するかを見てみましょう。

物理エンジンの作成


作成する物理エンジンでは、Koalaには動きを記述する独自の変数があります:速度、加速度、位置。 これらの変数を使用して、プログラムの各ステップで次のアルゴリズムを使用します。

  1. ジャンプまたはモーションアクションが選択されていますか?
  2. もしそうなら、コアラのジャンプまたは動きの強さを使用してください。
  3. また、重力をコアラに適用します。
  4. 結果のコアラ速度を計算します。
  5. 受信した速度をKoalaに適用し、その位置を更新します。
  6. Koalasと他のオブジェクトとの衝突を確認します。
  7. 衝突が発生した場合は、衝突が発生しなくなるまで障害物からコアラを移動します。 または貧しいコアレを損傷します。
画像
プログラムのすべてのステップでこれらのステップを実行します。 私たちのゲームでは、重力によってコアラが床からどんどん沈んでいきますが、衝突が検出されるたびに床の上のポイントに戻ります。 この機能を使用して、コアラが土地に触れているかどうかを判断することもできます。 そうでない場合は、コアラがジャンプ状態にあるか、障害物からジャンプした直後にプレーヤーがジャンプするのを防ぐことができます。
ポイント1〜5はKoalaオブジェクト内で発生します。 必要な情報はすべてこのオブジェクト内に保存する必要があり、Koaleが変数自体を更新できるようにすることは非常に論理的です。
ただし、6番目の点、つまり衝突の定義については、壁、床、敵、その他の危険など、レベルのすべての機能を考慮する必要があります。 衝突検出は、GameLevelLayerを使用してプログラムのすべてのステップで実行されます-これは、ほとんどの物理タスクを実行するCCLayerのサブクラスであることを思い出してください。
コアラが自分の手で位置を更新できるようにすると、最終的にコアラは壁や床に触れます。 そして、GameLevelLayerはコアルを復活させます。 そして何度も何度も-それはコアラが彼女が振動しているように見えるようになります。 (朝のコーヒーが多すぎる、コアリオ?)
そのため、コアレがその状態を更新することを許可しません。 代わりに、Koalaが更新する新しい変数desiredPositionをKoaleに追加します。 GameLevelLayerは、KoalaをdesiredPositionに移動できるかどうかを確認します。 その場合、GameLevelLayerはKoalaの状態を更新します。
すべてが明確ですか? コードでどのように見えるか見てみましょう!

TMXTiledMapをダウンロードする


Tile Mapsのようなマップがどのように機能するかについてはご存じだと思います。 そうでない場合は、 このチュートリアルでそれらについて読むことをお勧めします。
レベルを見てみましょう。 タイルマップエディターを起動し(これをまだ行っていない場合はダウンロードします)、プロジェクトフォルダーからlevel1.tmxを開きます。 以下が表示されます。

画像

サイドバーを見ると、3つの異なるレイヤーがあることがわかります。


コーディングの時間です! GameLevelLayer.mを開き、 #importの@implementation前に次を追加します。

 @interface GameLevelLayer() { CCTMXTiledMap *map; } @end 

headクラスのメッシュマップを操作するために、CCTMXTiledMapクラスのローカル変数マップを追加しました。
次に、レイヤーの初期化中にレイヤーにメッシュマップを配置します。 initメソッドに次を追加します

 CCLayerColor *blueSky = [[CCLayerColor alloc] initWithColor:ccc4(100, 100, 250, 255)]; [self addChild:blueSky]; map = [[CCTMXTiledMap alloc] initWithTMXFile:@"level1.tmx"]; [self addChild:map]; 

最初に、青い空の色に背景(CCLayerColor)を追加しました。 次の2行のコードは、マップ変数(CCTMXTiledMap)を読み込んでレイヤーに追加するだけです。

次に、 PlayerLevelLayer.mPlayer.hをインポートします。

 #import "Player.h" 

引き続きGameLevelLayer.mで@ interfaceセクションに次のローカル変数を追加します。

 Player * player; 

次に、 initメソッドに次のコードを使用して、Koalaをレベルに追加します

 player = [[Player alloc] initWithFile:@"koalio_stand.png"]; player.position = ccp(100, 50); [map addChild:player z:15]; 

このコードはKoalaスプライトオブジェクトをロードし、その位置を設定してマップオブジェクトに追加します。
あなたは、なぜ単にコアラオブジェクトをレイヤに直接追加するのではなく、マップに追加するのでしょうか? すべてがシンプルです。 どのレイヤーをコアラの前に配置し、どのレイヤーをコアラの後ろに配置するかを直接制御します。 したがって、コアルをメインレイヤーではなく、カードの子にします。 Koalaを前面に配置するため、Zオーダーに15を指定します。また、マップをスクロールしても、コアラはメインレイヤーではなく、マップに対して同じ位置にあります。
よし、やってみよう! プロジェクトを実行すると、次が表示されるはずです。

画像

ゲームのように見えますが、コアリオは重力を無視します! 物理エンジンを使用して、天から地球へと降ろします:]

コアリオの重力状況

画像
物理学のシミュレーションを作成するには、受け取った情報から始めて、コアラの状態を考慮し、コアラに力を加える分岐ロジックの複雑なセットを作成します。 しかし、この世界はすぐに複雑になりすぎます-それはそれほど難しい仕事ではないので、本当の物理学です。 現実の世界では、重力は常にオブジェクトを引き下げます。 そのため、一定の重力を加えて、プログラムのすべてのステップでコアラに適用します。
他の力も、単にオンとオフを切り替えるだけではありません。 現実の世界では、別の力が最初の力を上回るか等しくなるまで、力はオブジェクトに作用します。
たとえば、跳躍力は重力をオフにしません。 重力が再びコアラを地面に押し付けるまで、しばらく重力の力を超えます。
これが物理学のモデル化方法です。 コアラに重力をかけるかどうかを決めるだけではありません。 重力は常に存在します。

神を演じる

画像
エンジンのロジックは、力がオブジェクトに作用する場合、他の力が最初の力を超えるまで動き続けることを意味します。 Coalioが棚から飛び降りるとき、彼はパスで障害物に遭遇するまで一定の加速で下に移動し続けます。 Coalioを動かしても、彼の動きの力を使うのをやめるまで、彼は動きを止めません。 Coalioが停止するまで摩擦が作用します。
物理エンジンを作成すると、このような単純なゲームロジックが、氷床や崖からの落下などの複雑な物理的問題の解決にどのように役立つかがわかります。 この動作モデルにより、ゲームを動的に変更できます。
また、このような馬による動きは、オブジェクトの状態を常に確認する必要がないため、実装を容易にします。オブジェクトは、現実世界の物理法則に従うだけです。
時々、神を演じる必要があります! :]

地球の法則:CGPointsおよびForces


次の概念を示しましょう。


物理シミュレーションでは、オブジェクトに適用される力はオブジェクトを特定の速度に加速し、オブジェクトはパス内で別の力に遭遇するまでその速度で移動します。 速度は、新しいアクティブなフォースが現れると、フレームごとに変化する値です。
CGPoint構造を使用して、速度、力/加速度、位置の3つのことを示します。 CGPoint構造を使用する理由は2つあります。

  1. それらは2Dです。 速度、パワー/加速度、および位置はすべて、2Dゲームの2D値です。 重力は一方向にのみ作用すると言うことができますが、ゲームのある時点で重力の方向を緊急に変更する必要がある場合はどうでしょうか? スーパーマリオギャラクシーを考えてください!
  2. これは便利です。 CGPointを使用すると、Cocos2Dに組み込まれているさまざまな機能を使用できます。 特に、ccpAdd(加算)、ccpSub(減算)、ccpMult(float型の変数による乗算)を使用します。 これにより、コードの読み取りとデバッグがはるかに簡単になります!

コアラのオブジェクトの速度は可変であり、重力、運動、ジャンプ、摩擦などのさまざまな力の出現によって変化します。
ゲームの各ステップで、すべての力を加算し、結果の値をコアラの現在の速度に加算します。 その結果、新しい現在の速度を受け取ります。 フレームレートを使用して削減します。 その後、コアラを移動します。
注意:上記のいずれかが誤解を招く場合、素晴らしい人物であるダニエル・シフマンは、ベクターに関する優れたチュートリアルを作成しました。これは、使用する構造物に対する力の作用を完全に説明しています。
重力から始めましょう。 力を使用する実行ループを作成します。 if条件ブロックを閉じる直前に、 GameLevelLayer.mファイルのinitメソッドに次のコードを追加します。

 [self schedule:@selector(update:)]; 

次に、新しいメソッドをクラスに追加します。

 - (void)update:(ccTime)dt { [player update:dt]; } 

次に、 Player.hを開き、次のように変更します。

 #import <Foundation/Foundation.h> #import "cocos2d.h" @interface Player : CCSprite @property (nonatomic, assign) CGPoint velocity; - (void)update:(ccTime)dt; @end 

次のコードをPlayer.mに追加します。

私をクリック
 #import "Player.h" @implementation Player @synthesize velocity = _velocity; // 1 - (id)initWithFile:(NSString *)filename { if (self = [super initWithFile:filename]) { self.velocity = ccp(0.0, 0.0); } return self; } - (void)update:(ccTime)dt { // 2 CGPoint gravity = ccp(0.0, -450.0); // 3 CGPoint gravityStep = ccpMult(gravity, dt); // 4 self.velocity = ccpAdd(self.velocity, gravityStep); CGPoint stepVelocity = ccpMult(self.velocity, dt); // 5 self.position = ccpAdd(self.position, stepVelocity); } @end 

上記のコードをステップごとに見ていきましょう

  1. ここで、オブジェクトを初期化し、速度変数をゼロに設定する新しいinitメソッドを追加しました。
  2. ここで、重力ベクトルの値を指定しました。 1秒ごとに、Koalaの速度を450ピクセルずつ加速します。
  3. ここでは、重力ベクトルの値を減らしてフレームレートを満たすためにccpMultを使用しました。 ccpMultは、フロートとCGPointを受け取り、CGPointを返します。
  4. ここでは、現在のステップの重力を計算するとすぐに、現在の速度に重力を追加します。
  5. 最後に、1ステップの速度を計算したら、ccpAddを使用してコアラの位置を更新します。

おめでとうございます! 最初の物理エンジンを作成する正しい道を歩んでいます! プロジェクトを実行して結果を確認してください!

画像

Oooo-Coalioが床に落ちます! それを修正しましょう。

夜のキック-衝突検出


衝突検出は、物理エンジンの基盤です。 画像フレームの単純な使用から複雑な3Dオブジェクトの衝突まで、さまざまなタイプの衝突検出があります。 幸いなことに、プラットフォーマーは複雑な構造を必要としません。
オブジェクトとのコアラの衝突を検出するには、コアラを直接囲むセルにTMXTileMapを使用します。 さらに、iOSに組み込まれたいくつかの関数を使用して、Koalaのスプライトがセルのスプライトと交差するかどうかを確認します。
CGRectIntersectsRectおよびCGRectIntersection関数は、これらのチェックを非常に簡単にします。 CGRectIntersectsRectは2つの長方形が交差するかどうかをチェックし、CGRectIntersectionは交差する長方形を返します。
まず、Koalaのスコープを定義する必要があります。 ロードされた各スプライトにはフレームがあります。フレームはテクスチャのサイズであり、boundingBoxと呼ばれるパラメーターを使用してアクセスできます。
すでにboundingBoxにあるフレームを定義するのはなぜですか? 通常、テクスチャの周囲には透明なエッジがありますが、衝突を判断する際に考慮しません。
時には、スプライトの実際の画像(透明ではない)の周囲の数ピクセルを考慮する必要さえありません。 マリオが壁にぶつかったとき、彼はそれに少し触れますか、それとも彼の鼻はブロックにわずかに埋まっていますか?
やってみましょう。 Player.hに追加します

 -(CGRect)collisionBoundingBox; 

Player.mに追加します

 - (CGRect)collisionBoundingBox { return CGRectInset(self.boundingBox, 2, 0); } 

CGRectInsetは、2番目と3番目の引数のピクセル数でCGRectを圧縮します。 この場合、衝突フレームの幅は6ピクセル小さくなります-両側に3ピクセル。

重量挙げ


重量物を持ち上げる時間です。 (「ねえ、あなたは今私を太っていると思いましたか?」コアリオは言います)。
衝突を検出するには、GameLevelLayerにいくつかのメソッドが必要です。 特に:


上記の方法を簡素化する2つのヘルパー関数を作成します。


次のコードをGameLevelLayer.mに追加します。

 - (CGPoint)tileCoordForPosition:(CGPoint)position { float x = floor(position.x / map.tileSize.width); float levelHeightInPixels = map.mapSize.height * map.tileSize.height; float y = floor((levelHeightInPixels - position.y) / map.tileSize.height); return ccp(x, y); } - (CGRect)tileRectFromTileCoords:(CGPoint)tileCoords { float levelHeightInPixels = map.mapSize.height * map.tileSize.height; CGPoint origin = ccp(tileCoords.x * map.tileSize.width, levelHeightInPixels - ((tileCoords.y + 1) * map.tileSize.height)); return CGRectMake(origin.x, origin.y, map.tileSize.width, map.tileSize.height); } 

最初のメソッドは、メソッドに渡すピクセル単位の座標にあるセルの座標を返します。 セルの位置を取得するには、セルサイズで座標を除算します。
Cocos2D / OpenGLシステムの座標は左下隅から始まり、システム座標は左上隅から始まるため、高さの座標を反転する必要があります。 標準-それはクールではありませんか?
2番目の方法は反対のことを行います。 セル座標にセルサイズを乗算し、そのセルのCGRectを返します。 繰り返しますが、高さを拡張する必要があります。
なぜ高さのy座標に1を追加する必要があるのですか? セルの座標はゼロから始まるため、20個のセルの実際の座標は19です。高さに1を追加しない場合、ポイントは19 * tileHeightになります。

私は細胞に囲まれています!


それでは、コアラを囲むセルを決定する方法に移りましょう。 このメソッドでは、配列を作成し、それを返します。 この配列には、セルのGID、セル座標、およびこのセルのCGRect情報が含まれます。
この配列を優先度順に整理し、衝突を判断します。 たとえば、斜めの衝突を定義する前に、上、左、右、下から衝突を判断したいとします。 また、コアラと下のセルの衝突を検出すると、地面に触れるためのフラグを設定します。
このメソッドをGameLevelLayer.mに追加します

私をクリック
 - (NSArray *)getSurroundingTilesAtPosition:(CGPoint)position forLayer:(CCTMXLayer *)layer { CGPoint plPos = [self tileCoordForPosition:position]; //1 NSMutableArray *gids = [NSMutableArray array]; //2 for (int i = 0; i < 9; i++) { //3 int c = i % 3; int r = (int)(i / 3); CGPoint tilePos = ccp(plPos.x + (c - 1), plPos.y + (r - 1)); int tgid = [layer tileGIDAt:tilePos]; //4 CGRect tileRect = [self tileRectFromTileCoords:tilePos]; //5 NSDictionary *tileDict = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:tgid], @"gid", [NSNumber numberWithFloat:tileRect.origin.x], @"x", [NSNumber numberWithFloat:tileRect.origin.y], @"y", [NSValue valueWithCGPoint:tilePos],@"tilePos", nil]; [gids addObject:tileDict]; } [gids removeObjectAtIndex:4]; [gids insertObject:[gids objectAtIndex:2] atIndex:6]; [gids removeObjectAtIndex:2]; [gids exchangeObjectAtIndex:4 withObjectAtIndex:6]; [gids exchangeObjectAtIndex:0 withObjectAtIndex:4]; //6 for (NSDictionary *d in gids) { NSLog(@"%@", d); } //7 return (NSArray *)gids; } 

Pff-コード全体。 心配しないで、詳細に説明します。
しかし、その前に、マップ上に3つのレイヤーがあることに注意してください。
異なるレイヤーが存在するため、各レイヤーの衝突を別々に定義できます。


もちろん、さまざまなブロックとのさまざまな衝突を判断するにはさまざまな方法がありますが、マップ上のレイヤーなど、非常に効果的なものがあります。
さて、ステップごとにコードを見ていきましょう。

1.まず、入力するセルの座標(コアラの座標)を取得します。
2.次に、セルに関する情報を返す新しい配列を作成します。
3.次に、サイクルを9回開始します。コアラが既に配置されているセルを含む、9つの可能な移動セルがあるためです。 次の数行は、9つのセルの位置を定義し、それらをtilePos変数から保存します。

注:コアラが既に配置されているセルとの衝突を判断する必要がないため、8つのセルに関する情報のみが必要です。
このケースを常にキャッチして、コアラをセルの1つに移動する必要があります。 Coalioがソリッドセル内にある場合、Coalioスプライトの半分以上が中に入っています。 彼はそれほど速く動くべきではありません-少なくともこのゲームでは!
これらの8つのセルを操作しやすくするには、最初にCoalioセルを追加し、最後に削除します。

4. 4番目のセクションでは、tileGIDAtを呼び出します。 このメソッドは、特定の座標にあるセルのGIDを返します。 受信した座標にセルがない場合、メソッドはゼロを返します。 次に、値「cell not found」にゼロを使用します。
5.次に、ヘルパーメソッドを使用して、Cocos2D座標データのセルのCGRectを計算します。 受信した情報をNSDictionaryに保存します。 このメソッドは、受信したNSDictionaryから配列を返します。
6. 6番目のセクションでは、配列からKoalaセルを削除し、セルを優先度順に並べ替えます。
画像
多くの場合、コアラの下のセルとの衝突の場合、セルとの衝突も斜めに定義します。 右の図をご覧ください。 赤で示されたコアラの下のセルとの衝突を検出することにより、青で強調表示されたブロック#2との衝突も検出します。
衝突検出アルゴリズムはいくつかの仮定を使用します。 これらの仮定は、対角セルではなく隣接セルに当てはまります。 そのため、可能な限り対角セルを使用したアクションを避けるようにします。
そして、これは並べ替え前後の配列内のセルの順序を明確に示した写真です。 上、下、右、左のセルが最初に処理されることに気付くかもしれません。 セルの順序がわかれば、コアラがいつ地面に触れたのか、雲の中を飛んだのかを判断しやすくなります。
画像

7.セクション7のサイクルにより、セルをリアルタイムで監視できます。 ですから、すべてが計画通りに進んでいることを確実に知ることができます。

次のゲームの発売の準備がほぼ整いました! ただし、まだいくつかの小さなことをする必要があります。 壁レイヤーを変数としてGameLevelLayerクラスに追加して、使用できるようにする必要があります。

GameLevelLayer.m内で、次の変更を行います。

 //   @interface CCTMXLayer *walls; //    init,  ,      walls = [map layerNamed:@"walls"]; //    update [self getSurroundingTilesAtPosition:player.position forLayer:walls]; 

発射! しかし、残念ながら、ゲームはクラッシュします。 コンソールに次のように表示されます。

画像

まず、セルの位置とGID値に関する情報を取得します(ただし、上部に空の地形があるため、ほとんどはゼロです)。
最終的に、エラー「TMXLayer:無効な位置」ですべてがクラッシュします。 これは、マップの端の外側の位置がtileGIDat:メソッドに渡されるときに発生します。
少し後でこのエラーを回避しますが、最初に、既存の衝突の定義を変更します。

コアラの特権を取り戻します


その瞬間まで、コアラ自体がその位置を更新していました。 しかし今、私たちは彼女からこの特権を取っています。

画像

コアラが独立してその位置を更新すると、最終的には狂ったようにジャンプし始めます! しかし、私たちはこれを望んでいません。
そのため、Koalaでは、GameLevelLayerと対話する追加の変数desiredPositionが必要です。
Koalaクラスは、その次の位置を独立して計算する必要があります。 ただし、GameLevelLayerは、妥当性を確認した後にのみ、コアラを目的の位置に移動する必要があります。 同じことが衝突検出サイクルにも当てはまります。すべてのセルの衝突がチェックされる前に、実際のスプライトを更新することは望ましくありません。
いくつかの変更が必要です。 まず、以下をPlayer.hに追加します

 @property (nonatomic, assign) CGPoint desiredPosition; 

Player.mに追加されたものを合成します

 @synthesize desiredPosition = _desiredPosition; 

次に、 Player.mcollisionBoundingBoxメソッドを次のように変更します。

 - (CGRect)collisionBoundingBox { CGRect collisionBox = CGRectInset(self.boundingBox, 3, 0); CGPoint diff = ccpSub(self.desiredPosition, self.position); CGRect returnBoundingBox = CGRectOffset(collisionBox, diff.x, diff.y); return returnBoundingBox; } 

このコードは、GameLevelLayerが衝突を判断するために使用する望ましい位置に基づいてフレームを計算します。
注:コリジョンフレームの計算にはさまざまな方法があります。 CCNodeクラスに既にあるものと同様のコードを書くことができますが、現在のメソッドは、いくつかの非自明性にもかかわらず、はるかに単純です。
次に、現在の位置ではなくdesiredPositionを更新するように、updateメソッドに次の変更を加えます。

 //  'self.position = ccpAdd(self.position, stepVelocity);' : self.desiredPosition = ccpAdd(self.position, stepVelocity); 

衝突を始めましょう!


真剣な成果の時が来ました。 それをすべてまとめるつもりです。 GameLevelLayer.mに次のメソッドを追加します。

私をクリック
 - (void)checkForAndResolveCollisions:(Player *)p { NSArray *tiles = [self getSurroundingTilesAtPosition:p.position forLayer:walls ]; //1 for (NSDictionary *dic in tiles) { CGRect pRect = [p collisionBoundingBox]; //2 int gid = [[dic objectForKey:@"gid"] intValue]; //3 if (gid) { CGRect tileRect = CGRectMake([[dic objectForKey:@"x"] floatValue], [[dic objectForKey:@"y"] floatValue], map.tileSize.width, map.tileSize.height); //4 if (CGRectIntersectsRect(pRect, tileRect)) { CGRect intersection = CGRectIntersection(pRect, tileRect); //5 int tileIndx = [tiles indexOfObject:dic]; //6 if (tileIndx == 0) { //    p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + intersection.size.height); } else if (tileIndx == 1) { //    p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y - intersection.size.height); } else if (tileIndx == 2) { //    p.desiredPosition = ccp(p.desiredPosition.x + intersection.size.width, p.desiredPosition.y); } else if (tileIndx == 3) { //    p.desiredPosition = ccp(p.desiredPosition.x - intersection.size.width, p.desiredPosition.y); } else { if (intersection.size.width > intersection.size.height) { //7 // ,     float intersectionHeight; if (tileIndx > 5) { intersectionHeight = intersection.size.height; } else { intersectionHeight = -intersection.size.height; } p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + intersection.size.height ); } else { // ,     float resolutionWidth; if (tileIndx == 6 || tileIndx == 4) { resolutionWidth = intersection.size.width; } else { resolutionWidth = -intersection.size.width; } p.desiredPosition = ccp(p.desiredPosition.x , p.desiredPosition.y + resolutionWidth); } } } } } p.position = p.desiredPosition; //7 } 

いいね!書いたばかりのコードを見てみましょう。

1.最初に、コアラを囲むセルのセットを取得します。次に、このセットの各セルを循環します。セルを通過するたびに、衝突をチェックします。衝突が発生した場合、KoalaのdesiredPositionを変更します。
2.ループの各ループ内で、最初に現在のKoalaフレームを取得します。衝突が検出されるたびに、desiredPosition変数はその値を、衝突が発生しなくなるポイントに変更します。
3.次のステップは、NSDictionaryに格納したGIDを取得することです。これはゼロの場合があります。 GIDがゼロの場合、現在のループは終了し、次のセルに進みます。
4。セルが新しい位置にある場合、CGRectを取得する必要があります。衝突する場合としない場合があります。このプロセスを次のコード行で実行し、tileRect変数に保存します。CGRect Koalaとセルができたので、衝突をチェックできます。
5.セルの衝突をチェックするには、CGRectIntersectsRectを実行します。衝突が発生した場合、CGRectIntersection()関数を使用して、CGRect交差点を記述するCGRectを取得します。

ジレンマについて詳しく見てみましょう...


かなり興味深いケースです。衝突を正しく識別する方法を理解する必要があります。
コアラを動かす最良の方法は、衝突とは反対の方向に動かすことだと思うかもしれません。一部の物理エンジンはこの原理で実際に動作しますが、より良い方法でソリューションを適用します。
考えてみましょう:重力はコアラを常にその下のセルに引き寄せており、これらの衝突は進行中です。あなたがコアラが前進するのを想像するなら、同時に、コアルはまだ重力によって引き下げられています。動きを反対方向に単純に変更することでこの問題を解決する場合、コアラは左上に移動しますが、何か他のものが必要です!
コアラは、これらのセルの上にとどまるために十分な距離を移動する必要がありますが、同じペースで前進し続けます。
画像
コアラが壁を転がり落ちた場合も同じ問題が発生します。プレイヤーがコアラを壁に押し付けると、コアラの動きの望ましい軌道が斜め下向きに壁に向けられます。方向を変えるだけで、コアラを壁から上に移動させます。コアラを壁の外に残したいが、それでも同じペースで下がってほしい!
画像
したがって、衝突を垂直に、いつ水平に処理するかを決定し、両方のアクションを相互に排他的に処理する必要があります。一部の物理エンジンは、最初に最初のイベントを常に処理し、次に2番目のイベントを処理します。しかし、コアラのセルの位置に基づいてより良い決定をしたいと考えています。したがって、たとえば、セルがコアラの真下にある場合、コリジョン識別子がコアラを上に戻すようにします。
しかし、セルがコアラの位置に対して斜めになっている場合はどうでしょうか?この場合、CGRect交差点を使用して、コアラをどのように移動するかを理解します。この長方形の幅が高さよりも大きい場合、Koalaを垂直に戻す必要があります。高さが幅よりも大きい場合、コアラは水平方向に移動する必要があります。
画像
Koalaの速度とフレームレートが特定のフレームワーク内にある限り、このプロセスは正しく機能します。少し後で、コアラが落下しすぎてセルをすり抜けるケースを回避する方法を学びます。
Koalaの移動方法(垂直または水平)を決定したら、交差点のCGRectサイズを使用して、Koalaを移動する量を決定します。それぞれ幅または高さを見て、この大きさをコアラの変位距離として使用します。
特定の順序でセルをチェックするのはなぜですか?常に隣接するセルを最初に処理し、次に斜めのセルを処理する必要があります。結局、Koalaの真下のセルの衝突をチェックしたい場合、変位ベクトルは垂直に向けられます。
画像
ただし、コアラがセルにわずかに触れると、CGRectの衝突が上方に広がる可能性があります。
右側の写真を見てください。コリジョン四角形はコリジョン全体のごく一部であるため、青色の領域が拡大されます。ただし、コアラの直下のセルに関する問題をすでに解決している場合は、コアラの右下のセルとの衝突を検出する必要がなくなります。したがって、新たな問題を回避します。

コードに戻りましょう!


巨大なメソッドcheckForAndResolveCollisionsに戻りましょう ...

6. 6番目のセクションでは、現在のセルのインデックスを取得できます。セルのインデックスを使用して、セルの位置を取得します。隣接するセルを個別に操作して、コアラをシフトし、衝突の長さまたは高さを減算または加算します。とても簡単です。ただし、対角セルになるとすぐに、前のセクションで説明したアルゴリズムを適用します。
7. 7番目のセクションでは、衝突領域が広いか細長いかを判断しますか?幅が広い場合-垂直に作業します。セルインデックスが5より大きい場合、コアラを上に移動します。領域が上方向に引き伸ばされている場合、水平方向に作業します。セルインデックスの順序についても、同様の原則に基づいて行動します。最後に、受け取ったポジションをコアレに割り当てます。

この方法は、衝突検出システムの頭脳です。

実際に利用可能な知識をすべて活用しましょう!方法に変更し、更新を(まだでGameLevelLayer:

 //  "[self getSurroundingTilesAtPosition:player.position forLayer:walls];" : [self checkForAndResolveCollisions:player]; 

getSurroundingTilesAtPosition:forLayerブロックを削除またはコメントアウトすることもできます。

  /* for (NSDictionary *d in gids) { NSLog(@"%@", d); } //8 */ 

発射!結果に驚いた?

画像

ポールはコアリオを止めますが、すぐに彼にinれます!なんで?
私たちが逃したものを推測できますか?覚えておいてください-ゲームのすべてのステップで、コアラの速度に重力が加わります。これは、コアラが絶えずスピードを落としていることを意味します。
セルのサイズになるまでコアラの軌跡に速度を絶えず追加します。1ステップでセル全体を移動すると、問題が発生します(最近、このことについて話しました)。
衝突を検出したらすぐに、出会ったセルの方向にコアラの速度をゼロにする必要があります!コアラは動きを止めたので、速度を考慮しなければなりません。
これを行わないと、ゲームの動作がかなり奇妙になります。前に述べたように、コアラがさらに高くジャンプできないように、コアラが地面に触れているかどうかを判断する方法が必要です。ここでこのボックスをチェックします。以下の行をcheckForAndResolveCollisionsに追加します

私をクリック
 - (void)checkForAndResolveCollisions:(Player *)p { NSArray *tiles = [self getSurroundingTilesAtPosition:p.position forLayer:walls ]; //1 p.onGround = NO; ////// for (NSDictionary *dic in tiles) { CGRect pRect = [p collisionBoundingBox]; //3 int gid = [[dic objectForKey:@"gid"] intValue]; //4 if (gid) { CGRect tileRect = CGRectMake([[dic objectForKey:@"x"] floatValue], [[dic objectForKey:@"y"] floatValue], map.tileSize.width, map.tileSize.height); //5 if (CGRectIntersectsRect(pRect, tileRect)) { CGRect intersection = CGRectIntersection(pRect, tileRect); int tileIndx = [tiles indexOfObject:dic]; if (tileIndx == 0) { //   p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + intersection.size.height); p.velocity = ccp(p.velocity.x, 0.0); ////// p.onGround = YES; ////// } else if (tileIndx == 1) { //   p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y - intersection.size.height); p.velocity = ccp(p.velocity.x, 0.0); ////// } else if (tileIndx == 2) { //  p.desiredPosition = ccp(p.desiredPosition.x + intersection.size.width, p.desiredPosition.y); } else if (tileIndx == 3) { //  p.desiredPosition = ccp(p.desiredPosition.x - intersection.size.width, p.desiredPosition.y); } else { if (intersection.size.width > intersection.size.height) { //tile is diagonal, but resolving collision vertially p.velocity = ccp(p.velocity.x, 0.0); ////// float resolutionHeight; if (tileIndx > 5) { resolutionHeight = intersection.size.height; p.onGround = YES; ////// } else { resolutionHeight = -intersection.size.height; } p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + resolutionHeight); } else { float resolutionWidth; if (tileIndx == 6 || tileIndx == 4) { resolutionWidth = intersection.size.width; } else { resolutionWidth = -intersection.size.width; } p.desiredPosition = ccp(p.desiredPosition.x + resolutionWidth, p.desiredPosition.y); } } } } } p.position = p.desiredPosition; //8 } 

コアラの下(隣接または対角線)にセルがあるたびに、p.onGround変数の値をYESに設定し、速度をゼロにします。また、コアラの下に隣接するセルがある場合、その速度をリセットします。これにより、現在のコアラの速度に正しく対応することができます。
ループの開始時にonGround変数をNOに設定します。この場合、onGroundは、その下のセルとのコアラの衝突を検出したときにのみYESになります。この機能を使用して、現時点でコアラがジャンプできるかどうかを判断できます。Player.h
のヘッダーファイルに次のコードを追加します(そして、エグゼクティブで必要なものをすべて合成します)

 @property (nonatomic, assign) BOOL onGround; 

そしてPlayer.mで

 @synthesize onGround = _onGround; 

発射! すべてが意図したとおりに機能しますか? はい! ああ、あの豪華な日! やった!

画像

次は?


おめでとうございます!物理エンジンが完全に完成しました!このテキストに到達したら、安reliefのため息をつくことができます。難しい部分でした-チュートリアルの2番目の部分では複雑なことは何もありません。
そして、ここに私たちが完了したプロジェクトソースがあります
では第二部私たちのコアラが実行されているとジャンプになります。また、床に散りばめられたブロックをコアラにとって危険なものにし、勝ち負けの画面を作成します。
プラットフォーマーの物理エンジンについてさらに詳しく知りたい場合は、次のリソースにアクセスすることをお勧めします
。The Sonic the Hedgehog Wikiは、Sonicが固体細胞と相互作用する方法の優れた説明です
おそらく最高高次ファンプラットフォームガイド


翻訳者のメモ

このチュートリアルに触発され、このチュートリアルを翻訳することにしました
私自身は現在iOSでプラットフォームゲームを書いており、raywenderlich.comのチュートリアルを積極的に使用しています皆さんにアドバイスします!
最初の部分の翻訳に比較的長い時間を費やした後、2番目の部分の翻訳について考えました。必要に応じてコメントを記入してください。需要があれば、翻訳します。
発見されたすべての不正確さとタイプミスについては、Habrahtaまたはここのコメントに記入してください。
チュートリアルに関するすべての質問に喜んでお答えします!

Source: https://habr.com/ru/post/J162747/


All Articles