これは、LibGdx を使ったゲーム開発連載の第二回です。この第二回を始める前に 第一回 を読むようにしてください。
以降の記事には多くの内容が記載されているので、分かりやすいように噛み砕いて説明するよう努めます。
既に基本的なゲーム内世界と矢印キーを使ったり画面をタッチした時に移動するボブが実装されていると思うので、それを前提に説明を始めます。
それでは、キャラクターが移動する時にアニメーションをして動きをリアルにしましょう。
ボブのアニメーションを行うには、スプライトアニメーションと呼ばれるシンプルな技術を使います。
このアニメーションでは、連続した画像を設定した間隔で表示して動いているように見せます。
以下の連続画像を使って走るアニメーションを作ります。
Star Guardゲームをたくさんプレイしてそのランニング動作を分析し、 Gimp を使ってこの走るキャラクターを作成しました。
アニメーションを作成するのはとても簡単です。
各フレームを一定時間表示し、次の画像に切り替えます。最後の画像まで表示したらまた最初の画像に戻ります。これを ループ処理と呼びます。
1フレームあたりの表示時間であるframe durationを決める必要があります。
仮に60 FPSでゲームを描写するとして、これは 1 / 60 = 0.016秒ごとにフレームを描写することを意味します。
1回分の足踏みをアニメーションさせるのに5フレームを使っています。
典型的なアスリートの ケイデンス を 180とすれば、
ランニングをリアルに見せられるフレーム表示時間を算出できます。
画像をゲームに追加する前に、最適化しておきます。
現在このプロジェクトのpngファイルは、 star-assault-android
プロジェクトの assets/images
ディレクトリ配下に別けられて入っています。
今のところはblock.png
とbob_01.png
を使用しています。
さらに追加の画像、すなわち bob_02 - 06.png
があります。これらの画像でアニメーションシーケンスを作ります。
libgdx は内部で OpenGL を使用しているため、フレームワークにTextureとして大量の画像を渡して動作させるのは最適ではありません。
我々がするのは、いわゆる Texture Atlas
を作成することです。
texture atlas は内部に全ての画像が納まる大きさを持った一つの画像で、各画像の名前、atlas上での位置とサイズを表す記述子を持っています。
ここの画像はatlas上では regionsと呼ばれます。
画像が多い場合は、atlasは複数の pagesを持つことができます。
各ページはメモリ上に一つの画像として読み込まれ、regionsは個別の画像として使用されます。
この技術については、アプリケーションをより最適化し、読み込みをより速くし、実行がよりスムーズになる、ということだけ知っていれば十分です。
Libgdx には、こうしたatlasを作成するためのTexturePacker2という名前のユーティリティがあります。 これはコマンドライン上から実行するか、プラグラム上から使用することができます。これをJavaから実行するには、以下のプログラムを使用してください:
package net.obviam.starassault.utils; import com.badlogic.gdx.tools.imagepacker.TexturePacker2; public class TextureSetup { public static void main(String[] args) { TexturePacker2.process("/path-to-star-guard-assets-images/", "path-to-star-guard-assets-images", "textures.pack"); } }
gdx-tools.jar
を libs
ディレクトリに追加しているか確認してください。
process
メソッドに設定する引数は、素材の置かれているディレクトリを参照するパスに変更してください。
注意: TexturePacker2 ではアンダースコア文字を区切り文字として使用しており、現時点ではアンダースコアを使う必要がないので、アンダースコア文字 “_”を含むファイル名は変更します。
アンダースコア文字を、ハイフンの“-”文字に書き換えてください。
ディレクトリ内の画像を処理すると二つのファイル: textures.png
と textures.pack
が作成されます。
texture atlas は以下画像のようになります。
これでアニメーションの形、1フレームあたりの表示時間、ゲーム素材の最適化ができたので、それらをゲームに追加しましょう。
まず最初に最も簡単な Bob.java
の修正を行います。
public class Bob { // ... omitted ... // float stateTime = 0; // ... omitted ... // public void update(float delta) { stateTime += delta; position.add(velocity.tmp().mul(delta)); } }
stateTime
という名前の属性を追加しました。これはボブが特定の状態下にどれくらいの時間いるのかを保持します。 We will be using it to provide the time spent by Bob in the game.
これはアニメーションがフレームを表示する時間を算出するのに重要です。今のところはあまり気にする必要はありません。本当に理解したい場合は、アニメーションの各フレームを状態として考えてください。ボブは state_frame_1, state_frame_2 などの状態に遷移していきます。これらの状態はそれぞれ 0.066 秒間維持されます。
その状態になって時間が 0.066 秒経過したら、ボブは次の状態へ遷移します。
animationクラスは、現在の状態ではどの画像を表示するのかを知っています。これは key frameとも呼ばれます。
WorldRenderer.java
の内容はほとんど変更します。以下コードに全ての変更内容を記述しています
public class WorldRenderer { // ... omitted ... // private static final float RUNNING_FRAME_DURATION = 0.06f; /** Textures **/ private TextureRegion bobIdleLeft; private TextureRegion bobIdleRight; private TextureRegion blockTexture; private TextureRegion bobFrame; /** Animations **/ private Animation walkLeftAnimation; private Animation walkRightAnimation; // ... omitted ... // private void loadTextures() { TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("images/textures/textures.pack")); bobIdleLeft = atlas.findRegion("bob-01"); bobIdleRight = new TextureRegion(bobIdleLeft); bobIdleRight.flip(true, false); blockTexture = atlas.findRegion("block"); TextureRegion[] walkLeftFrames = new TextureRegion[5]; for (int i = 0; i < 5; i++) { walkLeftFrames[i] = atlas.findRegion("bob-0" + (i + 2)); } walkLeftAnimation = new Animation(RUNNING_FRAME_DURATION, walkLeftFrames); TextureRegion[] walkRightFrames = new TextureRegion[5]; for (int i = 0; i < 5; i++) { walkRightFrames[i] = new TextureRegion(walkLeftFrames[i]); walkRightFrames[i].flip(true, false); } walkRightAnimation = new Animation(RUNNING_FRAME_DURATION, walkRightFrames); } private void drawBob() { Bob bob = world.getBob(); bobFrame = bob.isFacingLeft() ? bobIdleLeft : bobIdleRight; if(bob.getState().equals(State.WALKING)) { bobFrame = bob.isFacingLeft() ? walkLeftAnimation.getKeyFrame(bob.getStateTime(), true) : walkRightAnimation.getKeyFrame(bob.getStateTime(), true); } spriteBatch.draw(bobFrame, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY); } // ... omitted ... // }
#05 – ランニング/ウォーキングサイクルを表示するフレーム時間を制御するRUNNING_FRAME_DURATION 定数を宣言します。
#08 – #11 – ボブの異なる状態ごとのTextureRegion
。 bobFrame
は現在のサイクルで表示される region を保持します。
#14 – #15 – ボブが走る/歩く時のアニメーションで使用される二つの Animation
オブジェクト。
TextureAtlas
から画像を読み込む#19 – 新しい loadTextures()
メソッド
#20 – internal タイプのファイルからTextureAtlas を読み込む。これは TexturePacker2
で作成した .pack
ファイルです。
#21 – “bob-01″ (これは拡張子を省いたpngファイル名です – TexturePacker2を参照)という名前領域をbobIdleLeft
変数に割り当てます。
#22 – #23 – TextureRegion
を新規に作成して(bobIdleLeftをコピーしたコンストラクタを使用します。参照ではなくコピーをする必要があるので注意してください)それをX軸上で反転させているので、同じ画像ですが右向きで待機状態のBob になります。追加で画像を読み込む必要が無く、既存の画像からTextureRegionを作成できるので、この反転機能はとても便利です。
#24 – ブロックに対応する region を割り当てます
#25 – #28 – アニメションを作成するための TextureRegion
の配列を作成します。フレームは5つあり、それらの名前は: bob-02, bob-03, bob-04, bob-05
and bob-06
です。手間を省くため、ループを使ってこれらを作成します。
#29 – ここで、左移動状態のアニメーションを定義します。一つ目の引数はフレームの表示時間を秒単位で表したもの(0.06)で、二つ目の引数はアニメーションを作るフレームを表示順に並べた一覧です。
#31 – #38 – 右移動状態のアニメーションを作成します。これは左移動状態のアニメーションをコピーしていますが、各フレームを反転しています。
フレームのコピーを作成して、オリジナルの画像まで反転させないことが重要です。
#40 – 変更された drawBob()
メソッド。
#42 – ボブの向きに応じて、右向きか左向きどちらかのidleフレームをactive フレームに設定します。
#44 – ボブが歩いている状態の場合、ボブの現在の状態時間を基にしてウォーキング画像の中から対応するフレームを抽出し、それを画面に描写する用の bobFrameに割り当てます。
StarAssaultDesktop
アプリケーションを実行するとボブのアニメーション動作が見られます。以下のような動きになります:
このプロジェクトのソースコードはここ: https://github.com/obviam/star-assaultにあります。
part2
gitを使ってチェックアウトするには:
git clone -b part2 git@github.com:obviam/star-assault.git
また zip ファイルとしてダウンロードすることもできます。
ジャンプと動作改善について説明した、この連載の 次の記事のチェックアウトもできます。