サイトのトップへ戻る

libGDX ドキュメント 日本語訳

サイト内検索

Orthographic camera

このページではOrthographicCamera クラスとその使用方法について説明します。 このorthographic cameraは平行投影(垂直投影)を実装しているので2D環境のゲームでのみ使用されます。 オブジェクトをゲーム内世界のどこに配置しても、配置場所によってサイズが変わることはありません。



説明

このCamera クラスは、現実世界のカメラのようにとても簡単に動作します。以下のことが可能です。

  • カメラの移動と回転
  • ズームインとズームアウト
  • viewportの変更
  • ウィンドウ上の座標とゲーム内世界の座標を相互変換する

カメラを使用すると、マトリクスを手動で操作する必要なくゲーム内世界を移動できます。 投影やビューマトリックスに関する全ての操作は内部の実装で処理されるので、あなたが意識することありません。

以下の簡単なアプリでは、シンプルなOrthographicCamera を使って平坦な世界を移動するやり方を例示しています。

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;

public class OrthographicCameraExample implements ApplicationListener {

    static final int WORLD_WIDTH = 100;
    static final int WORLD_HEIGHT = 100;

    private OrthographicCamera cam;
    private SpriteBatch batch;

    private Sprite mapSprite;
    private float rotationSpeed;

    @Override
    public void create() {
        rotationSpeed = 0.5f;

        mapSprite = new Sprite(new Texture(Gdx.files.internal("sc_map.png")));
        mapSprite.setPosition(0, 0);
        mapSprite.setSize(WORLD_WIDTH, WORLD_HEIGHT);

        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();

        // Constructs a new OrthographicCamera, using the given viewport width and height
        // Height is multiplied by aspect ratio.
        cam = new OrthographicCamera(30, 30 * (h / w));

        cam.position.set(cam.viewportWidth / 2f, cam.viewportHeight / 2f, 0);
        cam.update();

        batch = new SpriteBatch();
    }

    @Override
    public void render() {
        handleInput();
        cam.update();
        batch.setProjectionMatrix(cam.combined);

        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.begin();
        mapSprite.draw(batch);
        batch.end();
    }

    private void handleInput() {
        if (Gdx.input.isKeyPressed(Input.Keys.A)) {
            cam.zoom += 0.02;
        }
        if (Gdx.input.isKeyPressed(Input.Keys.Q)) {
            cam.zoom -= 0.02;
        }
        if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
            cam.translate(-3, 0, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
            cam.translate(3, 0, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
            cam.translate(0, -3, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
            cam.translate(0, 3, 0);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.W)) {
            cam.rotate(-rotationSpeed, 0, 0, 1);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.E)) {
            cam.rotate(rotationSpeed, 0, 0, 1);
        }

        cam.zoom = MathUtils.clamp(cam.zoom, 0.1f, 100/cam.viewportWidth);

        float effectiveViewportWidth = cam.viewportWidth * cam.zoom;
        float effectiveViewportHeight = cam.viewportHeight * cam.zoom;

        cam.position.x = MathUtils.clamp(cam.position.x, effectiveViewportWidth / 2f, 100 - effectiveViewportWidth / 2f);
        cam.position.y = MathUtils.clamp(cam.position.y, effectiveViewportHeight / 2f, 100 - effectiveViewportHeight / 2f);
    }

    @Override
    public void resize(int width, int height) {
        cam.viewportWidth = 30f;
        cam.viewportHeight = 30f * height/width;
        cam.update();
    }

    @Override
    public void resume() {
    }

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

    @Override
    public void pause() {
    }

    public static void main(String[] args) {
        new LwjglApplication(new OrthographicCameraExample());
    }
}

画像ダウンロード: sc_map.png

上記は、orthographic camera を使ってゲーム内世界を移動するLibGDX アプリケーションです。 ゲーム内世界の大きさの単位は、自由に定義することができます。 今回の場合、ゲーム内世界のサイズは 100 x 100 単位とします。

    static final int WORLD_WIDTH = 100;
    static final int WORLD_HEIGHT = 100;

多くの人はゲーム内世界をピクセル単位で捉えるという間違いを犯します。こうした捉え方は避けたほうが良いです。 It leads to unnecessary multiplying and dividing by constants, having weird "Pixel per unit" ratios dotted around your code, poor understanding of the pipeline and it confuses you! 多くの問題は、あなたがピクセル単位で"考えること"を止めるだけで簡単に避けることができます。

しかし、これらユニットというのは一体何なのでしょうか?何をするものなのでしょうか? 作成したオブジェクトのサイズがどれくらいなのかを、どうやって知ることができるのでしょうか? 画面上にはどれくらい数のユニットが表示されるのでしょうか? We will get to that shortly! Stick tight.

    private OrthographicCamera cam;  #1
    private SpriteBatch batch;       #2

    private Sprite mapSprite;        #3
    private float rotationSpeed;     #4

#1 - ゲーム内世界での視点を制御する OrthographicCamera インスタンス。

