Isométrique 2D: écran aux coordonnées de mosaïque

9

J'écris un jeu 2D isométrique et j'ai du mal à déterminer avec précision sur quelle tuile se trouve le curseur. Voici un dessin:

où xs et ys sont des coordonnées d'écran (pixels), xt et yt sont des coordonnées de tuile, W et H sont la largeur et la hauteur de tuile en pixels, respectivement. Ma notation pour les coordonnées est (y, x), ce qui peut prêter à confusion, désolé.

Le mieux que j'ai pu comprendre jusqu'à présent est le suivant:

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

Cela semble presque correct mais me donne un résultat très imprécis, ce qui rend difficile la sélection de certaines tuiles, ou parfois il sélectionne une tuile à côté de celle sur laquelle j'essaie de cliquer. Je ne comprends pas pourquoi et j'aimerais que quelqu'un m'aide à comprendre la logique derrière tout ça.

Merci!

Asik
la source

Réponses:

2

Pour une mesure précise, nous pourrions envisager ce qui suit:

Voyons d'abord comment transformer les coordonnées de l'espace isométrique, déterminées par les vecteurs i et j (comme dans isometricMap [i, j]) ou comme les yt et xt sur l'écran, en espace d'écran, déterminé par x et y de l'écran. Supposons que votre espace d'écran est aligné à l'origine avec l'espace isométrique pour des raisons de simplicité.

Une façon de faire la transformation consiste à faire d'abord une rotation, puis à mettre à l'échelle l'axe y ou x. Pour obtenir les valeurs nécessaires pour correspondre à votre yt et xt, je ne peux pas tout à fait trouver sur place ici. Vous pouvez créer une matrice pour ce faire ou non, puis utiliser la matrice inverse, mais l'opération inverse est fondamentalement ce que vous voulez.

Mettez la valeur à l'échelle en sens inverse, puis tournez vers l'arrière pour obtenir les valeurs et arrondissez vers le bas.

Il y a d'autres façons, je suppose, mais cela me semble le plus approprié en ce moment.

Toni
la source
argh. J'ai révisé ce post tant de fois et je pense que je ne peux pas faire passer mon message aussi clairement que je le voudrais de toute façon. J'ai besoin de dormir.
Toni
1
Merci, les matrices sont certainement la meilleure solution ici. J'ai quelque chose qui fonctionne presque maintenant!
Asik
4

J'ai eu ce même problème pour un jeu que j'écrivais. J'imagine que ce problème différera en fonction de la façon dont vous avez implémenté votre système isométrique, mais je vais vous expliquer comment j'ai résolu le problème.

J'ai d'abord commencé avec ma fonction tile_to_screen. (Je suppose que c'est ainsi que vous placez les tuiles au bon endroit en premier lieu.) Cette fonction a une équation pour calculer screen_x et screen_y. Le mien ressemblait à ceci (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)

J'ai pris ces deux équations et les ai transformées en un système d'équations linéaires. Résolvez ce système d'équations dans la méthode que vous choisissez. (J'ai utilisé une méthode rref. De plus, certaines calculatrices graphiques peuvent résoudre ce problème.)

Les équations finales ressemblaient à ceci:

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

Comme vous pouvez le voir, ce n'est pas aussi simple que l'équation initiale. Mais cela fonctionne bien pour le jeu que j'ai créé. Dieu merci pour l'algèbre linéaire!

Mise à jour

Après avoir écrit une classe Point simple avec différents opérateurs, j'ai simplifié cette réponse comme suit:

# 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
la source
Oui, un système de deux équations linéaires devrait également fonctionner. Étant donné que nous avons deux vecteurs qui ne sont pas parallèles, vous devriez pouvoir obtenir n'importe quel point sur le plan en utilisant les vecteurs unitaires de yt et xt. Bien que je pense que votre implémentation soit un peu restrictive et que je ne vais pas m'embêter à la valider.
Toni
2

Vous utilisez un bon système de coordonnées. Les choses deviennent beaucoup plus compliquées si vous utilisez des colonnes décalées.

Une façon de penser à ce problème est que vous avez une fonction pour transformer (xt, yt) en (xs, ys). Je vais suivre la réponse de Thane et l'appeler map_to_screen.

Vous voulez l' inverse de cette fonction. Nous pouvons l'appeler screen_to_map. Les inverses de fonction ont ces propriétés:

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

Ces deux sont de bonnes choses pour le test unitaire une fois que vous avez écrit les deux fonctions. Comment écris-tu l'inverse? Toutes les fonctions n'ont pas des inverses mais dans ce cas:

  1. Si vous l'écrivez comme une rotation suivie d'une translation, alors l'inverse est la translation inverse (dx négatif, dy) suivie de la rotation inverse (angle négatif).
  2. Si vous l'avez écrit comme une multiplication matricielle, alors l'inverse est la multiplication inverse matricielle.
  3. Si vous l'avez écrit comme des équations algébriques définissant (xs, ys) en termes de (xt, yt), alors l'inverse est trouvé en résolvant ces équations pour (xt, yt) donné (xs, ys).

Assurez-vous de tester que la fonction inverse + originale donne la réponse avec laquelle vous avez commencé. Thane's réussit les deux tests, si vous sortez le+ TILE_HEIGHT/2 décalage de rendu. Quand j'ai résolu l'algèbre, j'ai trouvé:

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

qui je crois est le même que celui de Thane screen_to_map.

La fonction transformera les coordonnées de la souris en flottants; utiliser floorpour les convertir en coordonnées de tuiles entières.

amitp
la source
1
Merci! J'ai fini par utiliser une matrice de transformation, de sorte que l'écriture de l'inverse est triviale, c'est-à-dire que c'est juste Matrix.Invert (). De plus, cela conduit à un style de codage plus déclaratif (Matrix.Translate () * Matrix.Scale () * Matrix.Rotate () plutôt qu'à un tas d'équations). Peut-être que c'est un peu plus lent, mais cela ne devrait pas être un problème.
Asik