Advertisement
  1. Game Development
  2. Isometric Games

Creando mundos isométricos: una guía para desarrolladores de juegos

Scroll to top
Read Time: 21 min

() translation by (you can also view the original English article)

En este tutorial, le daré una descripción general amplia de lo que necesita saber para crear mundos isométricos. Aprenderá qué es la proyección isométrica y cómo representar los niveles isométricos como matrices en 2D. Formularemos relaciones entre la vista y la lógica, de modo que podamos manipular fácilmente los objetos en la pantalla y manejar la detección de colisiones basada en mosaicos. También veremos la clasificación de profundidad y la animación de personajes.

Artículos Relacionados

¿Quieres más consejos para crear mundos isométricos? Consulte la publicación de seguimiento, Creando mundos isometricos: Una guía para los desarrolladores de juegos, Continuado, y el libro de Juwal, Starling Game Development Essentials.


1. El mundo isométrico

La vista isométrica es un método de visualización que se usa para crear una ilusión de 3D para un juego que, de otro modo, sería 2D, a veces denominado pseudo 3D o 2.5D. Estas imágenes (tomadas de Diablo 2 y Age of Empires) ilustran lo que quiero decir:

diablodiablodiablo

Diablo 2
AOEAOEAOE

Era de los imperios

Implementar una vista isométrica se puede hacer de muchas maneras, pero en aras de la simplicidad me centraré en un enfoque basado en mosaicos, que es el método más eficiente y ampliamente utilizado. He superpuesto cada captura de pantalla anterior con una cuadrícula de diamantes que muestra cómo el terreno se divide en mosaicos.


2. Juegos basados en azulejos

En el enfoque basado en mosaicos, cada elemento visual se divide en piezas más pequeñas, llamadas mosaicos, de un tamaño estándar. Estas fichas se organizarán para formar el mundo del juego de acuerdo con los datos de nivel predeterminados, generalmente una matriz 2D.

Por ejemplo, consideremos una vista 2D estándar de arriba hacia abajo con dos mosaicos, una baldosa de hierba y una baldosa de pared, como se muestra aquí:

base 2d tiles

Algunas fichas simples

Estos mosaicos son del mismo tamaño que el otro y son cuadrados, por lo que la altura del azulejo y el ancho del mosaico son los mismos.

Para un nivel con pastizales cerrados por todos lados por muros, la matriz 2D de los datos de nivel se verá así:

1
[[1,1,1,1,1,1],
2
 [1,0,0,0,0,1],
3
 [1,0,0,0,0,1],
4
 [1,0,0,0,0,1],
5
 [1,0,0,0,0,1],
6
 [1,1,1,1,1,1]]

Aquí, 0 denota una baldosa de hierba y 1 denota una baldosa de pared. Organizar las fichas de acuerdo con los datos de nivel producirá la imagen de nivel inferior:

2d level simple

Un nivel simple, que se muestra en una vista de arriba hacia abajo.

Podemos mejorar esto agregando azulejos de esquina y azulejos verticales y horizontales separados, que requieren cinco azulejos adicionales:

1
[[3,1,1,1,1,4],
2
 [2,0,0,0,0,2],
3
 [2,0,0,0,0,2],
4
 [2,0,0,0,0,2],
5
 [2,0,0,0,0,2],
6
 [6,1,1,1,1,5]]
2d level complex

Nivel mejorado con números de mosaico

Espero que el concepto del enfoque basado en mosaico sea ahora claro. Esta es una implementación de grilla 2D directa, que podríamos codificar de la siguiente manera:

1
for (i, loop through rows)
2
 for (j, loop through columns)
3
  x = j * tile width
4
  y = i * tile height
5
  tileType = levelData[i][j]
6
  placetile(tileType, x, y)

Aquí suponemos que el ancho del azulejo y la altura del azulejo son iguales (y lo mismo para todos los azulejos), y coinciden con las dimensiones de las imágenes del azulejo. Por lo tanto, el ancho del azulejo y la altura del azulejo para este ejemplo son ambos de 50px, que conforman el tamaño de nivel total de 300x300px, es decir, seis filas y seis columnas de losetas que miden 50x50px cada una.

