Разработка игры Fly (часть 3)

Опубликовал – 13.06.2010

Здравствуйте! Вот оно, свободное время! Предыдущие части довольно детально описывали весь процесс создания нашей игры, думаю, такой подход обеспечит наилучшее понимание происходящего.

Сегодня мы будем описывать простейший игровой объект, который будем брать за основу более сложных объектов. Создадим модуль, назовем его uGameObject и заведем в нем наш абстрактный класс TGameObject который будет описывать общие свойства всех игровых объектов.

Итак, давайте подумаем, какие, объект может иметь свойства:

  • Имя - по нему будем находить нужную графику к объекту.
  • Уникальный номер - для нахождения нужного нам объекта, или группы объектов в списке объектов.
  • Состояние - определяет действие объекта в данный момент (стреляет, стоит, летит и т.д.).
  • Размер - собственно размер самого объекта в пикселях.
  • Позиция - координаты объекта.
  • Скорость - скорость объекта в движении.
  • Направление - вектор.
  • Здоровье - кол-во здоровья объекта.
  • Жизни - кол-во жизней объекта.
  • Видимость - видимость объекта на экране.
  • Картинка - ссылка на объект TBitmap, буфер всех изображений для данного объекта.
  • Кол-во кадров анимации - вычисляется (размера буфера \ размер объекта).
  • Текущий кадр - номер кадра текущего состояния, будем использовать чтобы вырезать из буфера.
  • Прямоугольник - позиция и размер вписанного объекта в игровом мире.
  • Флаг удаления - после смерти устанавливается флаг, для удаления объекта из списка, менеджером объектов.

Это свойства, которые подходят под описание для любого объекта. Поэтому мы будем использовать абстрактный класс TGameObject для наследования этих свойств и общих, виртуальных методов, давайте их определим:

  • Отрисовка - рисование объекта относительно его координат.
  • Расчет действий - в этом методе будет происходить расчет действий и их последствий каждый кадр.
  • Установка состояния - принужденно посылать объекту действие (управление игроком например или группой объектов).
  • Инициализация - конструктор, будем присваивать свойства по умолчанию.

Думаю этого пока достаточно, чтобы реализовать то, что мы задумали.

Теперь давайте создадим модуль uGraphics, он будет содержать классы для работы с графикой. И сразу создадим два класса TImageLib и TRender. Первые будет отвечать за загрузку нужных картинок по имени объекта и следить чтобы не загружались одинаковые картинки. Второй будет управлять рисованием изображений, следить за Off Screen Buffers (внеэкранные поверхности), коих будет у нас 4:

  • Маска
  • Буфер нормального изображения - из расчета маски.
  • Конечный буфер – с уже нарисованным фоном.
  • Фон

Собственно, исходя из выше сказанного, будем править файл uConstants:

interface

const
  FieldHeight   = 360; //Высота игрового пространства
  FieldWidth    = 400; //Ширина игрового пространства

  //--состояния объектов--//
  ST_REST       = $0000; //отдыхаем
  ST_SHOT       = $0001; //стреляем
  ST_BOMB       = $0002; //скидываем бомбу
  ST_HIT        = $0010; //в нас попали

  //--константы для ImageLib--//
  ScreenCount   = 4; //кол-во поверностей
  OffScreenCount= 3; //кол-во внеэкранных поверхностей

  SCR_MASK      = 0; //буфер маски
  SCR_NORM      = 1; //буфер
  SCR_MAIN      = 2; //конечный буфер изображения
  SCR_BACK      = 3; //буфер заднего фона

implementation

end.

Итак, мы добавили константы для некоторых состояний объекта и константы внеэкранных поверхностей, для простоты обращения к ним.
Теперь, давайте откроем файл uGraphics и будем работать с ним:

unit uGraphics;

interface
uses
  Windows, SysUtils, Classes, Graphics, uConstants;

