目次
概要
この記事は斜め上からの視点 Isometric (クォータービュー) の作り方について解説します。
なお Unity で実装する場合は、平行投影にしてカメラを斜め上にするだけで実装できるので、ここで紹介しているような対応は不要となります。
2D描画のみ可能な環境で「クォータービュー」を実現する方法となります。
マップチップ座標から、クォータービュー座標への変換
問題を簡単にするため、このような座標変換をすると考えます。
右回りに45度回転させる数式を以下に書いていきます。
- w = チップの幅
- h = チップの高さ
- x = マップチップ座標(X)
- y = マップチップ座標(Y)
- X = クォータービュー座標(X)
- Y = クォータービュー座標(Y)
とします。
■マップチップ座標からクォータービュー座標に変換する
マップチップ座標からクォータービュー座標に変換する場合は、
という変換式を使います。
となります。
■クォータービュー座標からマップチップ座標に変換する
クォータービュー座標からマップチップ座標に変換する場合は、
ちなみにマップチップ座標とは、マップエディタ内の座標のことです。
これは「8×8」のマップですが、鉛筆で赤く指しているところが左から6コ目、上から0コ目なので、マップ座標は(x,y)=(6,0)となります。
あと、そもそもマウスの判定をしないのであれば、「クォータービュー座標からマップチップ座標の変換」は不要ですね。
描画だけ考えれば、わりと簡単です。マウス座標・タッチ座標でユニットを選択する、とか考えると少し面倒な話になってきます。
Y方向を反転する必要がある
先ほどの計算式はY方向が上向きなので、Y方向を反転する必要があります。(スクリーン座標のY方向は下向きとなります)
座標オフセットの問題
座標のオフセット方法が少し特殊なので、少し注意する必要があります。
チップの開始座標
通常のトップビューであれば、左上のチップが開始座標となります。
ですが、クォータービューの場合、左中央のチップが開始座標となります。
なのでオフセットするY座標は、画面の中心あたりになります。
■チップの左上座標のオフセット
スクリーン上ではチップの中心座標は(x,y)=(w/2,h/2)だけずらす必要があります。
なのですが、クォータービュー座標は、先ほど「左中央が開始座標」と説明したとおり、チップ座標も(x,y)=(w/2,0)だけ戻してやればOKです。
ソースコードの簡単な説明
昔Flashで作ったものなので、もう動作はしないですが参考として添付しておきます。
SceneTitle.as
このクラスで変換処理やチップの描画をしています。
「drawChip関数」がマップ座標からクォータービュー座標への変換です
/**
* チップの描画 (クォータービュー座標に変換))
* @param id チップ番号
* @param i マップ座標(X)
* @param j マップ座標(Y)
*/
private function drawChip(id:int, i:int, j:int):void
{
// マップ座標 -> クォータービュー座標 に変換
// X = w(x + y)
// Y = h(x - y)
var x:Number = CHIP_OFSX * (i + j);
var y:Number = CHIP_OFSY * (i - j);
// 表示オフセットを反映
x = x + MAP_OFSX;
y = -y + MAP_OFSY; // Y方向は逆なので反転する
// スクロールを反映
if (m_CenterIdx >= 0)
{
x -= m_View.getX();
y += m_View.getY(); // Y方向は逆なので反転する
}
// チップの描画
var ox:Number = CHIP_WIDTH * (id%CHIP_COL);
var oy:Number = CHIP_HEIGHT * (Math.floor(id / CHIP_COL));
_g.drawFastEx("chip", x, y, ox, oy, CHIP_WIDTH, CHIP_HEIGHT);
}
「quarterToMap関数」でクォータービュー座標からマップ座標への変換をしています。
/**
* クォータービュー座標をマップ座標に変換
* @param x クォータービュー座標(X)
* @param y クォータービュー座標(Y)
* @return マップ座標インデックス
*/
public function quarterToMap(x:int, y:int):int
{
var px:Number = x;
var py:Number = y;
// カメラ座標を反映
px += m_View.getX();
py -= m_View.getY(); // Y方向は逆なので反転する
// マップ全体のオフセットを反映
px = (px - MAP_OFSX);
py = -(py - MAP_OFSY); // Y方向は逆なので反転する
// チップのオフセットをする(クォータービュー座標は「左中心」がデカルト座標の「左上」)
px += CHIP_WIDTH / 2;
// クォータービュー座標 -> マップ座標 へ変換
// x = X/2w + Y/2h
// y = X/2w - Y/2h
var i:int = Math.floor((px / CHIP_OFSX / 2) + (py / CHIP_OFSY / 2));
var j:int = Math.floor((px / CHIP_OFSX / 2) - (py / CHIP_OFSY / 2));
// 範囲外チェック
if (i < 0 || m_Layer.width() <= i) { return -1; }
if (j < 0 || m_Layer.height() <= j) { return -1; }
// マップ用インデックスに変換
return j * m_Layer.width() + i;;
}
View.as
このクラスでスクロールを管理していますが、スクロール座標はSceneTitle.asで設定しています。
/**
* スクロールを開始する
*/
public function startScroll():void
{
// マップ座標 -> クォータービュー座標 に変換
// X = w(x + y)
// Y = h(x - y)
var cx:int = m_CenterIdx % m_Layer.width();
var cy:int = m_CenterIdx / m_Layer.width();
var nextX:Number = CHIP_OFSX * (cx + cy);
var nextY:Number = CHIP_OFSY * (cx - cy);
// スクロール開始
m_View.start(nextX, nextY);
}
関連記事
Godot Engine というゲームエンジンの場合、標準機能でクォータービューが実装されているので、1から作るよりも、ゲームエンジンを使うのが良いのかもしれません
【Godot】クォータービューの基本的な作り方