#2 - ゲーム内世界を描写するに使用する SpriteBatch インスタンス

#3 - ゲーム内世界の地図を描写するのに使用するSprite

#4 - カメラを回転させる際の回転速度




    @Override
    public void create() {
        rotationSpeed = 0.5f;                                                    #1

        mapSprite = new Sprite(new Texture(Gdx.files.internal("sc_map.png")));   #2
        mapSprite.setPosition(0, 0);                                             #3
        mapSprite.setSize(WORLD_WIDTH, WORLD_HEIGHT);                            #4

        float w = Gdx.graphics.getWidth();                                       #5
        float h = Gdx.graphics.getHeight();                                      #6
        cam = new OrthographicCamera(30, 30 * (h / w));                          #7
        cam.position.set(cam.viewportWidth / 2f, cam.viewportHeight / 2f, 0);    #8
        cam.update();                                                            #9

        batch = new SpriteBatch();                                               #10
    }

このcreate メソッドはApplicationListenerのインスタンスを新規に作成した際に呼び出されます。このメソッド内で使用する変数を初期化します

#1 - 現在の回転速度を0.5 度に設定します。

#2 - ファイル:sc_map.pngを使った新規Texture から Spriteを作成します。 sc_map.pngここからダウンロードして assets/ ディレクトリ内に配置してください。

#3 - mapSpriteの位置座標を 0,0に設定します。 (Sprite の x,y 座標は既定で 0,0となっているため、これは必須ではありません。)

#4 - mapSpriteのサイズを横幅WORLD_WIDTH と高さ WORLD_HEIGHTに設定します。これでsprite は 100x100のサイズ、またはゲーム内世界と同じサイズになりました。

#5 - アプリケーションが表示されている現在のwidthの値を保持するローカル変数を作成します。(これはピクセル単位の値です)

#6 - アプリケーションが表示されている現在のheightの値を保持するローカル変数を作成します。(これはピクセル単位の値です)

#7 - OrthographicCameraを作成します。二つのパラメータで作成されるviewport の横幅と高さを設定します。 これらの値によって、ゲーム内世界の各軸ごとに、どれだけの広さが表示されるかが決まります。

今回の例では、viewport の横幅に 30 を使用し、viewport の高さに30 * (h / w) を使用しています。 横幅については、単純にX軸上で30unit が表示されるということです。viewport の高さには、 30にディスプレイのアスペクト比 をかけた値を使用します。 これはオブジェクトが正しい比率で表示されるように描写するためです。 アスペクト比を無視してviewport の横幅と高さを単純にどちらも30にするとしましょう。正方形のディスプレイを使用しない限り(そんなディスプレイほとんどありません)、例えば30x30サイズのオブジェクトを描写すると使用しているディスプレイと同じ形状に押しつぶされて表示されます。 どうして30x30のオブジェクトなのに正方形にならないのでしょうか? これは、我々がviewport の横幅を30、高さを30と想定しているためです。この条件では端末のアスペクト比と合致していません。

いくつかの例 - 

横幅100、高さ100の viewport  ('new OrthographicCamera(100, 100)')でカメラを作成して中央に正確に配置した場合、一度に マップ'全体'、ゲーム内世界 '全体'を見ることができます。

横幅100、高さ50の viewport  ('new OrthographicCamera(100, 50)')でカメラを作成した場合、一度にマップの'半分' を見ることができます。

横幅50、高さ50の viewport  ('new OrthographicCamera(50, 50)')でカメラを作成した場合、一度にマップの'1/4' を見ることができます。

#8 - cameraの初期位置をマップの左下に設定します。なお... cameraの位置というのは cameraの中心点にあたります。

ですから、camera の左下隅が実際に(0,0)座標に来るようにするために、cameraの位置をviewport の横幅半分、viewport の縦幅半分移動させる必要があります。

#9 - cameraを更新します! この処理では内部の全てのマトリックスを更新するので、cameraを操作した際は常にこれを実行するようにしてください。

#10 - SpriteBatch インスタンスを作成します。

これで設定ができました。それでは画面に何か描写してcameraを操作してみましょう。




@Override
    @Override
    public void render() {
        handleInput();                             #1
        cam.update();                              #2                             
        batch.setProjectionMatrix(cam.combined);   #3

        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);  #4

        batch.begin();                             #5
        mapSprite.draw(batch);                     #6
        batch.end();                               #7
    }

#1 - 押されたキーに基づいてカメラの位置の更新、ズーム、回転といった操作をします。

#2 - OrthographicCameraを更新します。handleInput() メソッド内でOrthographicCameraを操作したので、必ず update() メソッドを呼び出すのを忘れないでください。.

#3 - Cameraのビューとプロジェクションマトリックスを使ってSpriteBatch インスタンスを更新します。

#4 - 画面をクリアします (actually the colour buffer)。

#5 - SpriteBatchの処理を開始します

#6 - mapSpriteを描写します!

#7 - SpriteBatchの処理を終了します




