Создание редактора карт. Часть 1.
Немного отвлечемся от самой игры. Чтобы в неё можно интересно играть, в ней должны быть разнообразные уровни (карты). Поэтому, наверно, правильным будет сделать сначала редактор карт для более удобного и быстрого создания уровней.
Итак, создаем новый проект. Его можно добавить в группу с самой игрой.
Проект называем MapEditor, форму frmMapEditor, либо по вашему желанию.
Для начала нам понадобятся следующие компоненты:
MainMenu – меню, из которого будет доступ к основным функциям редактора. Чтобы слишком не усложнять, в нем будет один основной пункт Карта с пунктами Новая (создание новой карты), Открыть (открытие существующей), Очистить (очистить карту), Свойства (переход к настройкам), Сохранить как… (сохранение карты), Выход (выход из программы).
DXDraw – “холст”, на котором будет отображаться карта
Panel – вспомогательный компонент, на котором можно расположить дополнительные элементы.
GroupBox – контейнер, на котором будут располагаться изображения тайлов.
9 radiobutton и 9 image – для выбора тайлов. В создании карты у нас будут использоваться 9 объектов, описанных в первой части. Размер у image делаем 32x32 и включаем свойство stretch. Первый radiobutton по умолчанию будет активным checked = true. Тут еще сделаем так: каждому radiobutton в свойство tag напишем его порядковый номер от 0 до 8. Благодаря этому мы сможем определить индекс тайла, который соответствует отмеченному флажку.
DXTimer – таймер, который будет отрисовывать карту.
· Enable = False
· Interval = 1
DXInput – компонент, предназначенный для работы с клавишами. Будет использоваться для передвижения по карте вверх/вниз и влево/вправо. В его свойствах можно настроить каким клавишам соответсвуют клавиши на клавиатуре.
DXImageList – хранение изображений-тайлов. Из него будут браться изображения при выводе на DXDraw.
· DXDraw = DXDraw1
· В TPictureCollection добавляем изображения наших тайлов, в том же порядке! Изображения выбираем размера 32x32 и выключаем свойство Transparent.
OpenDialog – диалог для открытия карты.
SaveDialog – диалог для сохранения карты.
Расположить и настроить все это хозяйство вы можете по своему усмотрению, у меня это выглядит так:
Теперь нам нужно описать класс, реализующий нашу карту. Добавляем к проекту новый модуль и называет его uTileMap. Он будет описывает нашу карту, а именно ее ширину и высоту, название карты, автора, двумерный массив с индексами тайлов в ячейках карты (как мы помним карта у нас состоит из ячеек (тайлов)), а также методы для создания, очистки, сохранения и загрузки карты. Описывать все не буду и приведу комментированный код:
unit uTileMap;
interface
uses Classes, Types;
type
TTileArray = array of array of byte; // двумерный динамический массив
TTileMap = class
private
FMapAthor: AnsiString; // имя автора карты
FMapName: AnsiString; // название карты
FMapWidth: integer; // ширина карты
FMapHeight: integer; // высота карты
FMapTilesIndex: TTileArray; // массив, хранящий индексы тайлов для ячеек карты
public
// проперти, для доступа к полям класса
property MapAthor: AnsiString read FMapAthor write FMapAthor;
property MapName: AnsiString read FMapName write FMapName;
property MapWidth: integer read FMapWidth write FMapWidth default 30;
property MapHeight: integer read FMapHeight write FMapHeight default 20;
property MapTilesIndex: TTileArray read FMapTilesIndex write FMapTilesIndex;
constructor Create(MWidth: integer = 30; MHeight: integer = 20);
// конструктор класса
destructor Destroy; // деструктор
procedure Clear; // очистка класса
procedure SaveToFile(FileName: string); // сохранение в текстовой файл
procedure LoadFromFile(FileName: string); // загрузка из текстового файла
procedure SaveToTextFile(FileName: string); // сохранение в бинарный файл
procedure LoadFromTextFile(FileName: string);
// загрузка из бинарного файла
end;
implementation
uses
SysUtils;
{ TTileMap }
procedure TTileMap.Clear;
// процедура очистки карты
// заключается в том, что индексы тайлов в массиве сбрасываются
var
i: integer;
j: integer;
begin
// заносим в массив индексы тайлов, в соответсвии с порядком на главной форме
// 0- блок, 1-твердый блок .. 10-пустота
// карта имеет границу из тведых блоков
for i := 0 to MapWidth - 1 do
begin
for j := 0 to MapHeight - 1 do
begin
MapTilesIndex[i][j] := 10;
// граница из твердых блоков
MapTilesIndex[0][j] := 1;
MapTilesIndex[MapWidth - 1][j] := 1;
end;
// граница из твердых блоков
MapTilesIndex[i][0] := 1;
MapTilesIndex[i][MapHeight - 1] := 1;
end;
end;
constructor TTileMap.Create(MWidth: integer = 30; MHeight: integer = 20);
// Cоздание карты. входными параметрами являются ширина и высота карты. По умолчанию 30x20
var
i: integer;
j: integer;
begin
// значения по умолчанию
MapAthor := 'DelphiExpert';
MapName := 'Level #';
MapWidth := MWidth;
MapHeight := MHeight;
// выделяем память для динамического массива
SetLength(FMapTilesIndex, FMapWidth, FMapHeight);
// очищаем карту, сбрасывая на значения по умолчанию
Clear;
end;
destructor TTileMap.Destroy;
begin
// освобождаем память
FreeAndNil(FMapTilesIndex);
end;
procedure TTileMap.SaveToFile(FileName: string);
// сохраняем карту в бинарный файл
var
MapFile: File;
i: integer;
j: integer;
begin
try
AssignFile(MapFile, FileName);
ReWrite(MapFile, 1);
// записываем в файл имя автора
i := Length(FMapAthor);
BlockWrite(MapFile, i, SizeOf(i));
BlockWrite(MapFile, MapAthor[1], i * SizeOf(Char));
// записываем в файл имя карты
i := Length(MapName);
BlockWrite(MapFile, i, SizeOf(i));
BlockWrite(MapFile, MapName[1], i * SizeOf(Char));
// записываем в файл размеры карты
BlockWrite(MapFile, MapWidth, SizeOf(i));
BlockWrite(MapFile, MapHeight, SizeOf(i));
// записываем в файл индексы тайлов
for i := 0 to MapWidth - 1 do
begin
for j := 0 to MapHeight - 1 do
BlockWrite(MapFile, MapTilesIndex[i, j], 1);
end;
finally
CloseFile(MapFile)
end;
end;
procedure TTileMap.LoadFromFile(FileName: string);
// считываем карту из бинарного файл
var
MapFile: File;
i: integer;
j: integer;
begin
try
AssignFile(MapFile, FileName);
Reset(MapFile, 1);
// считываем имя автора
BlockRead(MapFile, i, SizeOf(i));
SetLength(FMapAthor, i);
BlockRead(MapFile, FMapAthor[1], i * SizeOf(Char));
// считываем имя карты
BlockRead(MapFile, i, SizeOf(i));
SetLength(FMapName, i);
BlockRead(MapFile, FMapName[1], i * SizeOf(Char));
// считываем размеры карты
BlockRead(MapFile, FMapWidth, SizeOf(i));
BlockRead(MapFile, FMapHeight, SizeOf(i));
SetLength(FMapTilesIndex, MapWidth, MapHeight);
// считываем индексы тайлов
for i := 0 to MapWidth - 1 do
begin
for j := 0 to MapHeight - 1 do
BlockRead(MapFile, MapTilesIndex[i, j], 1);
end;
finally
CloseFile(MapFile);
end;
end;
procedure TTileMap.SaveToTextFile(FileName: string);
// сохраняем карту в текстовой файл
// аналогично сохранению в бинарный файл
var
MapFile: TextFile;
i: integer;
j: integer;
begin
try
AssignFile(MapFile, FileName);
ReWrite(MapFile);
Writeln(MapFile, MapAthor);
Writeln(MapFile, MapName);
Writeln(MapFile, MapWidth);
Writeln(MapFile, MapHeight);
for i := 0 to MapWidth - 1 do
begin
for j := 0 to MapHeight - 1 do
write(MapFile, MapTilesIndex[i, j]);
Writeln(MapFile);
end;
finally
CloseFile(MapFile)
end;
end;
procedure TTileMap.LoadFromTextFile(FileName: string);
// считываем карту из текстового файла
// аналогично считыванию из бинарного файла
var
MapFile: TextFile;
i: integer;
j: integer;
begin
try
AssignFile(MapFile, FileName);
Reset(MapFile);
Readln(MapFile, FMapAthor);
Readln(MapFile, FMapName);
Readln(MapFile, FMapWidth);
Readln(MapFile, FMapHeight);
SetLength(FMapTilesIndex, MapWidth, MapHeight);
for i := 0 to MapWidth - 1 do
begin
for j := 0 to MapHeight - 1 do
read(MapFile, MapTilesIndex[i, j]);
Readln(MapFile);
end;
finally
CloseFile(MapFile);
end;
end;
end.
Тут можно отметить то, что сохранять можно в текстовой и в бинарный файл. Текстовой удобнее для редактирования и просмотра, а бинарный удобней для программы. Так же можно добавить по своему желанию еще полей, например, номер версии, дату и т.п.
А теперь вернемся к нашей форме. Вкратце о том, что мы будем делать. При запуске появится основа для карты - сетка размера карты. Сбоку у нас панель с выбором тайла. При нажатии мышкой по области карты, в ячейку массива будет заноситься индекс тайла, а так же при ведении мышкой. При нажатии правой клавиши мыши, ячейка будет стираться.
Добавляем следующие поля и методы:
type
TfrmMapEditor = class(TForm)
. . .
procedure SelectTiles(Sender: TObject); // общая процедура для RadioButton, позволяющая выбирать тайл
private
{ Private declarations }
StartX, StartY: Integer; // положение, от которого будет рисоваться карта на холсте. Изменяя их, мы сможем перемещать карту по экрану.
Map: TTileMap; // сама карта
TileIndex: Byte; // индекс текущего тайла
OldTileIndex: Byte; // переменная, для запоминания индекса текущего тайла
down: Boolean; // флаг, определяющий, нажата ли клавиша мыши
MouseTileRect: TRect; // координаты ячейки под курсором мыши. По ним мы будем рисовать сетку над выделенной ячейкой.
procedure DrawTiles; // отрисовка тайлов
procedure DrawGrid; // отрисовка сетки
procedure DrawSelectGrid; // отрисовка сетки над выделенной ячейкой
public
{ Public declarations }
end;
Создание и уничтожение формы
procedure TfrmMapEditor.FormCreate(Sender: TObject);
var
i, j: Integer;
begin
// создаем карту - объект TTileMap
Map := TTileMap.Create;
// устанавливаем начальное положение, от которого будет рисоваться карта
// в данном случае карта будет рисоваться по центру холста DXDraw
StartX := ((DXDraw1.Width div 32) - Map.MapWidth) div 2 * 32;
StartY := ((DXDraw1.Height div 32) - Map.MapHeight) div 2 * 32; ;
end;
procedure TfrmMapEditor.FormDestroy(Sender: TObject);
begin
// освобождаем память
FreeAndNil(Map);
end;
Инициализация и финализация DXDraw
procedure TfrmMapEditor.DXDraw1Initialize(Sender: TObject);
begin
// включаем таймер
DXTimer1.Enabled := True;
end;
procedure TfrmMapEditor.DXDraw1Finalize(Sender: TObject);
begin
// выключаем таймер
DXTimer1.Enabled := False;
end;
Работа таймера
procedure TfrmMapEditor.DXTimer1Timer(Sender: TObject; LagCount: Integer);
begin
if not DXDraw1.CanDraw then
Exit;
DXDraw1.Surface.Fill(0);
DrawTiles; // отрисовываем тайлы
DrawGrid; // отрисовываем сетку
DrawSelectGrid; // отрисовываем сетку над выделенной ячейкой
DXDraw1.Flip;
DXInput1.Update;
// при нажатии навигационных клавиш (верх/вниз и влево/вправо) происходит перемещени карты
If isLeft in DXInput1.States then
StartX := StartX + 32;
If isRight in DXInput1.States then
StartX := StartX - 32;
If isUp in DXInput1.States then
StartY := StartY + 32;
If isDown in DXInput1.States then
StartY := StartY - 32;
end;
Процедуры отрисовки
procedure TfrmMapEditor.DrawGrid;
var
i, j: Integer;
begin
with DXDraw1.Surface.Canvas do
begin
// устанавливаем цвет и ширину карандаша, которым будем рисовать сетку
Pen.Width := 2;
Pen.Color := clGray;
// рисуем сетку
// горизонтальные линии
for i := 0 to Map.MapWidth - 1 do
begin
MoveTo(i * 32 + StartX, StartY);
LineTo(i * 32 + StartX, Map.MapHeight * 32 + StartY);
end;
// вертикальные линии
for j := 0 to Map.MapHeight - 1 do
begin
MoveTo(StartX, j * 32 + StartY);
LineTo(Map.MapWidth * 32 + StartX, j * 32 + StartY);
end;
// устанавливаем цвет и ширину карандаша, а также стиль кисти, которыми будем рисовать рамку вокруг карты
Pen.Style := psSolid;
Pen.Color := clGreen;
Brush.Style := bsClear;
// рисуем рамку - прямоугольник
Rectangle(StartX, StartY, Map.MapWidth * 32 + StartX,
Map.MapHeight * 32 + StartY);
Release;
end;
end;
procedure TfrmMapEditor.DrawSelectGrid;
begin
with DXDraw1.Surface.Canvas do
begin
// устанавливаем цвет и ширину карандаша, а также стиль кисти, которыми будем рисовать рамку вокруг карты
Pen.Color := clGreen;
Brush.Style := bsDiagCross;
Brush.Color := clGreen;
SetBkMode(DXDraw1.Surface.Canvas.Handle, Transparent);
// рисуем прямоугольник вогруг ячейки, над которой находится курсор мыши
Rectangle(MouseTileRect.Left, MouseTileRect.Top, MouseTileRect.Right,
MouseTileRect.Bottom);
Release;
end;
end;
procedure TfrmMapEditor.DrawTiles;
var
i, j: Integer;
TailIndex: Byte;
begin
for i := 0 to Map.MapWidth - 1 do
for j := 0 to Map.MapHeight - 1 do
begin
// выводим в ячейки карты тайлы из DXImageList, индексы которых соответсde.n значениям в массиве MapTilesIndex
TailIndex := Map.MapTilesIndex[i, j];
if TailIndex <> 10 then
DXImageList1.Items[TailIndex].Draw(DXDraw1.Surface, i * 32 + StartX,
j * 32 + StartY, 0);
end;
end;
Далее определим процедуры для работы с мышью.
procedure TfrmMapEditor.DXDraw1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
// если вышли ли за границу карты то ничего не делаем
if (X < StartX + 32) or (Y < StartY + 32) or
(X > Map.MapWidth * 32 + StartX - 32) or
(Y > Map.MapHeight * 32 + StartY - 32) then
Exit;
if Button = mbRight then
begin
// при нажатии правой клавишей происходит очищение ячейки TileIndex = 10
// но при этом мы запоминаем индекс текущего тайла
OldTileIndex := TileIndex;
TileIndex := 10;
end;
// заносим индекс в наш массив, определяя ячейку, над которой нажали клавишу
Map.MapTilesIndex[(X - StartX) div 32, (Y - StartY) div 32] := TileIndex;
// флаг того, что клавиша мыши нажата
// чтобы при движении мыши также происходило добавление тайлов на карту
down := True;
end;
procedure TfrmMapEditor.DXDraw1MouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
begin
// если вышли ли за границу карты то ничего не делаем
if (X < StartX + 32) or (Y < StartY + 32) or
(X > Map.MapWidth * 32 + StartX - 32) or
(Y > Map.MapHeight * 32 + StartY - 32) then
Exit;
// определяем границы прямоугольника над которым находиться курсор мыши
MouseTileRect.Left := X - (X mod 32);
MouseTileRect.Top := Y - (Y mod 32);
MouseTileRect.Right := X - (X mod 32) + 32;
MouseTileRect.Bottom := Y - (Y mod 32) + 32;
// и если клавиша мыши нажата, то тоже рисуем карту
if down then
Map.MapTilesIndex[(X - StartX) div 32, (Y - StartY) div 32] := TileIndex;
end;
procedure TfrmMapEditor.DXDraw1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
// сбрасываем флаг
down := False;
// возвращаем индекс тайла
// в случае если происходло стирание
if TileIndex = 10 then
TileIndex := OldTileIndex;
end;
Теперь опишем процедуру для выбора тайла при нажатии на соответсвующий radiobutton. И не забудьте назначить ее на метод OnClick каждого из них.
procedure TfrmMapEditor.SelectTiles(Sender: TObject);
begin
// определяем индекс тайла, соответствующего выделенному радио-кнопке
// для этого используется их свойство Tag
TileIndex := (Sender as TRadioButton).Tag;
end;
Ну вот что в итоге должно получиться.
В следующей части мы дополним возможности нашего редактора. Если есть какие-то вопросы или предложения, пишите в комментариях.
Большой выбор 3d принтеров и принтеров куосера тут - www.quadrum.ru
Похожие материалы
- Delphi исходники - игра Шашки
- Делфи исходник - игра Реверси
- Delphi исходники - Игра шашки 2
- Delphi исходники - карточный покер
- Игра убеги от кубиков