2Dアイソメトリック:スクリーンからタイルへの座標


9

等角投影の2Dゲームを作成していますが、カーソルがどのタイル上にあるかを正確に把握するのが困難です。これが描画です:

ここで、xsとysは画面座標(ピクセル)、xtとytはタイル座標、WとHはタイルの幅と高さ(ピクセル単位)です。座標の表記は(y、x)ですが、混乱する可能性があります。

これまでに私が理解できた最高のものはこれです:

int xtemp = xs / (W / 2);
int ytemp = ys / (H / 2);
int xt = (xs - ys) / 2;
int yt = ytemp + xt;

これはほぼ正しいように見えますが、非常に不正確な結果を与えており、特定のタイルを選択するのが難しくなっています。または、クリックしようとしているタイルの隣のタイルを選択する場合もあります。理由はわかりません。この背後にある論理を理解してくれる人がいれば助かります。

ありがとう!

回答:


2

正確な測定のために、以下を検討することができます。

最初に、iおよびjベクトル(isometricMap [i、j]など)によって決定される等尺性空間から、または画面上のytおよびxtとして、座標を画面のxおよびyによって決定される画面空間に変換する方法を検討します。簡単にするために、画面スペースが原点でアイソメトリックスペースと位置合わせされていると仮定します。

変換を行う1つの方法は、最初に回転を行い、次にy軸またはx軸をスケーリングすることです。ytとxtに一致する必要な値を取得するために、ここでその場で思いつくことはできません。これを行うための行列を作成してから、逆行列を使用することもできますが、基本的には逆演算が必要です。

値を逆にスケーリングし、逆方向に回転して値を取得し、下に丸めます。

これには他の方法もあると思いますが、今のところこれが最も適切だと思います。


ああ。私はこの投稿を何度も修正してきましたが、とにかく思い通りにポイントをきちんと伝えることができないと思います。睡眠が必要だ。
Toni

1
おかげで、行列は間違いなくここで最高のソリューションです。私は今ほとんど仕事をしています!
Asik 2012

4

私が書いているゲームでも同じ問題がありました。この問題は、アイソメトリックシステムを正確に実装した方法によって異なると思いますが、問題の解決方法を説明します。

私は最初にtile_to_screen関数から始めました。(私はそれがタイルを正しい場所に最初に配置する方法だと思います。)この関数には、screen_xとscreen_yを計算する方程式があります。鉱山はこのように見えました(python):

def map_to_screen(self, point):
    x = (SCREEN_WIDTH + (point.y - point.x) * TILE_WIDTH) / 2
    y = (SCREEN_HEIGHT + (point.y + point.x) * TILE_HEIGHT) / 2
    return (x, y)

私はそれら2つの方程式を取り、それらを線形方程式のシステムにしました。選択した任意の方法でこの連立方程式を解きます。(rrefメソッドを使用しました。また、一部のグラフ計算機はこの問題を解決できます。)

最終的な方程式は次のようになります。

# constants for quick calculating (only process once)
DOUBLED_TILE_AREA = 2 * TILE_HEIGHT * TILE_WIDTH
S2M_CONST_X = -SCREEN_HEIGHT * TILE_WIDTH + SCREEN_WIDTH * TILE_HEIGHT
S2M_CONST_Y = -SCREEN_HEIGHT * TILE_WIDTH - SCREEN_WIDTH * TILE_HEIGHT

def screen_to_map(self, point):
    # the "+ TILE_HEIGHT/2" adjusts for the render offset since I
    # anchor my sprites from the center of the tile
    point = (point.x * TILE_HEIGHT, (point.y + TILE_HEIGHT/2) * TILE_WIDTH)
    x = (2 * (point.y - point.x) + self.S2M_CONST_X) / self.DOUBLED_TILE_AREA
    y = (2 * (point.x + point.y) + self.S2M_CONST_Y) / self.DOUBLED_TILE_AREA
    return (x, y)

ご覧のとおり、初期方程式のように単純ではありません。しかし、私が作成したゲームではうまく機能します。線形代数の良さに感謝します!

更新

さまざまな演算子を使用して単純なPointクラスを記述した後、この答えを次のように簡略化しました。

# constants for quickly calculating screen_to_iso
TILE_AREA = TILE_HEIGHT * TILE_WIDTH
S2I_CONST_X = -SCREEN_CENTER.y * TILE_WIDTH + SCREEN_CENTER.x * TILE_HEIGHT
S2I_CONST_Y = -SCREEN_CENTER.y * TILE_WIDTH - SCREEN_CENTER.x * TILE_HEIGHT

def screen_to_iso(p):
    ''' Converts a screen point (px) into a level point (tile) '''
    # the "y + TILE_HEIGHT/2" is because we anchor tiles by center, not bottom
    p = Point(p.x * TILE_HEIGHT, (p.y + TILE_HEIGHT/2) * TILE_WIDTH)
    return Point(int((p.y - p.x + S2I_CONST_X) / TILE_AREA),
                 int((p.y + p.x + S2I_CONST_Y) / TILE_AREA))

def iso_to_screen(p):
    ''' Converts a level point (tile) into a screen point (px) '''
    return SCREEN_CENTER + Point((p.y - p.x) * TILE_WIDTH / 2,
                                 (p.y + p.x) * TILE_HEIGHT / 2)

はい、2つの線形方程式のシステムも機能するはずです。平行でない2つのベクトルがあることを考えると、ytとxtの単位ベクトルを使用して平面上の任意の点を取得できるはずです。私はあなたの実装は少し狭く見えていると思いますが、私はそれをわざわざ検証するつもりはありません。
Toni

2

良い座標系を使用しています。千鳥配置の列を使用すると、状況はさらに複雑になります。

この問題について考える1つの方法は、(xt、yt)を(xs、ys)に変換する関数があることです。私はターネの答えに従って呼び出しますmap_to_screen

この関数のが必要です。それと呼べますscreen_to_map。関数の逆には、次のプロパティがあります。

map_to_screen(screen_to_map(xs, ys)) == (xs, ys)
screen_to_map(map_to_screen(xt, yt)) == (xt, yt)

これら2つは、両方の関数を作成したら、単体テストに適しています。どのように逆を書くのですか?すべての関数に逆関数があるわけではありませんが、この場合は次のようになります。

  1. 回転とそれに続く並進として記述した場合、逆は逆並進(負のdx、dy)とそれに続く逆回転(負の角度)です。
  2. 行列乗算として記述した場合、その逆は行列逆乗算です。
  3. (xs、ys)を(xt、yt)で定義する代数方程式として記述した場合、(xs、ys)が与えられた(xt、yt)についてこれらの方程式を解くことで逆行列が求められます。

逆元の関数が、あなたが始めた答えを返すことを必ずテストしてください。+ TILE_HEIGHT/2レンダーオフセットを外すと、セインは両方のテストに合格します。代数を解いたとき、私は思いつきました:

x = (2*xs - SCREEN_WIDTH) / TILE_WIDTH
y = (2*ys - SCREEN_HEIGHT) / TILE_HEIGHT
yt =  (y + x) / 2
xt =  (y - x) / 2

私はそれがターネのと同じだと信じていscreen_to_mapます。

この関数は、マウス座標を浮動小数点数に変換します。floorそれらを整数タイル座標に変換するために使用します。


1
ありがとう!変換行列を使用することになったので、逆を書くのは簡単です。つまり、単にMatrix.Invert()です。さらに、それはより宣言的なスタイルのコーディング(Matrix.Translate()* Matrix.Scale()* Matrix.Rotate()ではなく、一連の方程式)につながります。少し遅いかもしれませんが、それは問題にはなりません。
Asik
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.