サイトのトップへ戻る

libGDX ドキュメント 日本語訳

サイト内検索

基本的なグラフィック

このページでは、多くの人にとって最も楽しい作業である、実際に画面上に画像を表示する処理について説明します。 Let’s start with about the simplest project that we can.

今回は画面上に以下のSprite (このチュートリアルで作成しました)を表示します:

jet

注意すべき重要なことは、上記のグラフィックのサイズが 512x256 ということです。 OpenGL in general and LibGDX in specific,画像ファイのサイズを2の累乗にする必要があります。 つまり、横幅と高さのピクセルサイズは 2,4,8,16,32,64,128,256,512,1024,2048, など… となります。 このファイルを 以降の内容を読む前にこの画像ファイルをandroidプロジェクトの assets\data フォルダに追加するようにしてください。

それではコードの内容を見てみましょう:

package com.gamefromscratch.graphicsdemo;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class GraphicsDemo implements ApplicationListener {
    private SpriteBatch batch;
    private Texture texture;
    private Sprite sprite;
    
    @Override
    public void create() {        
        batch = new SpriteBatch();
        texture = new Texture(Gdx.files.internal("data/jet.png"));
        sprite = new Sprite(texture);
    }

    @Override
    public void dispose() {
        batch.dispose();
        texture.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

そしてこのコードを実行すると:

image

画像は開始点からの相対位置に表示されます。LibGDXの場合、画面に左下隅が開始点(0,0)となります。

このコードに関しては、前回のチュートリアルのHello Worldの例と比べてあまり変わった点はありません。 新しく加わった概念はTextureとSpriteだけです。 texture とは、基本的な OpenGL のtextureを表します。 Texture( およびその他同様のクラス )に関して覚えておくべき重要なことは、それらはDisposableインタフェースを実装しているということです。 つまり、使い終わった時にはdispose()メソッドを呼び出す必要があります。でないとメモリリークが発生します! Spriteはtextureのジオメトリと色の情報を保持しています。つまり、Sprite内に位置情報(X位置とY位置のような)が保持されているということです。 画像のパスを引数として渡すことで、Textureのコンストラクタを実行します。これは前回のチュートリアルでフォントにアクセスした時と同じやり方です。 新規作成したTextureを引数として渡すことで、Spriteのコンストラクタを実行します。 There are other ways of creating Sprites, that we will see shortly.  Hello Worldのサンプルと同じ様に、SpriteBatchを開始して、それからdraw()メソッドを使ってSpriteBatchにspriteを描写します。



Pixmapを使った動的Texture

Textureの元となる画像をファイルから持ってくる必要はありません。ここでは、 Pixmap クラスを使ってTextureの元画像を動的に作成します。

package com.gamefromscratch.graphicsdemo;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class GraphicsDemo implements ApplicationListener {
    private SpriteBatch batch;
    private Pixmap pixmap;
    private Texture texture;
    private Sprite sprite;
    
    @Override
    public void create() {        
        batch = new SpriteBatch();
        
        // A Pixmap is basically a raw image in memory as repesented by pixels
        // We create one 256 wide, 128 height using 8 bytes for Red, Green, Blue and Alpha channels
        pixmap = new Pixmap(256,128, Pixmap.Format.RGBA8888);
        
        //Fill it red
        pixmap.setColor(Color.RED);
        pixmap.fill();
        
        //Draw two lines forming an X
        pixmap.setColor(Color.BLACK);
        pixmap.drawLine(0, 0, pixmap.getWidth()-1, pixmap.getHeight()-1);
        pixmap.drawLine(0, pixmap.getHeight()-1, pixmap.getWidth()-1, 0);
        
        //Draw a circle about the middle
        pixmap.setColor(Color.YELLOW);
        pixmap.drawCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getHeight()/2 - 1);
        
        
        texture = new Texture(pixmap);
        
        //It's the textures responsibility now... get rid of the pixmap
        pixmap.dispose();
        
        sprite = new Sprite(texture);
    }

    @Override
    public void dispose() {
        batch.dispose();
        texture.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        sprite.setPosition(0, 0);        
        sprite.draw(batch);
        sprite.setPosition(Gdx.graphics.getWidth()/2, Gdx.graphics.getHeight()/2);
        sprite.draw(batch);
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

ここでも、コードは前回のサンプルと非常に似ています。最も大きな違いは、Texureの画像データをファイルから読み込むのではなく、Pixmap を使って動的に作成しているという点です。 簡単に言えば、pixmapはメモリ上にあるピクセルデータのグリッドとして考えることができます。 pixmapにはいくつかの画像処理機能が備わっており、それらの多くは上記コードで実際に使用しています。 描写用コードについては何をするものなのかコメントで説明しているので、ここで詳細は説明しません。 ですが現在のGPU駆動の世界において非常に重要なのは、ピクセル単位の操作を行うと非常に非常に遅くなるということです。 一般的には、ピクセル単位での操作はできる限り避けたほうが良いです。

このサンプルで唯一注目すべき点は、render()メソッド内の変更内容です。 同一のSpriteを使ってどうやってSprite batchへの描写を二回行うのか?という点に注目してください。 はい、この動作は全く問題なく、そうすることでパフォーマンスにおけるオーバーヘッダが最小限に抑えられます。 Spriteの setPosition を使って、 sprite の位置を二回設定しています。 既定では、(0,0)は画面の左下にあたります。 その他にここで新しく出てきたコードは、 Gdx.graphics.getWidth() メソッドと getHeight() メソッドの呼び出しです。 これらのメソッドはウィンドウのサイズを(または、HTML5の場合はCanvasのサイズを)戻り値として返します。 もちろん完成版のコードでは、これらの値をrenderループの度に毎回取得して渡すのではなく、ローカルに保存して使い回すと良いでしょう。



TextureAtlas

しばしば、spriteシートを扱いたくなることがあるでしょう。spriteシートとは、複数のspriteを合成して一つの画像にまとめたものです。 そのような機能がLibGdxには搭載されています。 まず最初に必要なものは、spriteシートに合成する画像のディレクトリです。これは以下のようなものです:

image

コマンドラインかターミナルウィンドウを開き、以下のコマンドを実行してください:

java -cp gdx.jar;extensions/gdx-tools/gdx-tools.jar com.badlogic.gdx.tools.imagepacker.TexturePacker2 c: mp c: mp spritesheet
tmp

なにやら難しいことをやっているように見えるかもしれませんが、 基本的にはgdx-tools jar内にあるTexturePacker2を実行しているだけです。 一つ目の引数は元ディレクトリで、二つ目の引数は出力先で最後の引数は使用するファイル名です。 必要なファイル拡張子は自動的に追加されます。 この処理を行うと .atlasファイル と .pngファイル の二つが作成されます。 atlas ファイルとは各spriteがspritesheet上でどのように配置されているかを記述したテキストファイルで、画像ファイルの名前(拡張子は取り除いたもの)がキーとして使われます。 以下のように:

spritesheet.atlas:

spritesheet.png
format: RGBA8888
filter: Nearest,Nearest
repeat: none
0001
  rotate: false
  xy: 1, 651
  size: 192, 128
  orig: 192, 128
  offset: 0, 0
  index: -1
0002
  rotate: false

一方、 sprite sheet 自体は以下のようになります:

spritesheet

spritepacker ツールは、サイズが2の累乗になるよう自動的に画像を詰めていきます。 今回はこのツールの持つ機能のほんの一部しか使っていません。 このツールは設定を行うことで、ビルドプロセスの一部として実行したり、コード上やEclipse内から実行したり、プログラム実行時に実行することさえできます。 設定できるオプションは豊富にあります。 TexturePackerの詳細についてはここで読むことができます。

では、実際にはtexture atlas をどのように使うのでしょうか? 使い方はとても簡単で、まず最初に作成したpngファイルとatlasファイルをassetsフォルダにコピーします。 以下にTextureAtlasを使うためのコードを記載します:

package com.gamefromscratch.graphicsdemo;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.utils.Timer;
import com.badlogic.gdx.utils.Timer.Task;

public class GraphicsDemo implements ApplicationListener {
    private SpriteBatch batch;
    private TextureAtlas textureAtlas;
    private Sprite sprite;
    private int currentFrame = 1;
    private String currentAtlasKey = new String("0001");
    
    @Override
    public void create() {        
        batch = new SpriteBatch();
        textureAtlas = new TextureAtlas(Gdx.files.internal("data/spritesheet.atlas"));
        AtlasRegion region = textureAtlas.findRegion("0001");
        sprite = new Sprite(region);
        sprite.setPosition(120, 100);
        sprite.scale(2.5f);
        Timer.schedule(new Task(){
                @Override
                public void run() {
                    currentFrame++;
                    if(currentFrame > 20)
                        currentFrame = 1;
                    
                    // ATTENTION! String.format() doesnt work under GWT for god knows why...
                    currentAtlasKey = String.format("%04d", currentFrame);
                    sprite.setRegion(textureAtlas.findRegion(currentAtlasKey));
                }
            }
            ,0,1/30.0f);
    }

    @Override
    public void dispose() {
        batch.dispose();
        textureAtlas.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

以下は、上記コードを HTML5 バージョンにしたものです:

ご覧のように、画像はずっと回転を続けています。 実際には上記コードの大部分はTextureAtlasを操作しているコードではなく、デモ関連のコードとなっています。 ですから追加されたコードの中で重要なのは以下部分だけです:

batch = new SpriteBatch();
textureAtlas = new TextureAtlas(Gdx.files.internal("data/spritesheet.atlas"));
AtlasRegion region = textureAtlas.findRegion("0001");
sprite = new Sprite(region);

Textureの時と同じ様に、TextureAtlasを読み込んでいます。TextureをSpriteに割り当てるのではなく、spritesheet内の個々のspriteの座標が記述されたAtlasRegionを使います。 findRegion()メソッドを呼び出してキーを引数に渡すことで、ファイル名ごとのregionを取得します このキーの値は元画像のファイル名を元にして設定されていることを忘れないでください。 TextureAtlas は dispose()を実行する必要があり、実行しないとメモリリークが発生します。

As you can see by the call:

sprite.setRegion(textureAtlas.findRegion(currentAtlasKey));

setRegion()を呼び出すことで、spriteが参照するsprite sheet内のregionを変更することができます。

コードの残りの部分は、単にspriteの位置を設定して2.5倍に拡大しているだけです。 それからTimer.schedule()を使ってタスクのスケジュールを設定します。 このタスクは1秒間に30回呼び出されます。 このタスクでは単にTextureAtlasで使用するキーを変更しているだけです。 今回の場合、各ファイルには0001.png, 0002.png, など…と名前を付けているので、キーの値は0001から0020の間にする必要があります。 それからこの値を使ってspriteが参照するregionを更新します。 その結果、1/30秒ごとにspriteは次のアニメーションフレームに遷移し、最後のフレームに達すると最初のフレームに戻ります。

EDIT: I should state for the record, 上記コードは TextureAtlas を使ってアニメーションを行うためのものではなく、単にアニメーションの仕組みを例示するためのものです。libGDX には専用のアニメーションクラスがあり、それについては後ほど説明します。

警告しておきますが、これをGWT環境で実行しようとすると以下のように表示されます:

image

これは何らかの理由で GWT コンパイラ ( これは LibGDX の範疇外です) が String.format() をサポートしていないためです。 このサンプルをブラウザ上で実行したい場合は、単に以下の部分を

currentAtlasKey = String.format("%04d", currentFrame);

以下のように書き換えるだけです:

String base = new String();
        
        if(currentFrame >= 10)
            base = "00";
        else
            base = "000";
        
currentAtlasKey = base + currentFrame;

これで HTML 対象環境でもちゃんと動作します。

次のページでは、LibGDXにおける入力制御について見ていきます。



GL 2を使うために LibGDX を設定する

マリオ・ゼックナー氏の LibGDX のTextureサイズは2の累乗の制限を受けないという発言が私の注目を引きました。 2の累乗制限は OpenGL ES 1の場合のものです。OpenGL ES2を使用している場合は問題なく動作します。 そうは言っても、2の累乗のサイズを使用するほうがOpenGLと基礎ハードウェアのパフォーマンスは良くなります。 GL2を使用したい場合は、Hello World チュートリアルで簡単に説明した設定処理時に、その設定を行います。 configuration の useGL20にtrueを設定して、application listenerに渡すだけです。 例えば、デスクトップ用プロジェクトでGL 2を使う設定を行うには以下のようにします。

public class Main {
    public static void main(String[] args) {
        LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
        cfg.title = "graphicsdemo";
        cfg.useGL20 = true;
        cfg.width = 480;
        cfg.height = 320;
        
        new LwjglApplication(new GraphicsDemo(), cfg);
    }
}

もちろん、サポートする全てのプラットフォームでこの設定をすることを忘れないでください。

EDIT:

12/18/2013 – atlas ファイルのインクルード方法が説明されていないとの指摘を受けました。その説明を行うと、このチュートリアルが難しくなってしまうのです。 そこで、このサンプルで使用するデータフォルダのアーカイブをまとめました。ここからダウンロードできます