En un enfoque normal basado en mosaicos, implementamos una vista descendente o una vista lateral; para una vista isométrica necesitamos implementar la proyección isométrica.


3. Proyección isométrica

La mejor explicación técnica de lo que significa "proyección isométrica", hasta donde yo sé, es de este artículo de Clint Bellanger:

    Inclinamos nuestra cámara a lo largo de dos ejes (balancee la cámara 45 grados hacia un lado, luego 30 grados hacia abajo). Esto crea una rejilla en forma de diamante (rombo) donde los espacios de la rejilla son dos veces más anchos que altos. Este estilo fue popularizado por juegos de estrategia y juegos de rol de acción. Si miramos un cubo en esta vista, son visibles tres lados (lado superior y dos lados enfrentados).

Aunque suena un poco complicado, implementar esta visión es sencillo. Lo que debemos comprender es la relación entre el espacio 2D y el espacio isométrico, es decir, la relación entre los datos de nivel y la vista; la transformación de coordenadas "cartesianas" de arriba hacia abajo en coordenadas isométricas.

the_isometric_gridthe_isometric_gridthe_isometric_grid

Cuadrícula cartesiana vs. cuadrícula isométrica.

(No estamos considerando una técnica basada en mosaicos hexagonales, que es otra forma de implementar mundos isométricos).

Colocando azulejos isométricos

Permítanme intentar simplificar la relación entre los datos de nivel almacenados como una matriz 2D y la vista isométrica, es decir, cómo transformamos las coordenadas cartesianas en coordenadas isométricas.

Trataremos de crear la vista isométrica para nuestros datos de nivel de pastizales cerrados:

1
[[1,1,1,1,1,1],
2
 [1,0,0,0,0,1],
3
 [1,0,0,0,0,1],
4
 [1,0,0,0,0,1],
5
 [1,0,0,0,0,1],
6
 [1,1,1,1,1,1]]

En este escenario, podemos determinar un área transitable comprobando si el elemento de la matriz es 0 en esa coordenada, lo que indica hierba. La implementación de vista 2D del nivel anterior fue una iteración directa con dos bucles, colocando mosaicos cuadrados compensando cada uno con la altura del azulejo fijo y los valores de ancho del azulejo.

1
for (i, loop through rows)
2
 for (j, loop through columns)
3
  x = j * tile width
4
  y = i * tile height
5
  tileType = levelData[i][j]
6
  placetile(tileType, x, y)

Para la vista isométrica, el código sigue siendo el mismo, pero la función placeTile() cambia.

Para una vista isométrica, necesitamos calcular las coordenadas isométricas correspondientes dentro de los bucles.
Las ecuaciones para hacer esto son las siguientes, donde isoX y isoY representan las coordenadas isométricas x e y, y cartX y cartY representan las coordenadas cartesianas x e y:

1
//Cartesian to isometric:

2
3
isoX = cartX - cartY;
4
isoY = (cartX + cartY) / 2;
1
//Isometric to Cartesian:

2
3
cartX = (2 * isoY + isoX) / 2;
4
cartY = (2 * isoY - isoX) / 2;

Estas funciones muestran cómo se puede convertir de un sistema a otro:

1
function isoTo2D(pt:Point):Point{
2
  var tempPt:Point = new Point(0, 0);
3
  tempPt.x = (2 * pt.y + pt.x) / 2;
4
  tempPt.y = (2 * pt.y - pt.x) / 2;
5
  return(tempPt);
6
}
1
function twoDToIso(pt:Point):Point{
2
  var tempPt:Point = new Point(0,0);
3
  tempPt.x = pt.x - pt.y;
4
  tempPt.y = (pt.x + pt.y) / 2;
5
  return(tempPt);
6
}

El pseudocódigo para el ciclo se ve así:

1
for(i, loop through rows)
2
  for(j, loop through columns)
3
    x = j * tile width
4
    y = i * tile height
5
    tileType = levelData[i][j]
6
    placetile(tileType, twoDToIso(new Point(x, y)))