handleInput() メソッド内で行われているcameraの制御処理についてより詳しく見てみましょう.

    private void handleInput() {
        if (Gdx.input.isKeyPressed(Input.Keys.A)) {
            cam.zoom += 0.02;
            //If the A Key is pressed, add 0.02 to the Camera's Zoom
        }
        if (Gdx.input.isKeyPressed(Input.Keys.Q)) {
            cam.zoom -= 0.02;
            //If the Q Key is pressed, subtract 0.02 from the Camera's Zoom
        }
        if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
            cam.translate(-3, 0, 0);
            //If the LEFT Key is pressed, translate the camera -3 units in the X-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
            cam.translate(3, 0, 0);
            //If the RIGHT Key is pressed, translate the camera 3 units in the X-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
            cam.translate(0, -3, 0);
            //If the DOWN Key is pressed, translate the camera -3 units in the Y-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
            cam.translate(0, 3, 0);
            //If the UP Key is pressed, translate the camera 3 units in the Y-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.W)) {
            cam.rotate(-rotationSpeed, 0, 0, 1);
            //If the W Key is pressed, rotate the camera by -rotationSpeed around the Z-Axis
        }
        if (Gdx.input.isKeyPressed(Input.Keys.E)) {
            cam.rotate(rotationSpeed, 0, 0, 1);
            //If the E Key is pressed, rotate the camera by rotationSpeed around the Z-Axis
        }

        cam.zoom = MathUtils.clamp(cam.zoom, 0.1f, 100/cam.viewportWidth);

        float effectiveViewportWidth = cam.viewportWidth * cam.zoom;
        float effectiveViewportHeight = cam.viewportHeight * cam.zoom;

        cam.position.x = MathUtils.clamp(cam.position.x, effectiveViewportWidth / 2f, 100 - effectiveViewportWidth / 2f);
        cam.position.y = MathUtils.clamp(cam.position.y, effectiveViewportHeight / 2f, 100 - effectiveViewportHeight / 2f);
    }

このメソッドではキーの入力チェックを行っており、特定のキーが押された場合はそれに対応した操作がcameraに対して行われるのが分かるでしょう。

最後の5行では、cameraがゲーム内世界からはみ出さないように維持する処理を司っています。

カメラのズーム値の増減によってゲーム内世界が反転してしまったり、大きく見えすぎてしまわないようにする必要があります。 これを行うために、 effectiveViewportWidth effectiveViewportHeight を算出します。 これらの値は viewportWidth / height * zoom の式で簡単に算出できます (this gives us what we can see in the world given the current zoom)。 カメラのズームする値を、必要な値の範囲内からはみ出さないように 留めることができます。 最小値には0.1f に設定して、カメラがズームインし過ぎないようにします。 最大値には100/cam.viewportWidthを設定して、ゲーム内世界の幅を越えた範囲が表示されないようにします。

最後の二行では、ゲーム内世界の範囲外へ移動できないようにする処理を司っています。それぞれの軸上にて、0未満もしくは100より大きい位置には移動できません。




アプリケーションのサイズを変更する際にはどうすれば良いのでしょうか? そうなった場合には、異なる解像度/アスペクト比を持つ端末を制御するための処理を実装しましょう。 I will include a few basic strategies to give you the basic idea.

この処理に少し高レベルのメソッドが必要な場合は、 viewports を使用します。-> Viewportsに関するwiki記事

以下のresize手法を使えば、あなたの端末の横幅ピクセルが何であろうとX軸上には常に30unitを表示させることができます。

    @Override
    public void resize(int width, int height) {
        cam.viewportWidth = 30f;                 // Viewport of 30 units!
        cam.viewportHeight = 30f * height/width; // Lets keep things in proportion.
        cam.update();
    }

以下の resize 方法では、表示されるゲーム内世界の範囲は端末の解像度に応じて多くなったり少なくなったりします。

    @Override
    public void resize(int width, int height) {
        cam.viewportWidth = width/32f;  //We will see width/32f units!
        cam.viewportHeight = cam.viewportWidth * height/width;
        cam.update();
    }

listenerを起動するメインアプリケーション部分は、簡単な LWJGL アプリケーションです。

    public static void main(String[] args) {
        new LwjglApplication(new OrthographicCameraExample());
    }



結果は以下のようなアプリケーションになります:

最も一般的な使い方をするのであれば以下のメソッドを使えば事足りるので、ほとんどの場合はcameraの内部にアクセスする必要はありません:

メソッド 説明
lookAt(float x, float y, float z) 指定された座標を向くようにcamera の方向を再計算します。 - 2Dの場合z軸は無視されます
translate(float x, float y, float z) 各軸上で指定された分だけ camera を移動します。 - OrthographicCameraの場合zの値は無視されるので注意してください
rotate(float angle, float axisX, float axisY, float axisZ) Rotates the direction and up vector of this camera by the given angle around the given axis. The direction and up vector will not be orthogonalized. 変更された角度は維持され、それまでの回転に angleの加算された分だけカメラは回転します。
update() Recalculates the projection and view matrix of the camera and the frustum planes