type

  //узел изображений, содержит два поля pic и name
  PImageNode = ^TImageNode;
  TImageNode = record
    pic : TBitmap;
    name: String[255];
  end;

  //библиотека изображений наследует все методы класса TList
  TImageLib = class(TList)
  private
    function Find(aname: String): TBitmap; //поиск по имени возвращает TBitmap
    function Add(aname: String): TBitmap; //добавление нового изображения если его еще нет
  public
    function Get(aname: String): TBitmap; //публичный метод получения изображения
    destructor Destroy(); override; //уничтожение всех изображений по завершению
  end;

  //класс рисования
  TRender = class
  private
    Screen: array [0..pred(ScreenCount)] of TBitmap; //внеэкранные поверхности
  public
    procedure Clear(); //очистка буферов
    procedure Draw(pos: TPoint; size: TPoint; anim: TPoint; pic: TBitmap); //рисуем объект на SCR_MAIN
    procedure Copy(canvas: TCanvas; idscreen: Integer); overload; //копируем с Screen[idscreen] на canvas
    procedure Copy(idscreendst: Integer; idscreensrc: Integer); overload; //копируем с Screen[idscreensrc] на Screen[idscreensrc]
    constructor Create(back: ShortString); //инициализация
    destructor Destroy(); override; //уничтожаем все внеэкранные поверхности
  end;

var
  imagelib: TImageLib;
  render: TRender;
implementation

{--TImageLib--}

//поиск узла в списке изображений
function TImageLib.Find(aname: String): TBitmap;
var i: Integer;
begin
  //собственно, тут все просто возвращаем nil если не нашли либо объект класса TBitmap в случае успеха
  result := nil;
  for i := 0 to pred(Count) do
    if (PImageNode(Items[i])^.name = UpperCase(aname)) then begin
      result := PImageNode(Items[i])^.pic;
      exit;
    end;
end;

//добавление нового
function TImageLib.Add(aname: String): TBitmap;
var
  pnode: PImageNode;
begin
  //тут тоже все просто, создаем новый узел в нем новую картинку, загружаем ее и возвращаем объект TBitmap
  new(pnode);
  pnode^.pic := TBitmap.Create();
  pnode^.pic.LoadFromFile('images/' + aname + '.bmp');
  pnode^.name := UpperCase(aname);
  inherited Add(pnode);
  result := pnode^.pic;
end;

//получить картинку
function TImageLib.Get(aname: String): TBitmap;
begin
  //сначало пробуем найти картинку в списке загруженых изображений
  result := Find(aname);
  if (result <> nil) then exit;
  //если этого не произошло создаем картинку в любом случае возвращаем объект TBitmap
  result := Add(aname);
end;

//удаляем все картинки
destructor TImageLib.Destroy();
var
  i: Integer;
  pnode: PImageNode;
begin
  for i := 0 to pred(Count) do begin
    pnode := Items[i];
    pnode^.pic.Free();
    dispose(pnode);
  end;
  //не забываем вызывать деструктор TList
  inherited;
end;

{--TRender--}

//создаем поверхности и указываем имя фона
constructor TRender.Create(back: ShortString);
var i: Integer;
begin
  //создаем и очищаем каждый буфер
  for i := 0 to pred(ScreenCount) do begin
    Screen[i] := TBitmap.Create();
    Screen[i].Width   := FieldWidth;
    Screen[i].Height  := FieldHeight;
    Screen[i].Canvas.FillRect(rect(0, 0, FieldWidth, FieldHeight));
  end;
  //загружаем задний фон, он меняться не будет
  Screen[SCR_BACK] := imagelib.Get(back);
end;

procedure TRender.Draw(pos: TPoint; size: TPoint; anim: TPoint; pic: TBitmap);
begin
  //вычитаем маску и рисуем готовое изображение
  BitBlt(Screen[SCR_MASK].Canvas.Handle, pos.x, pos.y, size.x, size.y, pic.Canvas.Handle, anim.X * size.x, size.y, SRCAND);
  BitBlt(Screen[SCR_NORM].Canvas.Handle, pos.x, pos.y, size.x, size.y, pic.Canvas.Handle, anim.X * size.x, 0, SRCPAINT);
  BitBlt(Screen[SCR_MAIN].Canvas.Handle, pos.x, pos.y, size.x, size.y, Screen[SCR_MASK].Canvas.Handle, pos.x, pos.y, SRCAND);
  BitBlt(Screen[SCR_MAIN].Canvas.Handle, pos.x, pos.y, size.x, size.y, Screen[SCR_NORM].Canvas.Handle, pos.x, pos.y, SRCPAINT);
end;