isolevel screenshotisolevel screenshotisolevel screenshot

Nuestro pastizal encerrado en una vista isométrica.

Como ejemplo, veamos cómo una posición 2D típica se convierte a una posición isométrica:

1
2D point = [100, 100];
2
// twoDToIso(2D point) will be calculated as below
3
isoX = 100 - 100; // = 0
4
isoY = (100 + 100) / 2;  // = 100
5
Iso point == [0, 100];

Del mismo modo, una entrada de [0, 0] dará como resultado [0, 0], y [10, 5] dará [5, 7.5].

El método anterior nos permite crear una correlación directa entre los datos de nivel 2D y las coordenadas isométricas. Podemos encontrar las coordenadas del mosaico en los datos de nivel de sus coordenadas cartesianas usando esta función:

1
function getTileCoordinates(pt:Point, tileHeight:Number):Point{
2
  var tempPt:Point = new Point(0, 0);
3
  tempPt.x = Math.floor(pt.x / tileHeight);
4
  tempPt.y = Math.floor(pt.y / tileHeight);
5
  return(tempPt);
6
}

(Aquí, suponemos esencialmente que la altura del azulejo y el ancho del azulejo son iguales, como en la mayoría de los casos).

Por lo tanto, a partir de un par de coordenadas de pantalla (isométricas), podemos encontrar las coordenadas del mosaico llamando:

1
getTileCoordinates(isoTo2D(screen point), tile height);

Este punto de pantalla podría ser, por ejemplo, una posición de clic del mouse o una posición de pick-up.

Consejo: Otro método de colocación es el modelo Zigzag, que adopta un enfoque completamente diferente.

Moviéndose en coordenadas isométricas

El movimiento es muy fácil: manipula los datos de su mundo de juego en coordenadas cartesianas y simplemente usa las funciones anteriores para actualizarlo en la pantalla. Por ejemplo, si desea mover un carácter hacia adelante en la dirección y positiva, simplemente puede incrementar su propiedad y y luego convertir su posición a coordenadas isométricas:

1
y = y + speed;
2
placetile(twoDToIso(new Point(x, y)))

Clasificación de profundidad

Además de la ubicación normal, tendremos que ocuparnos de la clasificación en profundidad para dibujar el mundo isométrico. Esto asegura que los elementos más cercanos al jugador se dibujen encima de los elementos más alejados.

El método más simple de clasificación en profundidad es simplemente usar el valor cartesiano de coordenada y, como se menciona en este consejo rápido: cuanto más arriba de la pantalla esté el objeto, más temprano se dibujará. Esto funciona bien siempre que no tengamos sprites que ocupen más de un espacio de teselas.

La forma más eficiente de clasificar en profundidad para los mundos isométricos es dividir todos los mosaicos en dimensiones estándar de un solo mosaico y no permitir imágenes más grandes. Por ejemplo, aquí hay una ficha que no encaja en el tamaño de tesela estándar: vea cómo podemos dividirla en varias teselas, cada una de las cuales se adapta a las dimensiones de la tesela:

split big tilesplit big tilesplit big tile

Una imagen grande se divide en varias fichas de dimensiones isométricas estándar

4. Creando el Arte

El arte isométrico puede ser pixel art, pero no tiene que serlo. Al tratar con el arte de píxeles isométricos, la guía de RhysD te dice casi todo lo que necesitas saber. Algunas teorías se pueden encontrar en Wikipedia también.

