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

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

Здравствуйте, мои дорогие читатели. Это четвертая часть статей, которая посвящена разработке простенькой игры.

Сегодня мы будем разрабатывать класс списка игровых объектов TGameObjectList. Как видно из названия, этот класс содержит список объектов и обрабатывает их, либо выбранную группу. В сложных играх, объекты должны сортироваться по ID, в конце каждого игрового цикла, для быстрого доступа к определенной группе объектов (чтобы не перебирать весь массив). В нашем случае, это просто будет этакий менеджер, который будет запускать обработку действий и рисования объектов. Итак, начнем.

Создадим файл uGameObjectList с соответствующим классом:

unit uGameObjectList;

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

type
  TGameObjectList = class(TList) //будем наследовать от TList
  private
  public
    function Add(go: TGameObject): TGameObject; //добавить объект в список
    procedure Draw(id: DWORD = 0); //рисовать объекты
    procedure MakeStep(); //расчеты действий
    procedure CheckDead(); //проверка на удаление объекта из списка после смерти
    function GetCount(id: DWORD = 0): Integer; //кол-во объектов
    destructor Destroy(); override; //чистим за собой
  end;

var
  gameobjectlist: TGameObjectList;
implementation

function TGameObjectList.Add(go: TGameObject): TGameObject;
var pgo: PGameObject;
begin
  //все просто. добавляем указатель на объект в список. и возвращаем созданый объект
  result := go;
  if (result = nil) then exit;
  new(pgo);
  pgo^ := go;
  inherited Add(pgo);
end;

procedure TGameObjectList.Draw(id: DWORD);
var i: Integer;
begin
  //рисуем все объекты если id = 0 если нет рисуем объекты с заданым id
  if (id = 0) then begin
    for i := 0 to pred(Count) do
      PGameObject(Items[i])^.Draw()
  end else begin
    for i := 0 to pred(Count) do
      if (id = PGameObject(Items[i])^.getID) then
        PGameObject(Items[i])^.Draw();
  end;
end;

procedure TGameObjectList.MakeStep();
var i: Integer;
begin
  //расчет действий каждого объекта
  for i := 0 to pred(Count) do
    PGameObject(Items[i])^.MakeStep();
end;

procedure TGameObjectList.CheckDead();
var
  i: integer;
  pgo: PGameObject;
begin
  //проверяем, если объект подлежит удалению (после смерти), удаялем.
  i := 0;
  while i < Count do begin
    pgo := Items[i];
    if (pgo^.isRemove) then begin
      pgo^.Free;
      pgo^ := nil;
      Delete(i);
    end else inc(i);
  end;
end;

function TGameObjectList.GetCount(id: DWORD = 0): Integer;
var i: Integer;
begin
  //возвращаем общее кол-во объектов если id = 0 либо кол-во одинаковых объектов по id
  result := 0;
  if (id <> 0) then begin
    for i := 0 to pred(Count) do
      if (PGameObject(Items[i])^.getID = id) then inc(result);
  end else result := Count;
end;

destructor TGameObjectList.Destroy();
var i: Integer;
begin
  //вызываем деструкторы объетов и удаляем из списка

  for i := 0 to pred(Count) do
    PGameObject(Items[i])^.Free();

  while (Count > 0) do Delete(0);

  //вызываем деструктор TList
  inherited;
end;

end.

Этот класс очень прост в использовании. Далее мы это увидим. И наконец пришло время создать свою субмарину. Так как у нас объектов будет немного и AI у них будет совсем простое, будем описывать все наши модели в одном модуле, назовем его uModels. Все они будут наследовать класс TGameObject. В более сложных проектах иерархия куда сложней. Итак, создаем в этом модуле класс TSubmarine:

unit uModels;

interface
uses
  Windows, SysUtils, Classes, Graphics, uGameObject, uConstants, uGraphics, uCommon;

type
  TSubmarine = class(TGameObject)
  private
  public
    procedure MoveTo(v: Integer); //будет вызываться при обработке клавиатуры
    procedure MakeStep(); override; //расчет действий
    constructor Create();
  end;

implementation

constructor TSubmarine.Create();
begin
  inherited Create('submarine'); //конструктор абстрактного класса с именем объекта
  PreparePic(2, 2);
  //можете поиграться этими параметрами
  speed   := 3;
  pos.x   := (FieldWidth - size.x) shr 2;
  pos.y   := 200;
  id      := ID_SHIP;
  vec     := VEC_LEFT;
  life    := 2;
end;

procedure TSubmarine.MoveTo(v: Integer);
begin
  if (GetTickCount() - lasttime < 20) then exit; //ограничиваем движение временем
  vec := v; //направление
  case vec of
    VEC_LEFT: if (pos.x > 0) then dec(pos.x, speed);
    VEC_RIGHT: if (pos.x < FieldWidth - size.x) then inc(pos.x, speed);
  end;
  lasttime := GetTickCount();
end;

procedure TSubmarine.MakeStep();
begin
  //тут будем расчитывать время выстрела... а пока проверка на удаление...
  inherited MakeStep();
end;

end.

Так же нам необходим создать модуль, где мы будем хранить рабочие функции и процедуры. Назовем его uCommon:

unit uCommon;