procedure TRender.Clear();
var i: Integer;
begin
  //чистим оффскрин буфера белым цветом
  for i := 0 to pred(OffScreenCount) do begin
    Screen[i].Canvas.Brush.Color := clWhite;
    Screen[i].Canvas.FillRect(rect(0, 0, FieldWidth, FieldHeight));
  end;
  //а буфер промежуточного рендера черным для корректной маски
  Screen[SCR_NORM].Canvas.Brush.Color := clBlack;
  Screen[SCR_NORM].Canvas.FillRect(rect(0, 0, FieldWidth, FieldHeight));
end;

procedure TRender.Copy(canvas: TCanvas; idscreen: Integer);
begin
  //копируем c внеэкранной поверхности на канвас
  bitblt(canvas.Handle, 0, 0, FieldWidth, FieldHeight, Screen[idscreen].Canvas.Handle, 0, 0, SRCCOPY);
end;

procedure TRender.Copy(idscreendst: Integer; idscreensrc: Integer);
begin
  //копируем c внеэкранной поверхности на внеэкранную поверхность по ID
  bitblt(Screen[idscreendst].Canvas.Handle, 0, 0, FieldWidth, FieldHeight, Screen[idscreensrc].Canvas.Handle, 0, 0, SRCCOPY);
end;

destructor TRender.Destroy();
var i: Integer;
begin
  //уничтожаем наши буфера
  for i := 0 to pred(OffScreenCount) do Screen[i].Free();
  inherited ;
end;

end.

Итак, мы описали два класса TImageLib и TRender, думаю к ним мы возвращаться больше не будем. Теперь открываем uGameObject и опишем класс TGameObject:

unit uGameObject;

interface

uses
  Windows, SysUtils, Classes, Graphics, uConstants, uGraphics;

type
  TGameObject = class
  private
    name  : ShortString; //имя
    id    : DWORD; //уникальный номер объекта
    state : DWORD; //состояние
    size  : TPoint; //размер
    pos   : TPoint; //позиция
    speed : Integer; //скорость
    vec   : Integer; //направление
    health: Integer; //здоровье
    life  : Integer; //жизни
    visible: Boolean; //видимость
    pic   : TBitmap; //буфер картинки
    anim  : TPoint; //кадр анимации
    animcount: TPoint; //кол-во кадров
    rc    : TRect; //прямоугольная область
    remove: Boolean; //удаление объекта
    procedure PreparePic(row, col: Integer);
  public
    procedure SetState(st: DWORD); virtual; //установка состояния
    procedure Draw(); virtual; //отрисовка
    procedure MakeStep(); virtual; //расчет действий
    constructor Create(aname: ShortString); //инициализация
  end;

implementation

//создание объекта
constructor TGameObject.Create(aname: ShortString);
begin
  inherited Create();
  name    := UpperCase(aname); //имя в верхнем регистре
  pic     := imagelib.Get(name); //получение изображения из библиотеки изображений по имени
  visible := true; //объект видим по умолчанию
  remove  := false; //удалять его не надо
  state   := ST_REST; //устанавливаем состояние на "спящее"
  anim    := point(0, 0); //кадр устанавливаем первый
  size    := point(pic.Width, pic.Height); //по умолчанию размер равен нашему буферу
  animcount := point(1, 1); //кол-во кадров ставим (1 по X и 1 по Y) по умолчанию
end;

//подготовка размеров изображения по кол-ву колонок и столбцов кадров в буфере
procedure TGameObject.PreparePic(row, col: Integer);
begin
  //кол-во анимаицции
  animcount.X := row;
  animcount.Y := col;
  //размеры
  size.X := pic.Width div animcount.X;
  size.Y := pic.Height div animcount.Y;
end;

//установка состояния
procedure TGameObject.SetState(st: DWORD);
begin
  state := st;
end;

//отрисовка
procedure TGameObject.Draw();
begin
  //определяем прямоугольник изображения нашего объекта. Они нам нужны чтоб расчитывать столкновения и для проверки видимости
  rc := rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y);
  //рисуем объект
  render.Draw(pos, size, anim, pic);
end;

//наш виртуальный метод расчета действий
procedure TGameObject.MakeStep();
begin
  //тут он будет пустой
end;

end.

