Nous avons tous joué notre part d’incroyable jeux isométriques, que ce soit le Diablo original, ou Age of Empires ou Commandos. La première fois que vous êtes tombé sur un jeu isométrique, vous vous êtes peut-être demandé s'il s'agissait d'un jeu Jeu en 2D ou un Jeu en 3D ou quelque chose de complètement différent. Le monde des jeux isométriques a aussi un attrait mystique pour les développeurs de jeux. Essayons de résoudre le mystère de la projection isométrique et essayons de créer un monde isométrique simple dans ce tutoriel..
Ce didacticiel est une version mise à jour de mon didacticiel existant sur la création de mondes isométriques. Le didacticiel d'origine utilisait Flash avec ActionScript et reste pertinent pour les développeurs Flash ou OpenFL. Dans ce nouveau tutoriel, j'ai décidé d'utiliser Phaser avec du code JS, créant ainsi une sortie interactive HTML5 au lieu d'une sortie SWF.
Veuillez noter qu'il ne s'agit pas d'un didacticiel de développement Phaser, mais que nous utilisons simplement Phaser pour communiquer facilement les concepts de base de la création d'une scène isométrique. En outre, il existe des méthodes bien plus simples et plus efficaces pour créer du contenu isométrique dans Phaser, telles que le plug-in Phaser Isometric..
Par souci de simplicité, nous utiliserons l’approche basée sur les carreaux pour créer notre scène isométrique..
Dans les jeux 2D utilisant l'approche basée sur les tuiles, chaque élément visuel est décomposé en éléments plus petits, appelés tuiles, de taille standard. Ces tuiles seront disposées de manière à former le monde du jeu en fonction de données de niveau prédéterminées, généralement un tableau à deux dimensions..
Habituellement, les jeux basés sur des tuiles utilisent soit un de haut en bas vue ou un vue de côté pour la scène du jeu. Considérons une vue 2D top-down standard avec deux tuiles-a une dalle de gazon et un carrelage mural-comme indiqué ici:
Ces deux tuiles sont des images carrées de même taille, d’où la hauteur des carreaux et largeur de la dalle sont identiques. Considérons un niveau de jeu qui est une prairie entourée de tous côtés par des murs. Dans un tel cas, les données de niveau représentées par un tableau à deux dimensions ressembleront à ceci:
[[1,1,1,1,1,1], [1,0,0,0,0,1], [1,0,0,0,0,1], [1,0,0, 0,0,1], [1,0,0,0,0,1], [1,1,1,1,1,1]]
Ici, 0
dénote une tuile d'herbe et 1
désigne une dalle murale. En organisant les tuiles en fonction des données de niveau, vous obtiendrez nos prairies murées, comme indiqué dans l'image ci-dessous:
Nous pouvons aller un peu plus loin en ajoutant des dalles de coin et des dalles de mur verticales et horizontales séparées, ce qui nécessite cinq dalles supplémentaires, ce qui nous amène à nos données de niveau actualisées:
[[3,1,1,1,1,4], [2,0,0,0,0,2], [2,0,0,0,0,2], [2,0,0, 0,0,2], [2,0,0,0,0,2], [6,1,1,1,1,5]]
Regardez l'image ci-dessous, où j'ai marqué les carreaux avec leurs numéros de carreaux correspondants dans les données de niveau:
Maintenant que nous avons compris le concept de l'approche basée sur les tuiles, laissez-moi vous montrer comment utiliser un pseudo-code de grille 2D simple pour restituer notre niveau:
for (i, boucle à travers les lignes) pour (j, boucle à travers les colonnes) x = j * largeur de la tuile y = i * hauteur de la tuile tileType = levelData [i] [j] placetile (tileType, x, y)
Si nous utilisons les images de mosaïque ci-dessus, la largeur et la hauteur de la mosaïque sont égales (et identiques pour toutes les mosaïques) et correspondent aux dimensions des images de mosaïque. La largeur et la hauteur des carreaux de cet exemple sont donc de 50 px, ce qui correspond à la taille totale du niveau de 300 x 300 px, soit six lignes et six colonnes de carreaux de 50 x 50 px chacune..
Comme indiqué précédemment, dans une approche normale basée sur des mosaïques, nous implémentons une vue de haut en bas ou une vue de côté; pour une vue isométrique, nous devons mettre en œuvre la projection isométrique.
La meilleure explication technique de quoi projection isométrique signifie, à ma connaissance, est tiré de cet article de Clint Bellanger:
Nous inclinons notre caméra selon deux axes (en la faisant pivoter de 45 degrés, puis de 30 degrés vers le bas). Cela crée une grille en forme de losange (losange) où les espaces de grille sont deux fois plus larges que hauts. Ce style a été popularisé par les jeux de stratégie et les RPG d'action. Si nous regardons un cube dans cette vue, trois côtés sont visibles (le haut et les deux côtés en regard).
Bien que cela semble un peu compliqué, implémenter réellement cette vue est très facile. Ce que nous devons comprendre, c’est la relation entre l’espace 2D et l’espace isométrique, c’est-à-dire la relation entre les données de niveau et la vue; la transformation de haut en bas cartésien coordonnées en coordonnées isométriques. L'image ci-dessous montre la transformation visuelle:
Permettez-moi de simplifier la relation entre les données de niveau stockées sous forme de tableau 2D et la vue isométrique, c'est-à-dire comment nous transformons les coordonnées cartésiennes en coordonnées isométriques. Nous allons essayer de créer une vue isométrique pour notre désormais célèbre prairie fortifiée. L'implémentation de la vue 2D du niveau était une itération simple avec deux boucles, plaçant des carreaux carrés compensant chacun avec les valeurs de hauteur et de largeur de carreaux fixes. Pour la vue isométrique, le pseudo-code reste le même, mais le placeTile ()
changements de fonction.
La fonction d'origine dessine simplement les images de mosaïque aux coordonnées fournies X
et y
, mais pour une vue isométrique, nous devons calculer les coordonnées isométriques correspondantes. Les équations à faire sont les suivantes: isoX
et isoY
représenter les coordonnées x et y isométriques, et cartX
et cartY
Représentent les coordonnées cartésiennes x et y:
// Cartésien à isométrique: isoX = cartX - cartY; isoY = (cartX + cartY) / 2;
// isométrique à cartésien: cartX = (2 * isoY + isoX) / 2; cartY = (2 * isoY - isoX) / 2;
Oui c'est ça. Ces équations simples sont la magie derrière la projection isométrique. Voici les fonctions d'assistance de Phaser qui peuvent être utilisées pour convertir d'un système à un autre à l'aide du très pratique Point
classe:
fonction cartesianToIsometric (cartPt) var tempPt = new Phaser.Point (); tempPt.x = cartPt.x-cartPt.y; tempPt.y = (cartPt.x + cartPt.y) / 2; return (tempPt);
fonction isometricToCartesian (isoPt) var tempPt = new Phaser.Point (); tempPt.x = (2 * isoPt.y + isoPt.x) / 2; tempPt.y = (2 * isoPt.y-isoPt.x) / 2; return (tempPt);
Nous pouvons donc utiliser le cartésienToIsométrique
méthode d'assistance pour convertir les coordonnées 2D entrantes en coordonnées isométriques à l'intérieur du placeTile
méthode. En dehors de cela, le code de rendu reste le même, mais nous avons besoin de nouvelles images pour les tuiles. Nous ne pouvons pas utiliser les anciennes tuiles carrées utilisées pour notre rendu de haut en bas. L'image ci-dessous montre les nouveaux carreaux d'isométrie pour l'herbe et les murs, ainsi que le niveau isométrique rendu:
Incroyable, n'est ce pas? Voyons comment une position 2D typique est convertie en une position isométrique:
Point 2D = [100, 100]; // le point isométrique sera calculé comme ci-dessous isoX = 100 - 100; // = 0 isoY = (100 + 100) / 2; // = 100 Iso point == [0, 100];
De même, une entrée de [0, 0]
aura pour résultat [0, 0]
, et [10, 5]
va donner [5, 7.5]
.
Pour nos prairies murées, nous pouvons déterminer une zone praticable en vérifiant si l’élément de tableau est 0
à cette coordonnée, indiquant ainsi l'herbe. Pour cela, nous devons déterminer les coordonnées du tableau. Nous pouvons trouver les coordonnées de la tuile dans les données de niveau à partir de ses coordonnées cartésiennes en utilisant cette fonction:
fonction getTileCoordinates (cartPt, tileHeight) var tempPt = new Phaser.Point (); tempPt.x = Math.floor (cartPt.x / tileHeight); tempPt.y = Math.floor (cartPt.y / tileHeight); return (tempPt);
(Ici, nous supposons essentiellement que la hauteur et la largeur des carreaux sont égales, comme dans la plupart des cas.)
Par conséquent, à partir d'une paire de coordonnées d'écran (isométriques), nous pouvons trouver les coordonnées de tuile en appelant:
getTileCoordinates (isometricToCartesian (point d'écran), hauteur de la tuile);
Ce point d'écran peut être, par exemple, une position de clic de souris ou une position de prise.
En Flash, nous pouvons définir des points arbitraires pour un graphique comme point central ou [0,0]
. L'équivalent Phaser est Pivot
. Lorsque vous placez le graphique à say [10,20]
, ensuite ceci Pivot
le point sera aligné avec [10,20]
. Par défaut, le coin supérieur gauche d’un graphique est considéré comme son [0,0]
ou Pivot
. Si vous essayez de créer le niveau ci-dessus à l'aide du code fourni, vous n'obtiendrez pas le résultat affiché. Au lieu de cela, vous obtiendrez un terrain plat sans les murs, comme ci-dessous:
Cela est dû au fait que les images de mosaïque ont des tailles différentes et que nous ne traitons pas de l'attribut height de la mosaïque murale. L'image ci-dessous montre les différentes images de mosaïque que nous utilisons avec leurs cadres de sélection et un cercle blanc où leur valeur par défaut [0,0] est:
Voyez comment le héros se désaligne lorsqu'il dessine avec les pivots par défaut. Notez également que nous perdons la hauteur de la dalle murale si elle est dessinée à l'aide de pivots par défaut. L'image de droite montre à quel point ils doivent être correctement alignés pour que le carreau de mur atteigne sa hauteur et que le héros soit placé au milieu du carreau d'herbe. Ce problème peut être résolu de différentes manières.
Pour ce tutoriel, j'ai choisi la troisième méthode afin que cela fonctionne même avec un framework sans possibilité de définir des points de pivot..
Nous n'essaierons jamais de déplacer notre personnage ou projectile directement en coordonnées isométriques. Au lieu de cela, nous manipulerons les données de notre monde de jeu en coordonnées cartésiennes et utiliserons simplement les fonctions ci-dessus pour mettre à jour celles-ci à l'écran. Par exemple, si vous voulez faire avancer un caractère dans la direction positive, vous pouvez simplement incrémenter sa y
propriété en coordonnées 2D puis convertissez la position résultante en coordonnées isométriques:
y = y + vitesse; placetile (cartesianToIsometric (nouveau Phaser.Point (x, y)))
Ce sera un bon moment pour passer en revue tous les nouveaux concepts que nous avons appris jusqu'à présent et pour essayer de créer un exemple concret de quelque chose qui bouge dans un monde isométrique. Vous pouvez trouver les ressources d’image nécessaires dans la les atouts
dossier du dépôt git source.
Si vous essayez de déplacer l'image de la balle dans notre jardin clos, vous rencontrerez des problèmes avec tri en profondeur. En plus du placement normal, nous devrons nous occuper de tri en profondeur pour dessiner le monde isométrique, s'il y a des éléments en mouvement. Un bon tri en profondeur permet de s'assurer que les éléments les plus proches de l'écran sont placés au-dessus des éléments les plus éloignés..
La méthode de tri en profondeur la plus simple consiste simplement à utiliser la valeur de coordonnée y cartésienne, comme indiqué dans cette astuce: plus l'objet est haut sur l'écran, plus son tracé est rapide. Cela peut bien fonctionner pour des scènes isométriques très simples, mais une meilleure solution consiste à redessiner la scène isométrique une fois qu'un mouvement se produit, en fonction des coordonnées de la matrice de la tuile. Laissez-moi expliquer ce concept en détail avec notre pseudo-code pour le dessin de niveau:
for (i, boucle à travers les lignes) pour (j, boucle à travers les colonnes) x = j * largeur de la tuile y = i * hauteur de la tuile tileType = levelData [i] [j] placetile (tileType, x, y)
Imaginez que notre objet ou personnage se trouve sur la tuile [1,1]
-c'est-à-dire la tuile verte la plus haute dans la vue isométrique. Afin de dessiner correctement le niveau, le personnage doit être dessiné après avoir tracé le carreau de mur d'angle, les carreaux de mur gauche et droit et le carreau de sol, comme ci-dessous:
Si nous suivons notre boucle de dessin conformément au pseudo-code ci-dessus, nous dessinerons d'abord le mur du coin du centre, puis continuerons à dessiner tous les murs de la section supérieure droite jusqu'à ce qu'ils atteignent le coin droit..
Ensuite, dans la boucle suivante, il dessine le mur à gauche du personnage, puis la tuile d’herbe sur laquelle se trouve le personnage. Une fois que nous déterminons que c'est la tuile qui occupe notre personnage, nous allons dessiner le personnage après dessiner la tuile d'herbe. Ainsi, s'il y avait des murs sur les trois dalles d'herbe libres connectées à celle sur laquelle se trouve le personnage, ces murs chevaucheraient le personnage, ce qui donnerait un rendu trié de profondeur appropriée..
L'art isométrique peut être un pixel, mais ce n'est pas obligé. Dans le cas du pixel art isométrique, le guide de RhysD vous indique presque tout ce que vous devez savoir. Une théorie peut également être trouvée sur Wikipedia.
Lors de la création d'art isométrique, les règles générales sont les suivantes:
Les mosaïques isométriques plus grandes que les dimensions d'une mosaïque créeront des problèmes de tri en profondeur. Certaines des questions sont discutées dans ces liens:
Nous devons d’abord déterminer le nombre de directions de mouvement autorisées dans notre jeu. Habituellement, les jeux permettent un mouvement à quatre ou à huit. Regardez l'image ci-dessous pour comprendre la corrélation entre l'espace 2D et l'espace isométrique:
Veuillez noter qu'un personnage se déplacerait verticalement lorsque nous appuierions sur flèche vers le haut clé dans un jeu de haut en bas, mais pour un jeu isométrique, le personnage se déplacera à un angle de 45 degrés vers le coin supérieur droit.
Pour une vue de haut en bas, nous pourrions créer un ensemble d'animations de personnages faisant face dans une direction et les faire pivoter pour toutes les autres. Pour l’art des personnages isométriques, nous devons retransformer chaque animation dans chacune des directions autorisées. Pour les mouvements à huit directions, nous devons créer huit animations pour chaque action..
Pour faciliter la compréhension, nous désignons généralement les directions Nord, Nord-Ouest, Ouest, Sud-Ouest, Sud, Sud-Est, Est et Nord-Est. Les cadres de caractères ci-dessous montrent les cadres inactifs allant de Sud-Est et dans le sens des aiguilles d'une montre:
Nous placerons les personnages de la même manière que nous avons placé les tuiles. Le mouvement d'un caractère est accompli en calculant le mouvement en coordonnées cartésiennes puis en le convertissant en coordonnées isométriques. Supposons que nous utilisons le clavier pour contrôler le personnage.
Nous allons définir deux variables, dX
et dY
, sur la base des touches directionnelles appuyées. Par défaut, ces variables seront 0
et sera mis à jour selon le tableau ci-dessous, où U
, ré
, R
, et L
dénoter le Up, Vers le bas, Droite, et La gauche touches fléchées, respectivement. Une valeur de 1
sous une touche indique que la touche est enfoncée; 0
implique que la touche n'est pas enfoncée.
Clé Pos UDRL dX dY =============== 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 -1 0 1 0 1 0 1 1 0 0 1 1 -1 1 0 1 1 0 1 0 1 1 0 1
Maintenant, en utilisant les valeurs de dX
et dY
, nous pouvons mettre à jour les coordonnées cartésiennes comme ceci:
newX = currentX + (dX * speed); newY = currentY + (dY * speed);
Alors dX
et dY
représenter le changement dans les positions x et y du caractère, en fonction des touches enfoncées. Nous pouvons facilement calculer les nouvelles coordonnées isométriques, comme nous l'avons déjà expliqué:
Iso = cartesianToIsometric (new Phaser.Point (newX, newY))
Une fois que nous avons la nouvelle position isométrique, nous devons bouge toi le personnage à cette position. Sur la base des valeurs que nous avons pour dX
et dY
, nous pouvons décider de la direction à laquelle le personnage fait face et utiliser l’art du personnage correspondant. Une fois le personnage déplacé, n'oubliez pas de repeindre le niveau avec le tri en profondeur approprié, car les coordonnées de la tuile du personnage peuvent avoir changé..
La détection de collision est effectuée en vérifiant si la tuile à la position nouvellement calculée est une tuile qui ne peut pas être parcourue. Donc, une fois que nous avons trouvé la nouvelle position, nous ne déplaçons pas immédiatement le personnage là-bas, mais nous vérifions d'abord quelle mosaïque occupe cet espace..
coordonnée en mosaïque = getTileCoordinates (isometricToCartesian (position actuelle), hauteur de la mosaïque); if (isWalkable (coordonnée de tuile)) moveCharacter (); else // ne fait rien;
Dans la fonction isWalkable ()
, nous vérifions si la valeur du tableau de données de niveau à la coordonnée donnée est une tuile accessible à pied ou non. Nous devons prendre soin de mettre à jour la direction dans laquelle le personnage est confronté-même s'il ne bouge pas, comme dans le cas de lui frapper une tuile non praticable.
Cela peut sembler une solution appropriée, mais cela ne fonctionnera que pour des éléments sans volume. En effet, nous ne considérons qu'un seul point, qui est le point médian du personnage, pour calculer la collision. Ce dont nous avons vraiment besoin, c’est de trouver les quatre coins du personnage à partir de sa coordonnée de point médiane 2D disponible et de calculer les collisions pour tous ceux-ci. Si un coin tombe à l'intérieur d'une tuile non praticable, nous ne devrions pas déplacer le personnage..
Considérons un personnage et une tuile d’arbre dans le monde isométrique, et ils les deux ont les mêmes tailles d'image, aussi irréaliste que cela puisse paraître.
Pour bien comprendre le tri en profondeur, nous devons comprendre que chaque fois que les coordonnées x et y du personnage sont inférieures à celles de l'arbre, l'arbre recouvre le caractère. Chaque fois que les coordonnées x et y du caractère sont supérieures à celles de l'arbre, il se superpose à l'arbre. Quand ils ont la même coordonnée x, nous décidons uniquement en fonction de la coordonnée y: celle qui a la plus haute coordonnée chevauche l’autre. Quand ils ont la même coordonnée y, nous décidons en fonction de la coordonnée x seule: celle qui a la coordonnée x la plus élevée chevauche l’autre.
Comme expliqué précédemment, une version simplifiée consiste à dessiner séquentiellement les niveaux à partir de la mosaïque la plus éloignée, c'est-à-dire, dalle [0] [0]
-puis dessinez toutes les tuiles de chaque rangée une par une. Si un personnage occupe une tuile, nous dessinons d'abord la tuile terre, puis nous rendons la tuile du personnage. Cela fonctionnera bien, car le personnage ne peut pas occuper une dalle murale..
Ceci est une démo de Phaser. Cliquez pour vous concentrer sur la zone interactive et utilisez les touches fléchées pour déplacer le personnage. Vous pouvez utiliser deux touches fléchées pour vous déplacer dans les directions diagonales.
Vous pouvez trouver la source complète de la démo dans le référentiel source de ce tutoriel..