interface
uses
  Windows, SysUtils, Classes, Graphics;

  function PinR(p: TPoint; r: TRect): Boolean;
  function RinR(r1, r2: TRect): Boolean;
  function hasIntersect(const A, B : TRect): boolean;
  function min(A, B:integer):integer;
  function max(A, B:integer):integer;
  function tstbit(bField, b: DWORD): Boolean;
  procedure setbit(var bField: DWORD; b: DWORD);
  procedure setbitbool(var bField: DWORD; b: DWORD; condition: Boolean);
  function clrbit(var bField: DWORD; b: DWORD): DWORD;

implementation

//находиться ли точка в квадрате
function PinR(P:TPoint; R: TRect ): Boolean;
begin
    result := (R.Left <= P.x) and (P.x <= R.Right ) and
             (R.Top  <= P.y) and (P.y <= R.Bottom);
end;
//находиться ли квадрат в квадрате
function RinR(r1, r2: TRect): Boolean;
begin
    result := (r1.Left  >= r2.Left) and (r1.Top >= r2.Top)and
             (r1.Right <= r2.Right)and (r1.Bottom <= r2.bottom);
end;
//пересекаются ли два квадрата
function hasIntersect(const A,B : TRect): Boolean;
var R: TRect;
begin
    result  := false;
    R.Left  := max( A.Left,   B.Left   );
    R.Right := min( A.Right,  B.Right  );
    if R.Left >= R.Right then exit;
    R.Top   := max( A.Top,    B.Top    );
    R.Bottom:= min( A.Bottom, B.Bottom );
    if R.Top  >= R.Bottom then exit;
    result := true;
end;

//ясно без слов
function min(A, B: integer): Integer;
begin
   if (A < B) then result := A else result := B
end; 

function max(A, B: integer): Integer;
begin
   if (A > B) then result := A else result := B
end;

//проверка нужного бита
function tstbit(bField, b: DWORD): Boolean;
begin
   result := (bField and b) <> 0;
end;

//установка бита
procedure setbit(var bField: DWORD; b: DWORD);
begin
   bField := bField or b;
end;

//установка бита с условием
procedure setbitbool(var bField: DWORD; b: DWORD; condition: Boolean);
begin
   if condition then bField := bField or b else bField := bField and (not b);
end;

//удаление бита
function clrbit(var bField: DWORD; b: DWORD): DWORD;
begin
   result := bfield and b;
   bfield := bfield and (not b);
end;

end.

Обычно, я выношу эти функции в отдельную dll библиотеку. Но в этом проекте обойдемся простым модулем. Теперь остается добавить изменения в класс TGame:

unit uGame;

interface

uses
  Windows, Graphics, uConstants, uGraphics, uGameObject, uGameObjectList, uModels;

type
  TGame = class
  private
    player: TGameObject;
    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'); //создаем класс отрисовки
  gameobjectlist := TGameObjectList.Create(); //создаем класс списка объектов
  player := gameobjectlist.Add(TSubmarine.Create()); //создаем кораблик) класс игрока
end;

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

procedure TGame.ProcessKey();
begin
  //обработка клавиатуры
  if (player = nil) then exit;
  if (boolean(GetKeyState(VK_LEFT) and $80)) then TSubmarine(player).MoveTo(VEC_LEFT);
  if (boolean(GetKeyState(VK_RIGHT) and $80)) then TSubmarine(player).MoveTo(VEC_RIGHT);
end;

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

  //удаляем убитые объекты
  gameobjectlist.CheckDead();

  if (GetTickCount - lasttime > 600) then begin //небольшая задержка
    //расчет действий всех объектов
    gameobjectlist.MakeStep();
    lasttime := GetTickCount();
  end;

  //тут будем рисовать наши объекты
  gameobjectlist.Draw();

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

end.

Хочется сразу сказать, что было несколько мелких изменений в модуле uGameObject, думаю вы найдете их сами. Теперь давайте добавим картинку submarine.bmp в папку images. Формат всех наших картинок будет такой:

Сперва картинка с направлением влево затем вправо, а под ними маска. Утилитку по генерации маски я выложу в следующей статье. На сегодня это все.

Исходные коды к данной статье fly (part 4) Управление: стрелки клавиатуры.

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

Ответ

  1. Анонимно:

    Скажите пожалуйста, что такое PGameObject и где мы его раньше объявляли

    Thumb up Thumb down 0

    • доброжелатель:

      type
      PGameObject = ^TGameObject;
      TGameObject = class

      при объявлении класса TGameObject в прошлом уроке возможно упущено

      Thumb up Thumb down +1

  2. Mr Donald:

    Изза чего субмарина может не менять направление? У меня она хоть и ездит в обе стороны – смотрит все равно влево

    Thumb up Thumb down 0

  3. Mr Donald:

    Ааа.. вот про какие «мелкие изменения в модуле uGameObject» вы говорили… тогда этот пост небольшой спойлер

    Thumb up Thumb down 0

  4. Саня:

    Hidden due to low comment rating. Click here to see.

    Poorly-rated. Like or Dislike: Thumb up Thumb down -4

  5. Саня:

    та ну все равно, чуть бы по подробней((((

    Thumb up Thumb down 0

    • Очень много статей написано по дельфям. Советую начать обучение именно с них. Для игр, нужно иметь представление хотя бы об одном ООП языке программирования.

  6. Lesha:

    значит при рисовании фон у объектов делать чёрным?

    Thumb up Thumb down 0

Ответить

Comments

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