Al crear arte isométrico, las reglas generales son

  • Comience con una cuadrícula isométrica en blanco y adhiérase a la precisión perfecta de píxeles.
  • Intenta dividir el arte en imágenes de baldosas isométricas individuales.
  • Intente asegurarse de que cada baldosa sea accesible o no. Será complicado si tenemos que acomodar una sola loseta que contenga tanto áreas transitables como no transitables.
  • La mayoría de las fichas necesitarán mosaico sin fisuras en una o más direcciones.
  • Las sombras pueden ser difíciles de implementar, a menos que usemos un enfoque por capas donde dibujamos sombras en la capa del suelo y luego dibujamos el héroe (o árboles u otros objetos) en la capa superior. Si el enfoque que utilizas no es de varias capas, asegúrate de que las sombras caigan al frente para que no se caigan, por ejemplo, el héroe cuando se para detrás de un árbol.
  • En caso de que necesite usar una imagen de mosaico más grande que el tamaño de mosaico isométrico estándar, intente utilizar una dimensión que sea un múltiplo del tamaño del mosaico iso. Es mejor tener un enfoque en capas en tales casos, donde podemos dividir el arte en diferentes piezas en función de su altura. Por ejemplo, un árbol se puede dividir en tres partes: la raíz, el tronco y el follaje. Esto hace que sea más fácil ordenar las profundidades ya que podemos dibujar las piezas en las capas correspondientes que corresponden con sus alturas.

Los mosaicos isométricos que son más grandes que las dimensiones de un solo mosaico crearán problemas con la clasificación de profundidad. Algunos de los problemas se discuten en estos enlaces:


5. Personajes isométricos

Implementar caracteres en vista isométrica no es complicado, ya que puede sonar. El arte de los personajes debe ser creado de acuerdo con ciertos estándares. Primero necesitaremos fijar cuántas direcciones de movimiento están permitidas en nuestro juego; por lo general, los juegos proporcionarán movimiento en cuatro direcciones u movimiento en ocho direcciones.

Eight-way navigation directions in top-down and isometric viewsEight-way navigation directions in top-down and isometric viewsEight-way navigation directions in top-down and isometric views

Ocho direcciones de navegación en vista descendente e isométrica.

Para una vista de arriba hacia abajo, podríamos crear un conjunto de animaciones de personajes orientadas en una dirección, y simplemente rotarlas para todas las demás. Para el arte de los personajes isométricos, debemos volver a representar cada animación en cada una de las direcciones permitidas, por lo que para el movimiento a ocho bandas necesitamos crear ocho animaciones para cada acción. Para facilitar la comprensión, generalmente indicamos las direcciones como Norte, Noroeste, Oeste, Suroeste, Sur, Sureste, Este y Noreste, en sentido antihorario, en ese orden.

spriteSheetspriteSheetspriteSheet

Un personaje isométrico que mira en diferentes direcciones.

Colocamos personajes de la misma manera que colocamos los mosaicos. El movimiento de un personaje se logra calculando el movimiento en coordenadas cartesianas y luego convirtiendo a coordenadas isométricas. Supongamos que estamos usando el teclado para controlar el personaje.

Estableceremos dos variables, dX y dY, basadas en las teclas direccionales presionadas. Por defecto, estas variables serán 0 y se actualizarán según el cuadro siguiente, donde U, D, R y L indican las teclas de flecha Arriba, Abajo, Derecha e Izquierda, respectivamente. Un valor de 1 debajo de una tecla representa esa tecla presionada; 0 implica que no se está presionando la tecla.

1
  Key       Pos
2
U D R L    dX dY
3
================
4
0 0 0 0     0  0
5
1 0 0 0     0  1
6
0 1 0 0     0 -1
7
0 0 1 0     1  0
8
0 0 0 1    -1  0
9
1 0 1 0     1  1
10
1 0 0 1    -1  1
11
0 1 1 0     1 -1
12
0 1 0 1    -1 -1

Ahora, usando los valores de dX y dY, podemos actualizar las coordenadas cartesianas de la siguiente manera:

1
newX = currentX + (dX * speed);
2
newY = currentY + (dY * speed);

Entonces dX y dY representan el cambio en las posiciones x e y del personaje, en función de las teclas presionadas.

Podemos calcular fácilmente las nuevas coordenadas isométricas, como ya hemos discutido:

1
Iso = twoDToIso(new Point(newX, newY))

Una vez que tenemos la nueva posición isométrica, necesitamos mover el personaje a esta posición. En función de los valores que tenemos para dX y dY, podemos decidir a qué dirección se enfrenta el personaje y usar el arte del personaje correspondiente.

Detección de colisiones

