Advertisement
  1. Code
  2. Coding Fundamentals
  3. Game Development

Создание изометрических миров: Руководство для разработчиков игр

Scroll to top
Read Time: 18 min

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

В этом уроке я сделаю обзор того, что вам нужно знать для создания изометрических миров. Вы узнаете, что такое изометрическая проекция и как представить изометрические уровни в виде 2D массивов. Мы определим отношения между представлением и логикой, так что мы сможем легко управлять объектами на экране и контролировать взаимодействие структурных плиток. Мы также немного углубимся в процесс анимации персонажа.

Полезные статьи

Хотите найти еще больше статей на тему создания изометрических миров? Обратите внимание на следующие статьи, Создание изометрических миров: Руководство для разработчиков игр, её продолжение и Книга Juwal, Основы разработки игр.


1. Изометрический мир

Изометрическая проекция представляет собой метод, используемый для создания иллюзии 3D, или другими словами 2D игры - которая иногда называется псевдо 3D или 2.5D. Эти изображения, (взяты из Diablo 2 и Age of Empires) показывают, что я имею в виду:

diablodiablodiablo

Diablo 2
AOEAOEAOE

Age of Empires

Реализация изометрической проекции может быть выполнена разными способами, но для простоты я сосредоточусь на плиточном подходе, который является наиболее эффективным и широко используемым методом. Я наложил на каждый скриншот выше сетку, которая показывает как местность разделена на плитки.


2. Плиточные игры

В плиточном подходе каждый визуальный элемент разбивается на более мелкие куски, называемые плитками, которые имеют одинаковый размер. Эти плитки будут создавать игровой мир согласно заранее определенному порядку данных - обычно это 2D массив.

Для примера давайте рассмотрим стандартное представление 2D с двумя плитка - плитка травы и плитка стены - как показано здесь:

base 2d tiles

Несколько простых плиток

Эти плитки одинакового размера, квадратной формы, поэтому высота и ширина у плиток одинаковы.

Для уровня с пастбищем, со всех сторон окруженным стенами двумерный массив данных будет выглядеть следующим образом:

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]]

Здесь 0 обозначает плитки травы и 1 обозначает плитку стены. Организация плиток в соответствии с данными создаст примерно такой результат, как на картинке ниже:

2d level simple

Простой уровень, в представлении вида сверху.

Мы можем усложнить это добавив плитки углы и отдельные вертикальные и горизонтальные плитки стен, это потребует пять дополнительных видов плиток:

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

Усложнённый уровень с номерами плиток

Я надеюсь, что теперь концепция подхода, основанного на плитках понятна. Это реализация простой 2D сетки, которую мы могли бы представить в виде кода следующим образом:

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)

Предположим, что ширина и высота плиток одинаковы (это верно для всех плиток) и соответствует разрешению изображения плитки. Итак, ширина и высота плитки для нашего примера 50px, а а размер всего уровня 300x300px - то есть, шесть рядов и шесть столбцов по 50x50px для каждой плитки.

Обычно, в соответствии с плиточным подходом, мы реализуем вид сверху или сбоку; для изометрического вида нам нужно использовать изометрическую проекцию.


3. Изометрическая проекция

Лучшее техническое объяснение того, что означает "изометрическая проекция", насколько мне известно, размещено в статье Clint Bellanger:

Мы меняем угол камеры по двум осям (смещаем камеру на 45 градусов в одну сторону, затем на 30 градусов вниз). Это создает похожую на алмаз (ромб) сетку, где пространство удвоено в ширину, так как оно вытянуто. Этот стиль был популярен в RPG и стратегических играх. Если мы посмотрим на куб в этом представлении, видны три стороны (верх и две лицевые стороны).

Хотя это звучит немного сложно, на самом деле реализация этого представления очень проста. То, что нам нужно понять, это отношение между 2D пространством и изометрическим - то есть, связь между уровнем данных и представлением; преобразование от прямых "Декартовых" координат к изометрических координатам.

the_isometric_gridthe_isometric_gridthe_isometric_grid

Декартова сетка против Изометрической.

(Мы не рассматриваем технику шестиугольных плиток, которая является еще одним способом реализации изометрических миров).

Размещение изометрических плиток

Позвольте мне упростить связь между уровнем данных, который хранится как двумерный массив и изометрической проекцией - это то, как мы преобразовываем декартовы координаты в изометрические координаты.

Мы постараемся создать изометрическую проекции для данных нашей стены пастбища:

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]]

В этом случае мы можем определить зону по которой можно перемещаться, проверяя, является ли элемент массива 0, определяя тем самым плитку с травой. 2D представление уровня было простой итерацией с двумя циклами, разместившей квадратные плитки, с отступами друг от друга с фиксированной высотой и шириной.

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)

Для изометрическая проекции код остается тем же самым, но изменится функция placeTile().

Для изометрического вида необходимо вычислить соответствующие координаты внутри цикла.
Уравнения чтобы выполнить это будут такими, где isoX и isoY представляют изометрические x - и y координаты, а cartX и cartY декартовы координаты x и 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;

Эти функции показывают как вы можете их конвертировать из одной системы в другую:

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
}

Псевдокод для цикла выглядит следующим образом:

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

Наши стены пастбища в изометрической проекции.

В качестве примера давайте посмотрим, как обычная 2D позиция преобразуется в изометрическую:

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];

Аналогичным образом, указание [0,0] приведет к результату [0, 0] а [10, 5] выдаст результат [5, 7,5].

Вышеуказанный метод позволяет нам создать соответствие между 2D уровнем данных и изометрическими координатами. Мы можем найти координаты плиток из данных декартовых координат с помощью следующей функции:

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
}

(Предполагается, что ширина высота и плитка плитка равны, как и в большинстве подобных случаев).

Следовательно из пары координат экрана (изометрических), мы можем найти координаты плитки путем выполнения команды:

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

Точкой экрана может быть, например, положение клика мышкой или какая-либо другая позиция.

Совет: Другим способом размещения является модель Зигзага, которая использует совсем другой подход.

Перемещение в изометрических координатах

Движение это очень просто: вы манипулируете данными вашего игрового мира в декартовых координатах и используете вышеупомянутые функции для обновления результата на экране. Например, если вы хотите переместить персонаж вперед по оси y, можно просто увеличить значение y и затем преобразовать позицию в изометрические координаты:

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

Создание глубины

Помимо обычного размещения объектов нам нужно будет позаботиться о создании глубины в процессе рисования изометрического мира. Это гарантирует, то что элементы которые ближе к игроку прорисовываются поверх следующих элементов.

Простейший метод создания глубины, это просто использовать значение декартовой координаты y, как уже упоминалось в этом Совете: чем дальше объект на экране, тем раньше он прорисовывается. Это работает хорошо до тех пор, пока у нас не появляются спрайты, которые занимают больше, чем одну плитку пространства.

Наиболее эффективный способ создание глубины для изометрических миров — разбить все плитки на стандартные плитки одинакового размера и не допускать больших изображений. Например, вот плитка, которая не вписывается в стандартные размер - посмотрите, как мы можем разделить её на несколько фрагментов, которые соответствуют размерам плитки:

split big tilesplit big tilesplit big tile

Большое изображение делится на несколько фрагментов стандартного изометрического измерения

4. Создание рисунков

Изометрические искусство может быть пиксельным, но не обязательно. Когда имеешь дело с изометрическим пиксельным рисунком, руководство RhysD расскажет вам почти все, что нужно знать. Некоторые техники можно найти на Википедии.

При создании изометрического рисунка, общие правила таковы

  • Начинайте с пустой изометрические сетки и придерживайтесь точности в пикселях.
  • Попробуйте разделить рисунок на несколько одиночных плиток.
  • Убедитесь, что каждая плитка определена как свободная для передвижения или нет. Будет трудно, если нам нужно разместить один элемент, содержащий как свободную, так и закрытую для перемещений области.
  • Для большинства плиток нужно будет создать плавный переход в одном или нескольких направлениях.
  • Тени сложно реализовать, пока мы не применим многослойный подход, в котором мы нарисуем слой тени на земле и нарисуем героя (или деревья, или другие объекты) на верхнем слое. Если подход, который вы используете не многослойный, убедитесь, что тени падают вперёд, таким образом, чтобы они не падали, например, на героя, когда он стоит за деревом.
  • В случае, если вам нужно использовать изображение плитки больше стандартных размеров, попробуйте использовать разрешение, кратное размеру плитки. Лучше применять многоуровневый подход в таких случаях, где рисунок можно разделить на несколько частей, в соответствии с его высотой. Например, дерево может быть разделено на три части: корень, ствол и листья. Это делает процесс создания глубины проще, так как мы можем нарисовать части на слоях, соответствующих их высоте.

Изометрические плитки, которые больше, чем размеры одной плитки создадут проблемы при создании глубины. Некоторые вопросы рассматриваются в этих статьях:


5. Изометрические персонажи

Реализация персонажей в изометрической проекции не такая сложная, как может показаться. Изображение персонажа должно быть создано согласно определенным правилам. Сначала нам нужно будет определить, какие направления движения допускаются в нашей игре - обычно игры поддерживают четырёхсторонний или 8-и сторонний способ передвижения.

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

8-и сторонний способ передвижения в изометрической проекции.

Для вида сверху мы могли бы создать набор анимаций персонажей, задействованный в одном направлении и просто поворачивать их для всех остальных сторон. Для изображения изометрического персонажа нам нужно перерисовать каждую анимацию для каждом из возможных направлений - так для восьми сторон движения нам нужно создать восемь анимации для каждого действия. Для простоты понимания мы обычно обозначаем направления как Север, Северо-запад, Запад, Юго-Запад, Юг, Юго-Восток, Восток и Северо-Восток, против часовой стрелки, в данном случае.

spriteSheetspriteSheetspriteSheet

Изометрический персонаж перемещается в различных направлениях.

Мы размещаем персонажа таким же образом, как мы размещаем плитки. Движения персонажа осуществляется путем расчета движения в декартовых координатах и преобразования их в изометрические координаты. Предположим, что мы используем клавиатуру для управления персонажем.

