Создание редактора карт для игры

Опубиковано: 27.05.2013 г., автор: , просмотров: 12920

Создание редактора карт. Часть 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



Похожие материалы

Последние из рубрики

VINT 12 Июн 2014 в 19:09 #
Жду продолжения, когда будет сама игра, интересна логика столкновения с кубиками, падение когда снизу ничего нету и в этот момент столкновение с кубиками по бокам, а так же прыжок и в этот момент столкновение с кубиками!
mr_Freedom 28 Май 2013 в 00:00 #
Ооооо, да уж спасибо за проделаную работу буду с вами писать игру!

ОтменитьДобавить комментарий