Этот класс мы тоже больше не будем изменять. Давайте теперь, для наглядности, создадим объекты классов TImageLib и TRender. И выведем фон на нашу форму. Для этого возвращаемся в класс TGame:

unit uGame;

interface

uses
  Windows, Graphics, uConstants, uGraphics, uGameObject;

type
  TGame = class
  private
    //ObjLst  : TGameObjectList; //пока закомментируем
    LastTime: DWORD;
    procedure ProcessKey();
  public
    procedure MainLoop(canvas: TCanvas);
    constructor Create();
    destructor Destroy(); override;
  end;

var
  game: TGame;

implementation

constructor TGame.Create();
begin
  imagelib := TImageLib.Create(); //создаем класс библиотеки изображений
  render := TRender.Create('back'); //создаем класс отрисовки
end;

destructor TGame.Destroy();
begin
  render.Free(); render := nil; //по завершению уничтожаем в обратном порядке
  imagelib.Free(); imagelib := nil;
end;

procedure TGame.ProcessKey();
begin

end;

procedure TGame.MainLoop(canvas: TCanvas);
begin
  ProcessKey();
  render.Clear();
  //копируем копию фона в конечный буфер
  render.Copy(SCR_MAIN, SCR_BACK);

  //тут будем рисовать наши объекты

  //рисуем то что получилось на нашем канвасе
  render.Copy(canvas, SCR_MAIN);
end;

end.

Собственно, мы создали классы и объявили классу TRender имя фона ‘back’, это значит, что фоновая картинка будет находиться по такому пути ‘images/back.bmp’, поэтому добавим ее в эту папку.
Теперь, нам всего лишь необходимо создать объект класса TGame и запустить MainLoop();.
Это будет происходить в файл main.pas, давайте посмотрим:

unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, AppEvnts, uGame;

type
  TfrmMain = class(TForm)
    appEvent: TApplicationEvents;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure appEventIdle(Sender: TObject; var Done: Boolean);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

//при создании формы
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  //создаем нашу игру
  game := TGame.Create();
end;

//при закрытии формы
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  //уничтожаем игру, она будет чистить все за собой
  game.Free(); game := nil;
end;

//это наш appEvent
procedure TfrmMain.appEventIdle(Sender: TObject; var Done: Boolean);
begin
  //если объект класса TGame создан успешно запускаем главный цикл передавая канвас формы в качестве аргумента
  if game = nil then exit;
  game.MainLoop(frmMain.Canvas);
end;

end.

Давайте теперь откомпилируем все что получилось Ctrl+F9, либо запустим приложение F9. Должно получиться так:

Продолжение следует…
Исходные коды к данной статье fly (part 3)

Рассказать друзьям:
  • Добавить ВКонтакте заметку об этой странице
  • Мой Мир
  • Facebook
  • Twitter
  • Яндекс.Закладки
  • В Живую Ленту Google
  • Сто закладок
Комментарии (19) - Разработка игры Fly (часть 3)

Ответ

  1. Анонимно:

    Извините, у меня проблема, рисунок back.bmp нужно создать самой?Я сщздала, а окошко не показывает этот рисунок… что посоветуете?Юля

    Thumb up Thumb down +1

  2. Анонимно:

    У меня проблема – юнит uGame не видит юнит Main, проверял 100 раз, в uses юнита Main стоит uGame но на строчке
    52 render.Copy(canvas, SCR_MAIN);
    подсвечивает что не знает что такое canvas, что делать?

    Thumb up Thumb down +1

  3. Анонимно:

    Эх, новая проблема – ошибка цикла в модуле Graphics

    for i := 0 to pred(Count) do
    вот это место подсвечивается синим и окно с ошибкой
    Acces Violation at adress at module

    Не могу понять в чем дело :(

    Thumb up Thumb down 0

  4. Анонимно:

    наследует…

    Thumb up Thumb down 0

  5. Саня:

    ругается на это

    procedure TfrmMain.appEventIdle(Sender: TObject; var Done: Boolean);
    begin
    if game = nil then exit;
    game.MainLoop(frmMain.Canvas);
    end;

    подчёркикивает frmMain….

    Thumb up Thumb down 0

  6. red resource [url=http://sexfreevideos.net/]http://sexfreevideos.net/[/url]

    Thumb up Thumb down 0

Ответить

Comments

Перед отправкой формы: