iOSで無限のランナーを構築する方法:Cocos2D、自動化など

公開: 2022-03-11

iOSゲームの開発は、個人的な成長と経済的な成長の両方の観点から、豊かな経験になる可能性があります。 今年の初めに、Cocos2DベースのゲームであるBeeRaceをAppStoreにデプロイしました。 そのゲームプレイはシンプルです。プレイヤー(この場合はミツバチ)がポイントを集めて障害物を回避する無限のランナーです。 デモについては、こちらをご覧ください。

このチュートリアルでは、Cocos2Dから公開まで、iOS向けのゲーム開発の背後にあるプロセスについて説明します。 参考までに、短い目次を次に示します。

  • スプライトと物理オブジェクト
  • Cocos2Dの簡単な紹介
  • ストーリーボードでのCocos2Dの使用
  • ゲームプレイと(簡単な)プロジェクトの説明
  • ジョブを自動化します。 ツールを使用します。 かっこいい。
  • アプリ内課金
  • GameCenterを使用したマルチプレイヤーゲームプレイ
  • 改善の余地
  • 結論

スプライトと物理オブジェクト

ざらざらした詳細に入る前に、スプライトと物理オブジェクトの違いを理解しておくと役に立ちます。

エンドレスランナーゲームの画面に表示される任意のエンティティについて、そのエンティティのグラフィック表現はスプライトと呼ばれ、物理エンジンでのそのエンティティのポリゴン表現は物理オブジェクトと呼ばれます。

そのため、スプライトは画面上に描画され、対応する物理オブジェクトに支えられて、物理エンジンによって処理されます。 その設定はここで視覚化できます。ここでは、スプライトが画面に表示され、物理的な多角形の対応物が緑色で囲まれています。

iOSの無限ランナーゲームでは、スプライトと物理オブジェクトが共存します。

デフォルトでは、物理オブジェクトはそれぞれのスプライトに接続されていません。つまり、iOS開発者は、使用する物理エンジンと、スプライトとボディの接続方法を選択できます。 最も一般的な方法は、デフォルトのスプライトをサブクラス化し、それに具体的な物理ボディを追加することです。

それを念頭に置いて…

Cocos2DiOSゲーム開発に関する簡単なチュートリアル

Cocos2D-iphoneは、ハードウェアグラフィックスアクセラレーションにOpenGLを使用し、ChipmunkおよびBox2D物理エンジンをサポートするiOS用のオープンソースフレームワークです。

まず第一に、なぜそのようなフレームワークが必要なのですか? まず、フレームワークは、ゲーム開発でよく使用されるコンポーネントを実装します。 たとえば、Cocos2Dは、スプライト(特に、スプライトシート(なぜ?))をロードし、物理エンジンを起動または停止し、タイミングとアニメーションを適切に処理できます。 そして、これはすべて、広範囲にわたってレビューおよびテストされたコードを使用して行われます。なぜ、劣る可能性のあるコードを書き直すことに時間を費やすのでしょうか。

ただし、おそらく最も重要なのは、 Cocos2Dゲーム開発でグラフィックハードウェアアクセラレーションを使用することです。 このような加速がないと、適度な数のスプライトを使用するiOSの無限ランナーゲームは、パフォーマンスが著しく低下します。 より複雑なアプリケーションを作成しようとすると、おそらく画面に「バレットタイム」効果が見られるようになります。つまり、アニメーション化しようとするときに、各スプライトの複数のコピーが表示されます。

最後に、Cocos2Dはスプライトをキャッシュするため、メモリ使用量を最適化します。 したがって、複製されたスプライトは最小限の追加メモリを必要とします。これは明らかにゲームに役立ちます。

ストーリーボードでのCocos2Dの使用

私がCocos2Dを称賛した後、ストーリーボードの使用を提案するのは非論理的に思えるかもしれません。 Cocos2Dなどでオブジェクトを操作してみませんか? 正直なところ、静的ウィンドウの場合、XcodeのInterfaceBuilderとそのストーリーボードメカニズムを使用する方が便利なことがよくあります。

まず、マウスを使用して、エンドレスランナーゲームのすべてのグラフィック要素をドラッグして配置できます。 次に、StoryboardAPIは非常に便利です。 (はい、Cocos Builderについて知っています)。

これが私のストーリーボードの概要です。

