Carte isométrique échelonnée: calculez les coordonnées de la carte pour le point à l'écran

12

Je sais qu'il y a déjà beaucoup de ressources à ce sujet, mais je n'en ai pas trouvé qui correspond à mon système de coordonnées et j'ai beaucoup de mal à ajuster l'une de ces solutions à mes besoins. Ce que j'ai appris, c'est que la meilleure façon de le faire est d'utiliser une matrice de transformation. L'implémentation n'est pas un problème, mais je ne sais pas de quelle manière je dois transformer l'espace de coordonnées.

Voici une image qui montre mon système de coordonnées:

entrez la description de l'image ici

Comment transformer un point à l'écran en ce système de coordonnées?

Chris
la source
Découvrez ceci: jsfiddle.net/Sd4ZP/18
Markus von Broady
Je ne vois pas comment cela est utile en aucune façon. Je pense que vous n'avez pas bien compris ce que je veux dire.
Chris
Il fait la transformation, juste l'inverse, vous devez donc l'inverser.
Markus von Broady

Réponses:

23

D'abord, voici le code. Une explication suivra:

/*
 * tw, th contain the tile width and height.
 *
 * hitTest contains a single channel taken from a tile-shaped hit-test
 * image. Data was extracted with getImageData()
 */

worldToTilePos = function(x, y) {

    var eventilex = Math.floor(x%tw);
    var eventiley = Math.floor(y%th);

    if (hitTest[eventilex + eventiley * tw] !== 255) {
        /* On even tile */

        return {
            x: Math.floor((x + tw) / tw) - 1,
            y: 2 * (Math.floor((y + th) / th) - 1)
        };
    } else {
        /* On odd tile */

        return {
            x: Math.floor((x + tw / 2) / tw) - 1,
            y: 2 * (Math.floor((y + th / 2) / th)) - 1
        };
    }
};

Notez que ce code ne fonctionnera pas hors de la boîte pour la carte affichée dans votre question. Cela est dû au fait que les tuiles impaires sont décalées vers la gauche, alors que la tuile impaire est plus généralement décalée vers la droite (comme c'est le cas dans l' éditeur de carte en mosaïque ). Vous devriez pouvoir y remédier facilement en modifiant la valeur x renvoyée dans le cas des tuiles impaires.

Explication

Cela peut sembler être une méthode légèrement plus brutale pour accomplir cette tâche, mais elle a au moins l'avantage d'être parfaite au pixel près et légèrement plus flexible.

L'astuce consiste à visualiser la carte non pas comme une seule grille décalée, mais comme deux grilles superposées. Il y a la grille des lignes impaires et la grille des lignes paires, mais appelons-les plutôt rouges et vertes pour que nous puissions créer un joli diagramme ...

Deux grilles, rouge et verte

Remarquez à droite de cette image que j'ai marqué un point avec un point violet. C'est le point d'exemple que nous essaierons de trouver dans notre espace d'origine.

