クォータビューの作り方

概要

この記事は斜め上からの視点 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から作るよりも、ゲームエンジンを使うのが良いのかもしれません