無限に実行されるゲームの作成方法を学ぶには、優れたストーリーボードから始めてください。

ゲームのメインビューコントローラーには、いくつかのHUD要素が上にあるCocos2Dシーンが含まれています。

Cocos2Dチュートリアルの開始は、ビューコントローラーです。

白い背景に注意してください。これはCocos2Dシーンであり、実行時に必要なすべてのグラフィック要素をロードします。 その他のビュー(ライブインジケーター、タンポポ、ボタンなど)はすべて標準のCocoaビューであり、InterfaceBuilderを使用して画面に追加されます。

詳細については詳しく説明しません。興味がある場合は、GitHubで例を見つけることができます。

ゲームプレイと(簡単な)プロジェクトの説明

(もう少しモチベーションを上げるために、私のエンドレスランナーゲームについてもう少し詳しく説明したいと思います。技術的な議論に進みたい場合は、このセクションをスキップしてください。)

ライブゲームプレイ中、ミツバチは動かず、フィールド自体が実際に駆け寄り、さまざまな危険(クモや有毒な花)や特典(タンポポとその種)をもたらします。

Cocos2Dには、キャラクターを追跡するように設計されたカメラオブジェクトがあります。 実際には、ゲームの世界を含むCCLayerを操作することはそれほど複雑ではありませんでした。

コントロールはシンプルです。画面をタップすると蜂が上に移動し、もう一度タップすると蜂が下に移動します。

ワールドレイヤー自体には、実際には2つのサブレイヤーがあります。 ゲームが開始されると、最初のサブレイヤーが0からBUF_LENまで移入され、最初に表示されます。 2番目のサブレイヤーは、BUF_LENから2*BUF_LENに事前に入力されています。 ミツバチがBUF_LENに到達すると、最初のサブレイヤーがクリーンアップされ、2*BUF_LENから3*BUF_LENに即座に再入力され、2番目のサブレイヤーが表示されます。 このようにして、メモリリークを回避するための重要な部分である、廃止されたオブジェクトを保持せずに、レイヤーを交互に切り替えます。

私の無限のランナーゲームは、多層の世界で構成されています。

物理エンジンに関しては、2つの理由でChipmunkを使用しました。

  1. それは純粋なObjective-Cで書かれています。
  2. 私は以前にBox2Dを使用したことがあるので、2つを比較したいと思いました。

物理エンジンは、実際には衝突検出にのみ使用されていました。 時々、「なぜあなたはあなた自身の衝突検出を書かなかったのですか?」と尋ねられます。 実際には、それはあまり意味がありません。 物理エンジンは、まさにその目的のために設計されました。複雑な形状の物体間の衝突を検出し、そのプロセスを最適化することができます。 たとえば、物理エンジンは多くの場合、世界をセルに分割し、同じセルまたは隣接するセル内のボディに対してのみ衝突チェックを実行します。

ジョブを自動化します。 ツールを使用します。 かっこいい。

インディーズインフィニットランナーゲーム開発の重要な要素は、小さな問題に遭遇しないようにすることです。 時間はアプリを開発する際の重要なリソースであり、自動化は信じられないほど時間を節約できます。

しかし、場合によっては、自動化が完璧主義と期限の遵守の間の妥協点になることもあります。 この意味で、完璧主義は怒っている鳥の殺人者になる可能性があります。

たとえば、現在開発中の別のiOSゲームでは、特別なツール(GitHubで入手可能)を使用してレイアウトを作成するためのフレームワークを構築しました。 このフレームワークには制限があります(たとえば、シーン間の遷移が適切ではありません)が、これを使用すると、10分の1の時間でシーンを作成できます。

したがって、特別なスーパーツールを使用して独自のスーパーフレームワークを構築することはできませんが、これらの小さなタスクをできるだけ多く自動化することはできますし、そうする必要があります。

完璧主義は怒っている鳥の殺人者になる可能性があります。 時間はiOSゲーム開発の重要なリソースです。
つぶやき

この無限のランナーを構築する上で、自動化が再び重要でした。 たとえば、私のアーティストは、特別なDropboxフォルダーを介して高解像度のグラフィックを送信します。 時間を節約するために、App Storeで必要なさまざまなターゲット解像度のファイルセットを自動的に作成するスクリプトをいくつか作成し、-hdまたは@ 2xも追加しました(上記のスクリプトはImageMagickに基づいています)。

追加のツールに関しては、TexturePackerが非常に便利であることがわかりました。スプライトをスプライトシートにパックできるため、すべてのスプライトが1つのファイルから読み取られるため、アプリのメモリ消費量が少なくなり、読み込みが速くなります。 また、ほぼすべての可能なフレームワーク形式でテクスチャをエクスポートできます。 (TexturePackerは無料のツールではありませんが、価格に見合う価値があると思います。ShoeBoxなどの無料の代替ツールもチェックできます。)

ゲームの物理学に関連する主な問題は、各スプライトに適したポリゴンを作成することです。 言い換えれば、不明瞭な形の蜂や花の多角形の表現を作成します。 手作業でこれを実行しようとさえしないでください。常に多くの特殊なアプリケーションを使用してください。 Inkspaceを使用してベクターマスクを作成し、それをゲームにインポートするなど、非常にエキゾチックなものもあります。

私自身の無限のランナーゲーム開発のために、このプロセスを自動化するツールを作成しました。これをAndengineVertexHelperと呼びます。 名前が示すように、最初はAndengineフレームワーク用に設計されましたが、最近では多くの形式で適切に機能します。

この場合、plistパターンを使用する必要があります。

 <real>%.5f</real><real>%.5f</real>

次に、オブジェクトの説明を含むplistファイルを作成します。

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>jet_ant</key> <dict> <key>vertices</key> <array> <real>-0.18262</real><real>0.08277</real> <real>-0.14786</real><real>-0.22326</real> <real>0.20242</real><real>-0.55282</real> <real>0.47047</real><real>0.41234</real> <real>0.03823</real><real>0.41234</real> </array> </dict> </dict> </plist>

そしてオブジェクトローダー:

 - (void)createBodyAtLocation:(CGPoint)location{ float mass = 1.0; body = cpBodyNew(mass, cpMomentForBox(mass, self.sprite.contentSize.width*self.sprite.scale, self.sprite.contentSize.height*self.sprite.scale)); body->p = location; cpSpaceAddBody(space, body); NSString *path =[[NSBundle mainBundle] pathForResource:@"obj _descriptions" ofType:@"plist"]; // <- load plist NSDictionary *objConfigs = [[[NSDictionary alloc] initWithContentsOfFile:path] autorelease]; NSArray *vertices = [[objConfigs objectForKey:namePrefix] objectForKey:@"vertices"]; shape = [ChipmunkUtil polyShapeWithVertArray:vertices withBody:body width:self.sprite.contentSize.width height:self.sprite.contentSize.height]; shape->e = 0.7; shape->u = 1.0; shape->collision_type = OBJ_COLLISION_TYPE; cpSpaceAddShape(space, shape); }

スプライトが物理的なボディにどのように対応するかをテストするには、ここを参照してください。

はるかに良いですよね?

要約すると、可能な場合は常に自動化します。 単純なスクリプトでも、時間を大幅に節約できます。 そして重要なことに、その時間はマウスクリックの代わりにプログラミングに使用できます。 (さらなる動機付けのために、ここにトークンXKCDがあります。)

アプリ内課金

ゲームで収集されたブローボールはアプリ内通貨として機能し、ユーザーが蜂の新しいスキンを購入できるようにします。 ただし、この通貨はリアルマネーで購入することもできます。 アプリ内課金に関して注意すべき重要な点は、購入の有効性についてサーバー側のチェックを実行する必要があるかどうかです。 購入可能な商品はすべてゲームプレイの点で基本的に同じであるため(蜂の外観を変更するだけ)、購入の有効性についてサーバーチェックを実行する必要はありません。 ただし、多くの場合、必ずそうする必要があります。

詳細については、RayWenderlichに完璧なアプリ内課金チュートリアルがあります。

GameCenterを使用したマルチプレイヤーゲームプレイ

モバイルゲームでは、社交はFacebookの「いいね」ボタンを追加したりリーダーボードを設定したりするだけではありません。 ゲームをよりエキサイティングにするために、マルチプレイヤーバージョンを実装しました。

それはどのように機能しますか? まず、iOSGameCenterのリアルタイムのマッチメイキングを使用して2人のプレーヤーが接続されます。 プレイヤーは実際に同じ無限のランナーゲームをプレイしているので、ゲームオブジェクトのセットは1つだけで十分です。 つまり、1人のプレーヤーのインスタンスがオブジェクトを生成している必要があり、他のプレイのインスタンスがそれらを読み取ります。 つまり、両方のプレーヤーのデバイスがゲームオブジェクトを生成している場合、エクスペリエンスを同期させるのは困難です。

そのことを念頭に置いて、接続が確立された後、両方のプレーヤーはお互いに乱数を送信します。 数字の大きいプレイヤーが「サーバー」として機能し、ゲームオブジェクトを作成します。

分割された世界世代の議論を覚えていますか? 2つのサブレイヤーがありました。1つは0からBUF_LENまで、もう1つはBUF_LENから2 * BUF_LENまでですか? このアーキテクチャは誤って使用されたわけではありません。遅延したネットワーク上でスムーズなグラフィックスを提供する必要がありました。 オブジェクトの一部が生成されると、それはplistにパックされ、他のプレーヤーに送信されます。 バッファは、ネットワーク遅延があっても2番目のプレーヤーがプレイできるように十分な大きさです。 両方のプレーヤーは、0.5秒の間隔でお互いに現在の位置を送信し、またすぐに上下の動きを送信します。 エクスペリエンスをスムーズにするために、位置と速度はスムーズなアニメーションで0.5秒ごとに修正されるため、実際には、他のプレーヤーが徐々に動いたり加速したりしているように見えます。

マルチプレイヤーのエンドレスランニングゲームプレイに関しては、確かにさらに多くの考慮事項がありますが、これにより、関連する課題の種類を理解できるようになることを願っています。

改善の余地

ゲームは決して終わらない。 確かに、私が自分自身を改善したいいくつかの分野があります。

  1. コントロールの問題:タッピングは、スライドを好むプレーヤーにとっては直感的でないジェスチャーであることがよくあります。
  2. ワールドレイヤーは、CCMoveByアクションを使用して移動されます。 CCMoveByアクションがCCRepeatForeverで循環されたため、ワールドレイヤーの速度が一定の場合、これは問題ありませんでした。

     -(void) infiniteMove{ id actionBy = [CCMoveBy actionWithDuration: BUFFER_DURATION position: ccp(-BUFFER_LENGTH, 0)]; id actionCallFunc = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)]; id actionSequence = [CCSequence actions: actionBy, actionCallFunc, nil]; id repeateForever = [CCRepeatForever actionWithAction:actionSequence]; [self.bufferContainer runAction:repeateForever]; }

    しかし後で、ゲームが進むにつれてゲームを難しくするために、ワールド速度の増加を追加しました。

     -(void) infiniteMoveWithAccel { float duration = BUFFER_DURATION-BUFFER_ACCEL*self.lastBufferNumber; duration = max(duration, MIN_BUFFER_DURATION); id actionBy = [CCMoveBy actionWithDuration: duration position: ccp(-BUFFER_LENGTH, 0)]; id restartMove = [CCCallFunc actionWithTarget:self selector:@selector(infiniteMoveWithAccel)]; id fillBuffer = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)]; id actionSequence = [CCSequence actions: actionBy, restartMove, fillBuffer, nil]; [self.bufferContainer runAction:actionSequence]; }

    この変更により、アクションを再開するたびにアニメーションがボロボロになりました。 私は問題を修正しようとしましたが、役に立ちませんでした。 ただし、ベータテスターは動作に気づかなかったため、修正を延期しました。

  3. 一方では、Game Centerを使用したり、独自のゲームサーバーを実行したりするときに、マルチプレイヤーの独自の認証を作成する必要はありません。 一方で、ボットを作成することができなくなったので、変更したいと思います。

結論

独自のインディー無限ランナーゲームを作成することは素晴らしい経験になる可能性があります。 そして、プロセスの公開ステップに到達すると、自分の作品を公開するときに素晴らしい気分になる可能性があります。

レビュープロセスは、数日から数週間の範囲である可能性があります。 詳細については、クラウドソーシングされたデータを使用して現在のレビュー時間を推定する便利なサイトがここにあります。

さらに、AppAnnieを使用してApp Store内のすべてのアプリケーションに関するさまざまな情報を調べることをお勧めします。また、FlurryAnalyticsなどの分析サービスに登録することも役立ちます。

そして、このゲームに興味をそそられた場合は、ストアでBeeRaceをチェックしてください。