libGDXのViewport設定の謎

libGDXで画面のサイズを決めるときどうしてますか？
Android向けに作る際は、機種によって画面の縦横比がまちまちです。
ViewPortをうまく設定して縦横比を維持したまま論理座標を画面にあわせる方法の説明です。
![2014-04-20 05.07.47.png](https://qiita-image-store.s3.amazonaws.com/0/42140/16d2c7e2-0a10-9bc8-ef1e-a08e70995781.png)


#良くない例

論理座標にあわせてCameraの設定をする場合、以下の様な感じにすると実際の画面の縦横比に影響されて、縦長or横長になってしまいます。

```java
// 良くない例：画面いっぱいになってしまうので実際の画面の縦横比に影響されて、縦長or横長になってしまう
camera = new OrthographicCamera();
camera.setToOrtho(false, 640, 400);
batch.setProjectionMatrix(camera.combined);
```

# 公式wikiでのやりかた
これを回避するために、実際の画面サイズをもとに計算して・・・となると面倒なのでViewPortsクラスを使いましょう。
公式のWikiでは [ViewPorts](https://github.com/libgdx/libgdx/wiki/Viewports) のページで解説されています。


縦横比を維持したまま任意の論理座標に設定したいときは、こんな感じかなー。

```java
camera = new PerspectiveCamera();
viewport = new FitViewport(camera, 800, 480);
batch.setProjectionMatrix(camera.combined);
```
これで800x480の座標系になるはず。
左下が原点(x=0,y=0)です。
実際の画面の縦横比との差異は、上下または左右に隙間ができます。


##トラブルシューティング：ViewPorts関連のクラスがimport出来ない

gdx-setup-ui.jar で作ったプロジェクトだと、ViewPorts関連のクラスがimport出来ないみたい。
新しいgdx-setup.jarでプロジェクトをつくろう。
ダウンロードはここから。https://github.com/libgdx/libgdx/wiki/Project-Setup-Gradle
この場合、プロジェクトはGradleプロジェクトになるので、Eclipseで作ってる場合はプラグインをインストールしよう。
ここをよんでね。https://github.com/libgdx/libgdx/wiki/Gradle-and-Eclipse
※ Eclipse上でヘルプ→新規ソフトウェアのインストールで「http://dist.springsource.com/release/TOOLS/gradle」

生成されたプロジェクトをインポートするときは、ファイル→インポート→Gradle→Gradle Project です。
※ 一般→既存のプロジェクトをワークスペースへ  ではないので注意！

インポートするといろいろダウンロードはじまって時間かかるけどまってね。


#Stageクラスを利用する方法
公式Wikiの方法では、CameraとViewportのインスタンスを保持していないといけないので、Stageクラスのみをつかう方法のご紹介です。
※これがベストかはわからにけど

## サンプルコード

```java
package com.dokokano.gdxviewporttest1;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.Scaling;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.ScalingViewport;
import com.badlogic.gdx.utils.viewport.ScreenViewport;

public class GdxViewportTest extends ApplicationAdapter {

	static final int LOGICAL_WIDTH = 640;		// ゲーム内の論理座標 幅
	static final int LOGICAL_HEIGHT = 480;	// ゲーム内の論理座標 高さ

	Stage stage;	// ViewportとCameraの管理をさせる

	SpriteBatch batch;
	private ShapeRenderer renderer;

	Texture img;
	BitmapFont font;

	@Override
	public void create () {
		renderer = new ShapeRenderer();
		batch = new SpriteBatch();
		img = new Texture("badlogic.jpg");	// 256x256 px
		font = new BitmapFont();

		// Initialite Stage & View port
		stage = new Stage(new FitViewport(LOGICAL_WIDTH,LOGICAL_HEIGHT));
//		stage = new Stage(new ExtendViewport(LOGICAL_WIDTH,LOGICAL_HEIGHT));
//		stage = new Stage(new ScalingViewport(Scaling.fill ,  LOGICAL_WIDTH,LOGICAL_HEIGHT));
//		stage = new Stage(new ScreenViewport());

		Matrix4 cameraMatrix = stage.getViewport().getCamera().combined;
		batch.setProjectionMatrix(cameraMatrix);
		renderer.setProjectionMatrix(cameraMatrix);
	}

	@Override
	public void render () {

		// 背景塗りつぶし（全体を黒）
		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);


		// 論理表示領域を青で塗りつぶし
	    renderer.begin(ShapeType.Filled);
    	renderer.setColor(0,0,0.5f,1);
	    renderer.rect(0, 0,LOGICAL_WIDTH,LOGICAL_HEIGHT);
	    renderer.end();

		batch.begin();
		// ビットマップ描画(0,0)-(256,256)
		batch.setColor(1,1,1,0.5f);
		batch.draw(img, 0, 0);
		// 100x100グリッドにあわせて座標の数値描画
	    for ( int x=-1000; x<=1000; x+=100) {
		    for ( int y=-1000; y<1000; y+=100) {
			    font.setScale(1f);
			    font.setColor(Color.WHITE);
			    font.draw(batch, "("+x + ","+y +")" , x, y);
		    }
	    }
		batch.end();

		renderer.begin(ShapeType.Line);
		// 原点からの100pxごとに四角形描画(緑）
	    for ( int i=-1000; i<=1000; i+=100 ) {
	    	renderer.setColor(Color.GREEN);
		    renderer.rect(0, 0, i	, i);
	    }
	    // 100x100グリッドを描画（白いクロス）
	    for ( int x=-1000; x<=1000; x+=100) {
		    for ( int y=-1000; y<1000; y+=100) {
		    	renderer.setColor(Color.WHITE);
			    renderer.x(x, y, 10);
		    }
	    }
	    renderer.end();
	}

	@Override
	public void resize(int width, int height) {
		stage.getViewport().update(width, height,true);
	}


}
```

## サンプルコードの解説
ポイントは
Create()での

```java
stage = new Stage(new FitViewport(LOGICAL_WIDTH,LOGICAL_HEIGHT));
Matrix4 cameraMatrix = stage.getViewport().getCamera().combined;
batch.setProjectionMatrix(cameraMatrix);
renderer.setProjectionMatrix(cameraMatrix);
```
Update()での

```java
stage.getViewport().update(width, height,true);
```
です。

ViewPortとしてFitViewportクラスを使ってます。
これは、縦横比を維持したまま画面に収まるようにViewPortを設定してくれます。
LOGICAL_WIDTH,LOGICAL_HEIGHTはゲーム内で使う論理座標のサイズです。
これはゲームで使いやすいように自由にきめてください。
座標の原点は左下です。
X座標、Y座標ともに、右上にむかって＋になります。
ホントは、左上を原点にしたいのですが・・・まあ、余計な抵抗すると他ではまりそうなのでこれは受け入れましょう。

batchとrendererにたいしては、Stageから取得したViewportのCameraからMatrixを設定しています。


実際にAndroid機で表示した際のスクリーンショットは以下のとおりです。
LOGICAL_WIDTH,LOGICAL_HEIGHTを変えて表示しています。
※ この機種の実際の画面解像度は1280x720です。

### LOGICAL_WIDTH x LOGICAL_HEIGHT = 500x300
![2014-04-20 05.07.02.png](https://qiita-image-store.s3.amazonaws.com/0/42140/2e50e727-e87e-cab0-4846-fb9dce4f2d39.png)
だいたいそのまま収まっています。
（若干左右に隙間）

### LOGICAL_WIDTH x LOGICAL_HEIGHT = 400x300
![2014-04-20 05.07.47.png](https://qiita-image-store.s3.amazonaws.com/0/42140/16d2c7e2-0a10-9bc8-ef1e-a08e70995781.png)
左右に隙間ができて、縦横比が維持されていることがわかります。


### LOGICAL_WIDTH x LOGICAL_HEIGHT = 600x300
![2014-04-20 05.08.41.png](https://qiita-image-store.s3.amazonaws.com/0/42140/fd36b24e-0be8-eb81-2704-8bf8bb3076a0.png)
上下に隙間ができて、縦横比が維持されていることがわかります。


基本的これでViewPortの設定はOKです。


# ほかのViewPortの種類

## 画面に隙間が出来ないように設定する。
この場合縦横比が合わない分は、上下または左右にはみだします

```java
stage = new Stage(new ScalingViewport(Scaling.fill ,  LOGICAL_WIDTH,LOGICAL_HEIGHT));
```

LOGICAL_WIDTH x LOGICAL_HEIGHT = 400x300
![2014-04-20 06.33.46.png](https://qiita-image-store.s3.amazonaws.com/0/42140/163b2763-545f-98a1-f3d2-30a85284e404.png)
上下にはみ出ています。



## 画面サイズにあわせてViewPortを拡大
画面サイズが大きいぶんには論理サイズ自体が拡大されます。
最低限論理サイズ分の座標は確保されます。
広くなった分を可変サイズのオブジェクトで利用するには便利かな？

```java
stage = new Stage(new ExtendViewport(LOGICAL_WIDTH,LOGICAL_HEIGHT));
```
LOGICAL_WIDTH x LOGICAL_HEIGHT = 400x300
![2014-04-20 06.31.26.png](https://qiita-image-store.s3.amazonaws.com/0/42140/2ecba089-6aab-c095-3a60-41e84f38774e.png)
画面の左右が余るので、右側に拡大されています。500x300くらいになっています。


## 画面の実解像度と1対1にする
座標系が物理解像度と一緒になります。
おすすめしません。

```java
stage = new Stage(new ScreenViewport());
```
LOGICAL_WIDTH x LOGICAL_HEIGHT = 400x300
![2014-04-20 06.35.50.png](https://qiita-image-store.s3.amazonaws.com/0/42140/d4a8e222-8ad7-a0bc-7d3e-4e8926081f4e.png)
論理座標とは関係なく物理座標で描画されています。


# タッチされた座標の変換
タッチされた座標は物理座標（原点が左上）なので、論理座標に変換したいですね。
CameraクラスのunprojectメソッドでViewPortの論理座標に変換できます。
以下のサンプルではタッチされていた場合、その座標を中心に赤い円を描画しています。

```java
        if (Gdx.input.isTouched()) {
            Vector3 touchPos = new Vector3();
            touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
            stage.getViewport().getCamera().unproject(touchPos);

    		renderer.begin(ShapeType.Filled);
	    	renderer.setColor(Color.RED);
	    	renderer.circle(touchPos.x, touchPos.y, 50);
    	    renderer.end();
        }
```

# Desktop版
それはそうと同じソースがWindowsでもそのまま動くのはイイですね！
![スクリーンショット 2014-04-20 06.38.33.png](https://qiita-image-store.s3.amazonaws.com/0/42140/48790197-23f2-7ce8-3220-6cd4c2d04e45.png)
Windowsで動かす場合は、ウィンドウサイズを任意で変えられますがから、ViwePort指定は同様に重要です。




# まだ試行錯誤中です
この方法はあってるのかどうか。
ご意見ください。