2D-Isometrie: Bildschirm-zu-Kachel-Koordinaten

9

Ich schreibe ein isometrisches 2D-Spiel und habe Schwierigkeiten, genau herauszufinden, auf welcher Kachel sich der Cursor befindet. Hier ist eine Zeichnung:

Dabei sind xs und ys Bildschirmkoordinaten (Pixel), xt und yt Kachelkoordinaten, W und H Kachelbreite bzw. Kachelhöhe in Pixel. Meine Notation für Koordinaten ist (y, x), was verwirrend sein kann, tut mir leid.

Das Beste, was ich bisher herausfinden konnte, ist Folgendes:

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

Dies scheint fast richtig zu sein, führt jedoch zu einem sehr ungenauen Ergebnis, wodurch es schwierig wird, bestimmte Kacheln auszuwählen, oder manchmal wird eine Kachel neben der ausgewählt, auf die ich klicken möchte. Ich verstehe nicht warum und möchte, dass mir jemand helfen kann, die Logik dahinter zu verstehen.

Vielen Dank!

Asik
quelle

Antworten:

2

Für eine genaue Messung könnten wir Folgendes in Betracht ziehen:

Betrachten wir zunächst, wie Koordinaten vom isometrischen Raum, der durch i- und j-Vektoren (wie in isometricMap [i, j]) oder als yt und xt auf dem Bildschirm bestimmt wird, in den durch x und y des Bildschirms bestimmten Bildschirmraum transformiert werden. Nehmen wir an, Ihr Bildschirmbereich ist der Einfachheit halber am Ursprung mit dem isometrischen Bereich ausgerichtet.

Eine Möglichkeit, die Transformation durchzuführen, besteht darin, zuerst eine Drehung durchzuführen und dann die y- oder x-Achse zu skalieren. Um die notwendigen Werte zu erhalten, die zu Ihrem yt und xt passen, kann ich hier nicht ganz auf die Stelle kommen. Sie können eine Matrix erstellen, um dies zu tun oder nicht, und dann die umgekehrte Matrix verwenden, aber die umgekehrte Operation ist im Grunde das, was Sie wollen.

Skalieren Sie den Wert in umgekehrter Reihenfolge und drehen Sie ihn dann rückwärts, um die Werte zu erhalten und nach unten zu runden.

Ich denke, es gibt andere Möglichkeiten, aber das scheint mir momentan am angemessensten zu sein.

Toni
quelle
argh. Ich habe diesen Beitrag so oft überarbeitet und ich denke, ich kann meinen Standpunkt nicht so ordentlich vermitteln, wie ich es sowieso gerne hätte. Ich brauche Schlaf.
Toni
1
Danke, Matrizen sind hier definitiv die beste Lösung. Ich habe jetzt etwas, das fast funktioniert!
Asik
4

Ich hatte das gleiche Problem für ein Spiel, das ich schrieb. Ich stelle mir vor, dass dieses Problem davon abhängt, wie genau Sie Ihr isometrisches System implementiert haben, aber ich werde erklären, wie ich das Problem gelöst habe.

Ich habe zuerst mit meiner Funktion tile_to_screen begonnen. (Ich gehe davon aus, dass Sie die Kacheln auf diese Weise an der richtigen Stelle platzieren.) Diese Funktion hat eine Gleichung zur Berechnung von screen_x und screen_y. Meins sah so aus (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)

Ich nahm diese beiden Gleichungen und machte sie zu einem System linearer Gleichungen. Lösen Sie dieses Gleichungssystem mit einer beliebigen Methode. (Ich habe eine rref-Methode verwendet. Außerdem können einige Grafikrechner dieses Problem lösen.)

Die endgültigen Gleichungen sahen folgendermaßen aus:

# 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)

Wie Sie sehen können, ist es nicht so einfach wie die Anfangsgleichung. Aber es funktioniert gut für das Spiel, das ich erstellt habe. Gott sei Dank für die lineare Algebra!

Aktualisieren

Nachdem ich eine einfache Point-Klasse mit verschiedenen Operatoren geschrieben hatte, vereinfachte ich diese Antwort auf Folgendes:

# 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)
Thane Brimhall
quelle
Ja, ein System aus zwei linearen Gleichungen sollte ebenfalls funktionieren. Wenn man bedenkt, dass wir zwei Vektoren haben, die nicht parallel sind, sollten Sie in der Lage sein, jeden Punkt auf der Ebene zu erhalten, indem Sie die Einheitsvektoren von yt und xt verwenden. Obwohl ich denke, dass Ihre Implementierung etwas eingeschränkt aussieht und ich mich nicht darum kümmern werde, sie zu validieren.
Toni
2

Sie verwenden ein gutes Koordinatensystem. Wenn Sie versetzte Spalten verwenden, wird es viel schwieriger.

Eine Möglichkeit, über dieses Problem nachzudenken, besteht darin, dass Sie eine Funktion haben, mit der Sie (xt, yt) in (xs, ys) umwandeln können. Ich werde Thanes Antwort folgen und sie anrufen map_to_screen.

Sie möchten die Umkehrung dieser Funktion. Wir können es nennen screen_to_map. Funktionsumkehrungen haben folgende Eigenschaften:

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

Diese beiden sind gute Dinge für den Unit-Test, wenn Sie beide Funktionen geschrieben haben. Wie schreibt man die Umkehrung? Nicht alle Funktionen haben Inversen, aber in diesem Fall:

  1. Wenn Sie es als Rotation gefolgt von einer Translation geschrieben haben, ist die Umkehrung die inverse Translation (negatives dx, dy), gefolgt von der inversen Rotation (negativer Winkel).
  2. Wenn Sie es als Matrixmultiplikation geschrieben haben, ist die Umkehrung die inverse Matrixmultiplikation.
  3. Wenn Sie es als algebraische Gleichungen geschrieben haben, die (xs, ys) als (xt, yt) definieren, wird die Umkehrung durch Lösen dieser Gleichungen für (xt, yt) gegeben, die (xs, ys) gegeben sind.

Stellen Sie sicher, dass Sie testen, ob die Funktion inverse + original die Antwort zurückgibt, mit der Sie begonnen haben. Thane's besteht beide Tests, wenn Sie den + TILE_HEIGHT/2Render-Offset herausnehmen. Als ich die Algebra gelöst habe, habe ich mir Folgendes ausgedacht:

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

was ich glaube, ist das gleiche wie das von Thane screen_to_map.

Die Funktion verwandelt die Mauskoordinaten in Gleitkommazahlen. Verwenden Sie floordiese Option, um sie in ganzzahlige Kachelkoordinaten umzuwandeln.

amitp
quelle
1
Vielen Dank! Am Ende habe ich eine Transformationsmatrix verwendet, so dass das Schreiben der Umkehrung trivial ist, dh es ist nur Matrix.Invert (). Außerdem führt dies zu einem deklarativeren Codierungsstil (Matrix.Translate () * Matrix.Scale () * Matrix.Rotate () anstelle einer Reihe von Gleichungen). Vielleicht ist es etwas langsamer, aber das sollte kein Problem sein.
Asik