La chose à noter à propos de n'importe quel point du monde est qu'il se trouvera toujours dans exactement deux régions - une rouge et une verte (à moins qu'elle ne soit sur un bord, mais vous serez probablement coupée dans la limite du bord dentelé de toute façon). Trouvons ces régions ...

Deux régions candidates

Maintenant, choisissez laquelle des deux régions est la bonne. Il y aura toujours exactement une réponse.

À partir de là, nous pourrions faire une arithmétique plus simple et calculer la distance au carré de notre point d'échantillonnage à chaque point central des deux régions. Le plus proche sera notre réponse.

Il existe cependant une alternative. Pour chaque région de test, nous échantillonnons une image bitmap qui correspond à la forme exacte de nos carreaux. Nous l'échantillons à un point traduit en coordonnées locales pour cette seule tuile. Pour notre exemple, cela ressemblerait à ceci:

Échantillons ponctuels

Sur la gauche, nous vérifions la zone verte et obtenons un hit (pixel noir). Sur la droite, nous testons la région rouge et obtenons un échec (pixel blanc). Le deuxième test est bien sûr redondant puisqu'il sera toujours exactement l'un ou l'autre, jamais les deux.

Nous arrivons alors à la conclusion que nous avons un coup sûr dans la tuile impaire à 1,1. Cette coordonnée doit être simple à mapper aux coordonnées de tuile d'origine en utilisant une transformation différente pour les lignes paires et impaires.

Cette méthode vous permet également d'avoir des propriétés simples par pixel sur la ou les images bitmap de test de pixels. Par exemple, le blanc est hors tuile, le noir est un hit, le bleu est l'eau, le rouge est solide.

izb
la source
2
C'est génial³!
HumanCatfood du
Réponse fantastique et bien expliquée - il suffit maintenant de l'implémenter dans le code. Comme l'affiche originale n'a fourni aucune information sur le code derrière elle ou la langue autre que le tag en Javascript, je dis une réponse A *
Tom 'Blue' Piddock
1

Je pense que le problème que vous rencontrez concerne votre espace de coordonnées. Les coordonnées que vous avez données aux tuiles ne sont pas vraiment une projection isométrique - Vous devez considérer le xAxis comme allant en diagonale en bas à droite et le yAxis comme allant en diagonale en bas à gauche (ou une variante de cela)

en ce moment, si vous vous déplacez le long des axes de coordonnées illustrés, vous voyagerez dans une direction diagonale dans "l'espace des carreaux" de sorte que les carrés deviennent des diamants (et tout sera plus complexe)

La matrice de transformation que vous recherchez est une matrice construite à partir de ces axes x et y. C'est en fait la même chose que de faire le calcul suivant.

screenOffsetXY = screenPointXY - tileOriginXY;
tileX = dot(screenOffsetXY, xAxis) / tileWidth;
tileY = dot(screenOffsetXY, yAxis) / tileWidth;

Edit: Je viens de tomber sur une question similaire (mais avec le système de coordonnées dont je parlais) et quelqu'un a donné une réponse beaucoup plus approfondie ici:

Comment convertir les coordonnées de la souris en index isométriques?

Cholesky
la source
0

Fondamentalement, vous souhaitez obtenir la position de la souris dans la fenêtre à l'aide d'un écouteur d'événements, vous devez supprimer le décalage de la position du canevas dans la fenêtre par rapport aux positions de la souris, de sorte que la position de la souris est ensuite relative au canevas.

function mouseTwoGridPosition(e){

var mousex = e.pageX; //mouse position x
var mouseY = e.pageY;  //mouse position y
var canvas_width = 1000; //pixels
var offset_left = 100; //offset of canvas to window in pixels
var offset_top = 150; //offset of canvas to window in pixels

var isotile = 64; //if iso tile is 64 by 64

//get mouse position relative to canvas rather than window
var x = mousex - canvas_width/2 - offset_left;
var y = mousey - offset_top;


//convert to isometric grid
var tx = Math.round( (x + y * 2) / isotile) - 1;
var ty = Math.round((y * 2 - x) / isotile) - 1;

 //because your grid starts at 0/0 not 1/1 we subtract 1 
 // this is optional based on what grid number you want to start at



   return [tx,ty];
}

Je suppose que vous savez comment écouter des événements sur toile pour déplacer la souris, sinon vous voudrez peut-être en savoir plus sur JS avant d'envisager la conception de jeux isométriques: D

Dave
la source
N'est-ce pas un peu trop simple? Je veux qu'elle corresponde parfaitement à la forme de la tuile iso. Vous vérifiez juste une zone rectangulaire si je comprends bien.
Chris
qu'entendez-vous par "forme" de l'isotile ... par exemple, dites que votre souris est dans les limites en forme de losange de 0: 1, le retour sera de 0: 1 jusqu'à ce que votre souris quitte cette frontière. Si la taille de vos carreaux varie, ma méthode ne fonctionnera pas. La fonction fournie est ce que j'utilise et cela fonctionne bien pour moi.
Dave
Je me le demande, car toutes les autres solutions sont beaucoup plus compliquées. Les tuiles sont de la même taille, bien sûr, je vais donc vérifier cela.
Chris
Bien bricoler avec cela assurez-vous d'obtenir le bon décalage de la toile à gauche et en haut - et la largeur de la toile, lorsque vous faites les calculs fonctionneront bien (également la largeur de la taille isotile).
Dave
Êtes-vous sûr que vous utilisez le même système de coordonnées que moi? C'est encore complètement éteint.
Chris
0

J'ai résolu ce problème en modifiant l'espace de coordonnées. Cela commence maintenant sans décalage dans la première ligne et pour cela j'ai trouvé un exemple de travail que j'ai pu ajuster un peu.

    hWidth = this.tileset.tileSize.width / 2;
    hHeight = this.tileset.tileSize.height / 2;

    pX = point.x - halfWidth;
    pY = point.y - halfHeight;

    x = Math.floor((pX + (pY - hHeight) * 2) / this.tileset.tileSize.width);
    y = Math.floor((pY - (pX - hWidth) * 0.5) / this.tileset.tileSize.height);

    tx = Math.floor((x - y) / 2) + 1 + this.camera.x;
    ty = y + x + 2 + this.camera.y;
Chris
la source