La detección de colisión se realiza comprobando si la ficha en la nueva posición calculada es una ficha no accesible. Entonces, una vez que encontramos la nueva posición, no movemos el personaje inmediatamente, pero primero verificamos qué tesela ocupa ese espacio.

1
tile coordinate = getTileCoordinates(isoTo2D(iso point), tile height);
2
if (isWalkable(tile coordinate)) {
3
  moveCharacter();
4
} else {
5
  //do nothing;
6
}

En la función isWalkable(), verificamos si el valor del conjunto de datos de nivel en la coordenada dada es un mosaico con desplazamiento o no. Debemos tener cuidado de actualizar la dirección en la que se encuentra el personaje, incluso si él no se mueve, como en el caso de que golpee una ficha que no se puede mover.

Clasificación de profundidad con personajes

Considera un personaje y una ficha de árbol en el mundo isométrico.

Para comprender correctamente la clasificación por profundidad, debemos entender que siempre que las coordenadas xey del personaje sean menores que las del árbol, el árbol se solapa con el carácter. Siempre que las coordenadas xey del personaje sean mayores que las del árbol, el carácter se superpone al árbol.

Cuando tienen la misma coordenada x, decidimos basándonos únicamente en la coordenada y: la que tenga una coordenada y más alta se superpone a la otra. Cuando tienen la misma coordenada y, entonces decidimos basándonos solamente en la coordenada x: lo que tenga la coordenada x más alta se superpone a la otra.

Una versión simplificada de esto es dibujar secuencialmente los niveles comenzando desde el mosaico más alejado, es decir, tile[0][0], luego dibujar todos los mosaicos en cada fila uno por uno. Si un personaje ocupa una ficha, primero dibujamos la ficha de tierra y luego renderizamos la ficha de personaje. Esto funcionará bien, porque el personaje no puede ocupar un azulejo de la pared.

La clasificación de profundidad debe hacerse cada vez que una baldosa cambie de posición. Por ejemplo, tenemos que hacerlo cada vez que se muevan los personajes. Luego actualizamos la escena mostrada, después de realizar la clasificación de profundidad, para reflejar los cambios de profundidad.


6. ¡Pruebalo!

Ahora, aproveche sus nuevos conocimientos creando un prototipo funcional, con controles de teclado y clasificación de profundidad adecuada y detección de colisión. Aquí está mi demo:

Haga clic para dar el foco SWF, luego use las teclas de flecha. Haga clic aquí para ver la versión completa.

Puede encontrar útil esta clase de utilidad (la he escrito en AS3, pero debería poder entenderla en cualquier otro lenguaje de programación):

1
package com.csharks.juwalbose
2
{
3
  import flash.display.Sprite;
4
	import flash.geom.Point;
5
6
	public class IsoHelper
7
	{
8
		/**

9
		 * convert an isometric point to 2D

10
		 * */
11
		public static function isoTo2D(pt:Point):Point{
12
			//gx=(2*isoy+isox)/2;

13
			//gy=(2*isoy-isox)/2

14
			var tempPt:Point=new Point(0,0);
15
			tempPt.x=(2*pt.y+pt.x)/2;
16
			tempPt.y=(2*pt.y-pt.x)/2;
17
			return(tempPt);
18
		}
19
		/**

20
		 * convert a 2d point to isometric

21
		 * */
22
		public static function twoDToIso(pt:Point):Point{
23
			//gx=(isox-isoxy;

24
			//gy=(isoy+isox)/2

25
			var tempPt:Point=new Point(0,0);
26
			tempPt.x=pt.x-pt.y;
27
			tempPt.y=(pt.x+pt.y)/2;
28
			return(tempPt);
29
		}
30
31
		/**

32
		 * convert a 2d point to specific tile row/column

33
		 * */
34
		public static function getTileCoordinates(pt:Point, tileHeight:Number):Point{
35
			var tempPt:Point=new Point(0,0);
36
			tempPt.x=Math.floor(pt.x/tileHeight);
37
			tempPt.y=Math.floor(pt.y/tileHeight);
38
39
			return(tempPt);
40
		}
41
42
		/**

43
		 * convert specific tile row/column to 2d point

44
		 * */
45
		public static function get2dFromTileCoordinates(pt:Point, tileHeight:Number):Point{
46
			var tempPt:Point=new Point(0,0);
47
			tempPt.x=pt.x*tileHeight;
48
			tempPt.y=pt.y*tileHeight;
49
50
			return(tempPt);
51
		}
52
53
	}
54
}

