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

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

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



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

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

    Test, just a test 15 Jan 2018 в 06:36 #
    Hello. And Bye.
    VINT 12 Jun 2014 в 19:09 #
    Жду продолжения, когда будет сама игра, интересна логика столкновения с кубиками, падение когда снизу ничего нету и в этот момент столкновение с кубиками по бокам, а так же прыжок и в этот момент столкновение с кубиками!
    mr_Freedom 28 May 2013 в 00:00 #
    Ооооо, да уж спасибо за проделаную работу буду с вами писать игру!

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