Мы установим две переменные, dX и dY, в зависимости от нажатой кнопки. По умолчанию эти переменные будут равны 0 и будут обновлены в соответствии с указаниями ниже, где U, D, R и L указывают вверх, вниз, вправо и влево, соответственно. Значение 1 под соответствующим ключом говорит о нажатии; 0 означает, что кнопка не нажата.

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

Теперь используя значения dX и dY, мы можем обновить декартовы координаты:

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

Итак, dX и dY ожидают изменения в x - и y позициях персонажа, основываясь на нажатия клавиш.

Мы можем легко вычислить новые изометрические координаты, как мы уже обсуждали:

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

После того, как у нас есть новые изометрические координаты, мы должны переместить персонаж в эту позицию. На основе значений, которые мы получили для dX и dY, мы можем решить, куда направляется персонаж и использовать соответствующее изображение.

Обнаружение взаимодействий

Обнаружение взаимодействий осуществляется проверкой того, является ли новая позиция плитки возможной или нет. Когда мы вычисляем новую позицию, мы не сразу перемещаем туда персонажа, а сначала проверяем, какие плитки занимают это пространство.

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

В функции isWalkable() мы проверяем, является ли значение массива данных по заданным координатам свободной плиткой или нет. Мы должны обновить направление, в котором стоит персонаж - даже если он не двигается, как в случае его взаимодействия с непроходимой плиткой.

Глубина для персонажей

Рассмотрим персонаж и плитку  дерево для изометрического мира.

Для правильного понимания глубины, мы должны знать, что всякий раз, когда x - и y координаты персонажа  меньше, чем у дерева, дерево перекроет персонаж. Всякий раз, когда x - и y координаты персонажа больше, чем дерева, персонаж перекроет дерево.

Когда они имеют одинаковые x координаты, то мы решаем, основываясь на единственном y координате: у кого он больше, тот и перекроет другого. Когда они имеют одинаковые y координаты, то мы решаем, основываясь на единственном x координате: у кого он больше, тот и перекроет другого.

Чтобы проще понять это, последовательно нарисуйте уровень, начиная от дальних плиток - то есть, tile[0][0] - затем нарисуйте все плитки ряд за рядом. Если персонаж занимает плитку, мы сначала нарисуем плитки земли и затем отобразить плитку персонажа. Это будет работать, так как персонаж не может занимать плитку стены.

Глубина должна вычисляться каждый раз, когда изменяется позиции любой плитки. Например, мы должны это сделать, всякий раз, когда персонаж двигается. Мы обновляем сцену, после выполнения проверки глубины, чтобы отразить все изменения.


6. Вперёд!

Теперь используем ваши полученные знания для создания эффективного, рабочего прототипа, с управлением клавиатурой, правильной глубиной и вычислением взаимодействий. Вот мой пример:

Нажмите, чтобы SWF был в фокусе, а затем используйте клавиши со стрелками. Нажмите здесь чтобы получить полную версию.

Это класс может быть полезным (я написал его на AS3, но вы должны быть в состоянии понять на любом другом языке программирования):

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
}

Если вы запутались, вот код моего демо (в виде кода Flash и 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();

Точки соприкосновений

Уделите особое внимание точкам соприкосновения плитки и героя. (Точки соприкосновения можно рассматривать как точки запуска для каждого конкретного спрайта.) Они обычно не располагаются внутри изображения, но чаще всего это будет левый верхний угол прямоугольника спрайта.

Мы должны изменить наш код прорисовки, чтобы исправить точки соприкосновения, главным образом для героя.

Обнаружение взамодействий

Еще один интересный момент, который стоит отметить, то что мы можем вычислить взаимодействия, основываясь на точке, где находится герой.

Но герой имеет объем и не может быть представлен как точка, поэтому мы должны представить героя как прямоугольник и проверить возможные взаимодействия для каждого из его углов, чтобы избежать перекрытия другими плитками и следовательно отсутствия глубины.

Хитрости

В демоверсии я просто перерисовываю каждый кадр сцены на основе новой позиции героя.  Мы находим плитку, которую герой занимает и рисуем героя поверх плитки земли, когда цикл рендеринга достигает этих плит.

Но если мы посмотрим поближе, мы обнаружим, что в этом случае нет необходимости перебирать все плитки. Плитка травы и верхняя и левая плитки всегда рисуются до того, как герой нарисован, поэтому нам вообще не нужно их перерисовывать.  Кроме того, нижняя и правая плитки стены всегда впереди героя и, следовательно, нарисованы после того, как герой нарисован. 

По существу, тогда нам нужно только создать глубину между стенкой внутри активной области и героем, то есть двумя плитками.  Использование таких приёмов поможет вам сэкономить много времени, что может иметь решающее значение для производительности.


Заключение

К настоящему времени у вас должна быть отличная основа для создания собственных изометрических игр: вы можете отображать мир и объекты в нем, представлять данные уровня в простых 2D-массивах, преобразовывать между декартовыми и изометрическими координатами и разбираться с такими понятиями, как создание глубины и анимация персонажей.  Наслаждайтесь созданием изометрических миров!

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 Code 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.