Baner
; В начало ; Новости ; Теория ; Ресурсы ; Ссылки ; Форум ; Почта ;
Математика и физика
2D графика

   DirectDraw:
3D графика

   OpenGL:
Rambler's Top100 Rambler's Top100
Изометрия.
Довольно часто можно встретиь, использующие изометрическую проекцию. Это нечто среднее между 2D и 3D, поэтому такие игры довольно часто называют 2.5D. Вы, конечно же, знаете такие игры; это Diablo, Railroad Tycoon, Fallout и многие другие. Камера в такой проекции находится сверху и сбоку сцены, и лишена эффекта кошачьего глаза, т.е параллельные линии остаются параллельными. Чаще всего 2.5D игры делают с помощью 2D API - DirectDraw например. Но там сделана система координат, связанная с поверхностью экрана, а нам нужна полу3Dэшная система, поэтому все преобразования придется делать самому. О том как это сделать и пойдет речь в этой главе.

Первое, что нужно сделать - подготовить изображения. Наиболее оптимальным решением будет использовать изображения с соотношением длины и высоты равным 2:1. Также необходимо чтобы и длина и высота делились без остатка на 2. Лучше используйте спрайты размерами 60х30 или 64х32. Я использовал спрайты размером 60х30 и вот как они выглядели:


Изображения готовы - приступаем к программированию. Казалось бы все просто: в зависимости от того какой объект находится в определленой ячейке массива карты, рисуешь определеный спрайт. Но не тут-то было: координаты то разные. В массиве обычные - прямоугольные, а на экране должны быть кривые - косоугольные. Для сравнения приведу картинки этих самых систем:
Нужна формула преобразования. Копошился я два дня в инете: кроме формул, которые не помещаются в две строки ничего не нашел. Пришлось выводить самому. Оказалось все элементарно. Методом научного тыка удалось установить следующее. Пусть X и Y - это координаты прямоугольной карты (нашего массива), а U и V - это уже координаты косоугольной системы переведенные на экран. Тогда формулы принимают такой вид:
u = x-y + (W-1)
v = x+y
Здесь W - это ширина нашей мэпы. Почему W-1, потому что нумерация клеток идет не с 1, а с 0. Все просто, не правда ли. Если хотите понять откуда взялись эти выражения нарисуйте на листочке рядом две системы: прямую и кривую :), и внимательно посмотрите на них. У меня просто не хватает слов, чтобы объяснить это. Иногда может понадобиться обратный перевод: из косоугольных координат в прямые. Методом сложения и вычитания выведенных формул получаем искомые:
x = (u+v - W+1) / 2
y = (v-u + W-1) / 2
Теперь о том, как будет выглядеть на практике рисование нашей карты:
void CClassGraphic::DrawLand(MAP *pMap)
{
	UINT u, v, nTile;
	for (int col=0; col<MC_W; col++)
	for (int row=0; row<MC_H; row++)
	{
		nTile = pMap->Obj[col][row];
		if (nTile != MB_NOTHING)
		{
			// IL_W - это ширина спрайта
			// IL_H - его высота
			u = (col-row+MC_W-1)*(IL_W / 2);
			v = (row+col)*(IL_H / 2);
			
			ddsBack->BltFast(u, v-nTilesOffset[nTile], 
				ddsImg, (RECT*)&rcTiles[nTile],
				DDBLTFAST_SRCCOLORKEY);		
		}
	}
}
Координаты U и V умножаются на половины соответствующих размеров спрайта потому, что расстояние между соседними клетками равно именно половине соответствующих размеров. "Как же так?" - спросите вы - "Почему они не наежают друг на друга?". Все просто: рисование изометрической карты идет как бы через клетку. Именно поэтому карты хранят в прямоугольных массивах - чтобы попусту не терять половину памяти (хотя скорее из-за того, что так удобнее). Теперь о том почему вторым параметром функции BltFast является v-nTilesOffset[nTile], а не просто v. Это сделанно для того, чтобы корректно отображать объекты высота которых больше стандартной (высоты спрайта), колоны например. Координата V поднимается на величину равную разнице высоты текущего спрайта и стандартного спрайта. Таким образом рисуемое изображение оказывается как раз где надо. Просто заранее следует подготовить массив, в котором будут храниться значения смещения каждого спрайта. Зачастую необходимо отобразить объекты занимающие на карте не одну клетку, а много. Например нам нужно отобразить вот такую штуковину:

Как известно, спрайт нужно рисовать из левого верхнего угла. Вследствие поворота изометрической системы, крайней левой частью изометрического изображеия будет на самом деле являться левая нижняя часть прямоугольного изображения. Непонятно? - вернитесь к изображению алтаря: оно состоит из 9 частей (3x3), при этом читающий книгу будет повернут лицом к северу. Ну теперь думаю понятно. Так вот, механизм такой: на прямоугольной карте нижнюю левую часть обозначить как этот большой объект, а все остальные квадраты, входящие в этот объект обозначить как MB_NOTHING (то бишь ничего нету, дырка :). Тогда в цикле рисования мэпы объект нарисуется и после не будет ничем перекрываться. Что еще может понадобиться при написании Diablo III :) ? Конечно же, определение позиции курсора в изометрической системе. Что мы можем получить от Windows? Да только экранные координаты курсора. Будем переводить в этом помогут маски. Создайте что-нибудь похожее на такую картинку:

Главное, чтобы вы точно знали цвета по краям. ВНИМАНИЕ: ни в коем случае не используйте Photoshop для создания масок: он сохраняет файлы с искожением, погрешность в 1 единицу цвета вам гарантированно. Я из-за него потерял 2 дня: ничего не работало, а я искал ошибку в коде. Оказывается синий, например, был не RGB(0,0,255), а RGB(0,0,254). А по сему для создания масок используйте старый, милый PaintBrush. Так с софтом разобрались - возвращаемся к делу. Целочисленным делением координаты мыши на соответствующий стандартный размер определяем приблизительные координаты клетки в которую попал курсор, вернее координаты прямоугольной на экране клетки. Далее нахождением остатка от деления находим координаты курсора относительно той самой прямоугольной области, берем из маски пиксель с такими координатами и в зависимости от цвета смещаем координаты и получаем окончательный результат. Чтобы окончательно разобраться взгляните на иллюстрацию.

При необходимости можно перевести изо координаты в координаты карты, что я и сделал. Таким образом процедура для расчета перемещения курсора будет выглядеть примерно так:
...
hMaskBm = (HBITMAP)LoadImage(
		NULL, "LandSelMask.bmp", IMAGE_BITMAP, IL_W, IL_H,
		LR_LOADFROMFILE|LR_CREATEDIBSECTION);
hMaskDC = CreateCompatibleDC(NULL);
SelectObject(hMaskDC, hMaskBm);
...

void CClassGame::CalcMouse()
{
	const int u = (int)(mouseX / IL_W);
	const int v = (int)(mouseY / IL_H);

	m_scene.Map.MouseX = (int)(((u+v)*2-MC_W+1)/2);
	m_scene.Map.MouseY = (int)(((v-u)*2+MC_W-1)/2);

	COLORREF color;
	color = GetPixel(hMaskDC, mouseX % IL_W, mouseY % IL_H);

	if (color == RGB(255, 0, 0))	m_scene.Map.MouseX--;
	if (color == RGB(0, 255, 0))	m_scene.Map.MouseY++;
	if (color == RGB(0, 0, 255))	m_scene.Map.MouseX++;
	if (color == RGB(255,255,0))	m_scene.Map.MouseY--;
}
Ну вот, в принципе, и все.
IsoEngine Можете писать свой собственный Baldurs Gate. Для начала я подготовил небольшой изодвижок - каркас для работы.

Cкачать демку.[24 Kb]
Cкачать исходники.[39 Kb]

Используются технологии uCoz