Si realmente te quedas atascado, aquí tienes el código completo de mi demo (en formato de código de línea de tiempo Flash y AS3):

1
// Uses senocular's KeyObject class

2
// http://www.senocular.com/flash/actionscript/?file=ActionScript_3.0/com/senocular/utils/KeyObject.as

3
4
import flash.display.Sprite;
5
import com.csharks.juwalbose.IsoHelper;
6
import flash.display.MovieClip;
7
import flash.geom.Point;
8
import flash.filters.GlowFilter;
9
import flash.events.Event;
10
import com.senocular.utils.KeyObject;
11
import flash.ui.Keyboard;
12
import flash.display.Bitmap;
13
import flash.display.BitmapData;
14
import flash.geom.Matrix;
15
import flash.geom.Rectangle;
16
17
var levelData=[[1,1,1,1,1,1],
18
[1,0,0,2,0,1],
19
[1,0,1,0,0,1],
20
[1,0,0,0,0,1],
21
[1,0,0,0,0,1],
22
[1,1,1,1,1,1]];
23
24
var tileWidth:uint = 50;
25
var borderOffsetY:uint = 70;
26
var borderOffsetX:uint = 275;
27
28
var facing:String = "south";
29
var currentFacing:String = "south";
30
var hero:MovieClip=new herotile();
31
hero.clip.gotoAndStop(facing);
32
var heroPointer:Sprite;
33
var key:KeyObject = new KeyObject(stage);//Senocular KeyObject Class

34
var heroHalfSize:uint=20;
35
36
//the tiles

37
var grassTile:MovieClip=new TileMc();
38
grassTile.gotoAndStop(1);
39
var wallTile:MovieClip=new TileMc();
40
wallTile.gotoAndStop(2);
41
42
//the canvas

43
var bg:Bitmap = new Bitmap(new BitmapData(650,450));
44
addChild(bg);
45
var rect:Rectangle=bg.bitmapData.rect;
46
47
//to handle depth

48
var overlayContainer:Sprite=new Sprite();
49
addChild(overlayContainer);
50
51
//to handle direction movement

52
var dX:Number = 0;
53
var dY:Number = 0;
54
var idle:Boolean = true;
55
var speed:uint = 5;
56
var heroCartPos:Point=new Point();
57
var heroTile:Point=new Point();
58
59
//add items to start level, add game loop

60
function createLevel()
61
{
62
	var tileType:uint;
63
	for (var i:uint=0; i<levelData.length; i++)
64
	{
65
		for (var j:uint=0; j<levelData[0].length; j++)
66
		{
67
			tileType = levelData[i][j];
68
			placeTile(tileType,i,j);
69
			if (tileType == 2)
70
			{
71
				levelData[i][j] = 0;
72
			}
73
		}
74
	}
75
	overlayContainer.addChild(heroPointer);
76
	overlayContainer.alpha=0.5;
77
	overlayContainer.scaleX=overlayContainer.scaleY=0.5;
78
	overlayContainer.y=290;
79
	overlayContainer.x=10;
80
	depthSort();
81
	addEventListener(Event.ENTER_FRAME,loop);
82
}
83
84
//place the tile based on coordinates

