Delphi - объектно-ориентированный язык программирования, разработанный компанией Borland в 1995 году. Он основан на языке программирования Pascal, но имеет более расширенные возможности и добавлены новые функции.
Delphi является интегрированной средой разработки (IDE), которая позволяет разрабатывать программное обеспечение для различных платформ, включая Windows, macOS, Android и iOS. Delphi достигает многоплатформенности с помощью...
Сейчас, с появлением полностью 3х мерных технологий, этот способ представления игрового поля называется изометрический, а раньше он назывался 2,5 мерный. Но смысл остался один и тот же. Существует плоскость расположенная под неким углом к неподвижной камере. Эффект перемещения достигается скроллингом карты.
На рисунке показан вид сбоку, где N - нормаль к поверхности пространства a - угол обзора камеры.
Для начала стоит упомянуть где использовался этот способ представления : Diablo, Fallout, Gorky 17 ( хотя несколько видоизменено ). С примерами игр, я конечно могу и ошибаться.
Начнем с разработки спрайтов. В отличии от простого 2D представления, они имеют более сложную форму :
Размер выбирайте : ... 64x32, 60x30 ... 2x1. / В моем примере 60x30 /. Советую сделать спрайтов поверхности штук 30-40 на каждый тип ландшафта.
Наступило время все эти спрайты разместить. Для начала определимся, что карта хранится в массиве, а если точнее то в массиве хранятся индексы этих спрайтов. Сами спрайты будем хранить в TImageList. При написании, я использовал динамический массив составленный из списка. В примере это класс PTable и его описание находится в файле Table.pas. Там все просто:
procedureCreate(const
X, Y: integer); procedure
Put(X, Y, Number: integer); // поместить в ячейку X,Y элемент Number function
Get(X, Y: integer): integer; // получить
Первой процедурой создаем таблицу с размерами X,Y. На самом деле можно создавать только квадратные т.е. 2x2, 5x5, 100x100 ... NxN. Если хотите сделать произвольного размера то надо вводить 2 списка, но мне кажется, что и квадратной формой можно обойтись. А далее, после создания, работаем как с обычным массивом.
C Table все, хотя я может кого то немножко и озадачил, но использовать массив ( Array ) для задания карт в играх не рекомендую. Причина простая. Допустим, уровни имеют разный размер (в Heroes от 64x63 - 256x256), а с массивом Вы сможете сделать только фиксированный - т.к. размер уровня узнается уже после задания массива. Вторая причина более важна : немножко изменив класс PTable, в нем можно хранить не только индексы спрайтов. Например в каждой ячейке сохраняется информация о :
В итоге данные карты хранятся в PTable и теперь все это надо сделать это вывести их на экран. Но это задача не так проста т.к. спрайты не прямоугольной формы и следовательно имеется смещение для нечетных столбцов :
Я решил эту проблему просто. Сначала проходим цикл для четных, потом для нечетных :
procedureTIFlur.Draw(DestCanvas: TCanvas; SourceRect: TRect); var
I, J, dX, dY: integer; begin
// рисуем четные столбцы dx := 0; dy := 0; for
I := SourceRect.Left to
SourceRect.Left + SourceRect.Right do
begin
for
J := SourceRect.Top to
SourceRect.Top + SourceRect.Bottom do
begin
if
Odd(I) = False then
Resource.Draw(DestCanvas, dX, dY, Map.Get(I, J)); Inc(dY, 30); // !!!!!!!! SpriteHeigth !!!!!!! end
; dy := 0; Inc(dX, 30); // !!!!!! SpriteWidth div 2 !!!!!!! end
; // рисуем нечетные столбцы dx := 0; dy := 15; // !!!!!! SpriteHeigth div 2 !!!!!! for
I := SourceRect.Left to
SourceRect.Left + SourceRect.Right do
begin
for
J := SourceRect.Top to
SourceRect.Top + SourceRect.Bottom do
begin
if
Odd(I) = True then
Resource.Draw(DestCanvas, dX, dY, Map.Get(I, J)); Inc(dY, 30); // !!!!!!!! SpriteHeigth !!!!!!! end
; dY := 15; // !!!!!! SpriteHeigth div 2 !!!!!! Inc(dX, 30); // !!!!!! SpriteWidth div 2 !!!!!!! end
; end
;
Для наглядности приведу 2 картинки :
При наложении образуется уже нормальная картинка. Для удобства я написал класс TIFlur смотрите файл IMap.pas. Все практически тоже, что и у TFlur :
typeTIFlur = class
private
Resource: TImageList; // Ресурсы графики public
Width, Heigth: Integer; // Ширина и высота карты Map: PTable; // Массив карты развернутый в список constructor
Create(const
X, Y: integer); // ширина, высота карты procedure
LoadResource(FileName: string
); // путь к BMP ресурсу procedure
RandomGenerator; // случайное заполнение Table procedure
Draw(DestCanvas: TCanvas; SourceRect: TRect); // Canvas для вывода, // SourceRect - какой кусок карты Table выводить на экран, В КОЛ_ВАХ СПРАЙТОВ destructor
Destroy; override
; end
;
Так, только необходимые процедуры, ничего лишнего.
Пожалуй, самое сложное это определить координаты клетки где находится мышь. Ведь координаты курсора у нас X и Y, а в карте все ячейки распологаются со сдвигами. Но тут нас выручат маски. Для начала создадим такую маску :
Получим координаты мыши и целочисленным делением получим координаты клетки где находится курсор ( так мы поступали при простом виде сверху ) :
I := (X divSpriteWidth) * 2; // для четных I := (X div
SpriteWidth) * 2 - 1; // для нечетных // а Y везде одинаковый: J := Y div
SpriteHeigth;
На рисунке эти координаты 2.0
По такой не слабой формуле мы вычисляем, куда конкретно попал курсор в пределах одной маски :
dX := SpriteWidth * (Frac(X / SpriteWidth)); dY := SpriteHeigth * (Frac(Y / SpriteHeigth));
Это остато от деления умножается на ширину или высоту маски. Соответственно эти переменные ВСЕГДА лежат в пределах dX (0-60) и dY (0-30). С помощью этих координат мы можем определить цвет куда тыркнулась мышка и по цвету задать смещение. Приведу целиком тело этой процедуры.
procedureTForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var
dX, dY: Real; I, J: integer; begin
case
Odd(Screen.Left) of
False: begin
I := (X div
SpriteWidth) * 2; end
; True: begin
X := X + 30; I := (X div
SpriteWidth) * 2 - 1; end
; end
; J := Y div
SpriteHeigth; CursorX := I; CursorY := J; dX := SpriteWidth * (Frac(X / SpriteWidth)); dY := SpriteHeigth * (Frac(Y / SpriteHeigth)); case
Mask.Canvas.Pixels[Trunc(dx), Trunc(dy)] of
clRed: begin
// Красный CursorX := I - 1; CursorY := J - 1; end
; clBlue: begin
// Синий CursorX := I + 1; CursorY := J - 1; end
; clLime: begin
// зеленый CursorX := I - 1; end
; clYellow: begin
// Желтый CursorX := I + 1; end
; end
;
Теперь при перемещении мыши по экрану, мы сразу можем определить реальные координаты карты. Они на рисунке обозначены черным цветом 3.0 Для компиляции исходника потребуется компонент THeadedTimer. Там же Вы узнаете как избежать "дрожания" курсора.
Теперь пару слов о способах задания объектов для карты. Каждый движущийся объект будет иметь по 2 переменные на каждую координату. Допустим первые X и Y координаты в системе карты. А вторые 2 dX и dY пиксельные координаты. Т.е. персонаж ходит по координатам dX и dY, а взаимодействует с картой по координатам X и Y.
Хотя все это условно и все зависит от Вас.В скором времени я добавлю анимированный спрайт на карту и пару объектов. Все качаем исходник тут.