85
function placeTile(id:uint,i:uint,j:uint)
86
{
87
var pos:Point=new Point();
88
	if (id == 2)
89
	{
90
91
		id = 0;
92
		pos.x = j * tileWidth;
93
		pos.y = i * tileWidth;
94
		pos = IsoHelper.twoDToIso(pos);
95
		hero.x = borderOffsetX + pos.x;
96
		hero.y = borderOffsetY + pos.y;
97
		//overlayContainer.addChild(hero);

98
		heroCartPos.x = j * tileWidth;
99
		heroCartPos.y = i * tileWidth;
100
		heroTile.x=j;
101
		heroTile.y=i;
102
		heroPointer=new herodot();
103
		heroPointer.x=heroCartPos.x;
104
		heroPointer.y=heroCartPos.y;
105
106
	}
107
	var tile:MovieClip=new cartTile();
108
	tile.gotoAndStop(id+1);
109
	tile.x = j * tileWidth;
110
	tile.y = i * tileWidth;
111
	overlayContainer.addChild(tile);
112
}
113
114
//the game loop

115
function loop(e:Event)
116
{
117
	if (key.isDown(Keyboard.UP))
118
	{
119
		dY = -1;
120
	}
121
	else if (key.isDown(Keyboard.DOWN))
122
	{
123
		dY = 1;
124
	}
125
	else
126
	{
127
		dY = 0;
128
	}
129
	if (key.isDown(Keyboard.RIGHT))
130
	{
131
		dX = 1;
132
		if (dY == 0)
133
		{
134
			facing = "east";
135
		}
136
		else if (dY==1)
137
		{
138
			facing = "southeast";
139
			dX = dY=0.5;
140
		}
141
		else
142
		{
143
			facing = "northeast";
144
			dX=0.5;
145
			dY=-0.5;
146
		}
147
	}
148
	else if (key.isDown(Keyboard.LEFT))
149
	{
150
		dX = -1;
151
		if (dY == 0)
152
		{
153
			facing = "west";
154
		}
155
		else if (dY==1)
156
		{
157
			facing = "southwest";
158
			dY=0.5;
159
			dX=-0.5;
160
		}
161
		else
162
		{
163
			facing = "northwest";
164
			dX = dY=-0.5;
165
		}
166
	}
167
	else
168
	{
169
		dX = 0;
170
		if (dY == 0)
171
		{
172
			//facing="west";

173
		}
174
		else if (dY==1)
175
		{
176
			facing = "south";
177
		}
178
		else
179
		{
180
			facing = "north";
181
		}
182
	}
183
	if (dY == 0 && dX == 0)
184
	{
185
		hero.clip.gotoAndStop(facing);
186
		idle = true;
187
	}
188
	else if (idle||currentFacing!=facing)
189
	{
190
		idle = false;
191
		currentFacing = facing;
192
		hero.clip.gotoAndPlay(facing);
193
	}
194
	if (! idle && isWalkable())
195
	{
196
		heroCartPos.x +=  speed * dX;
197
		heroCartPos.y +=  speed * dY;
198
		heroPointer.x=heroCartPos.x;
199
		heroPointer.y=heroCartPos.y;
200
201
		var newPos:Point = IsoHelper.twoDToIso(heroCartPos);
202
		//collision check

203
		hero.x = borderOffsetX + newPos.x;
204
		hero.y = borderOffsetY + newPos.y;
205
		heroTile=IsoHelper.getTileCoordinates(heroCartPos,tileWidth);
206
		depthSort();
207
		//trace(heroTile);

208
	}
209
	tileTxt.text="Hero is on x: "+heroTile.x +" & y: "+heroTile.y;
210
}
211
212
//check for collision tile

213
function isWalkable():Boolean{
214
	var able:Boolean=true;
215
	var newPos:Point =new Point();
216
	newPos.x=heroCartPos.x +  (speed * dX);
217
	newPos.y=heroCartPos.y +  (speed * dY);
218
	switch (facing){
219
		case "north":
220
			newPos.y-=heroHalfSize;
221
		break;
222
		case "south":
223
			newPos.y+=heroHalfSize;
224
		break;
225
		case "east":
226
			newPos.x+=heroHalfSize;
227
		break;
228
		case "west":
229
			newPos.x-=heroHalfSize;
230
		break;
231
		case "northeast":
232
			newPos.y-=heroHalfSize;
233
			newPos.x+=heroHalfSize;
234
		break;
235
		case "southeast":
236
			newPos.y+=heroHalfSize;
237
			newPos.x+=heroHalfSize;
238
		break;
239
		case "northwest":
240
			newPos.y-=heroHalfSize;
241
			newPos.x-=heroHalfSize;
242
		break;
243
		case "southwest":
244
			newPos.y+=heroHalfSize;
245
			newPos.x-=heroHalfSize;
246
		break;
247
	}
248
	newPos=IsoHelper.getTileCoordinates(newPos,tileWidth);
249
	if(levelData[newPos.y][newPos.x]==1){
250
		able=false;
251
	}else{
252
		//trace("new",newPos);

253
	}
254
	return able;
255
}
256
257
//sort depth & draw to canvas

258
function depthSort()
259
{
260
	bg.bitmapData.lock();
261
	bg.bitmapData.fillRect(rect,0xffffff);
262
	var tileType:uint;
263
	var mat:Matrix=new Matrix();
264
	var pos:Point=new Point();
265
	for (var i:uint=0; i<levelData.length; i++)
266
	{
267
		for (var j:uint=0; j<levelData[0].length; j++)
268
		{
269
			tileType = levelData[i][j];
270
			//placeTile(tileType,i,j);

271
272
			pos.x = j * tileWidth;
273
			pos.y = i * tileWidth;
274
			pos = IsoHelper.twoDToIso(pos);
275
			mat.tx = borderOffsetX + pos.x;
276
			mat.ty = borderOffsetY + pos.y;
277
			if(tileType==0){
278
				bg.bitmapData.draw(grassTile,mat);
279
			}else{
280
				bg.bitmapData.draw(wallTile,mat);
281
			}
282
			if(heroTile.x==j&&heroTile.y==i){
283
				mat.tx=hero.x;
284
				mat.ty=hero.y;
285
				bg.bitmapData.draw(hero,mat);
286
			}
287
288
		}
289
	}
290
	bg.bitmapData.unlock();
291
//add character rectangle

292
}
293
createLevel();

Puntos de registro

Presta especial atención a los puntos de registro de las fichas y al héroe. (Los puntos de registro pueden considerarse como los puntos de origen para cada sprite en particular). Generalmente, estos no caerán dentro de la imagen, sino que serán la esquina superior izquierda del cuadro delimitador del sprite.

Tendremos que modificar nuestro código de dibujo para corregir los puntos de registro correctamente, principalmente para el héroe.

Detección de colisiones

Otro punto interesante a tener en cuenta es que calculamos la detección de colisión en función del punto donde se encuentra el héroe.

Pero el héroe tiene volumen y no puede representarse con precisión con un solo punto, por lo que debemos representar al héroe como un rectángulo y verificar las colisiones contra cada esquina de este rectángulo para que no haya solapamientos con otros mosaicos y, por lo tanto, no haya artefactos de profundidad.

Atajos

En la demostración, simplemente vuelvo a dibujar la escena de nuevo en cada fotograma según la nueva posición del héroe. Encontramos la ficha que ocupa el héroe y dibujamos al héroe sobre la ficha de tierra cuando los bucles de representación llegan a esas fichas.

Pero si miramos más de cerca, veremos que no hay necesidad de recorrer todas las fichas en este caso. Las fichas de hierba y las fichas de la pared superior e izquierda siempre se dibujan antes de que se dibuje el héroe, por lo que no es necesario que vuelvas a dibujarlas. Además, los azulejos de la pared inferior y derecha siempre están delante del héroe y, por lo tanto, se dibujan después de que se dibuja el héroe.

Esencialmente, entonces, solo tenemos que realizar una clasificación en profundidad entre la pared dentro del área activa y el héroe, es decir, dos fichas. Al darse cuenta de este tipo de accesos directos, podrá ahorrar mucho tiempo de procesamiento, lo que puede ser crucial para el rendimiento.


Conclusión

Por ahora, debe tener una gran base para construir sus propios juegos isométricos: puede renderizar el mundo y los objetos en él, representar datos de nivel en matrices 2D simples, convertir entre coordenadas cartesianas e isométricas, y tratar conceptos como la clasificación en profundidad y animación de personajes. ¡Disfruta creando mundos isométricos!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.