вторник, 10 января 2012 г.

Bomberman - создаём бомбу

После примерного наброска основных принципов игры в незамудрённом виде можно заняться более подробной проработкой деталей.

Я решил заняться наиболее интересным и тем, что в моём первом варианте было реализовано слабовато - это бомба.

Далее идёт описание принятых решений по задумкам из первого отчёта и относительно подробный экскурс по проектированию и реализации бомбы.

Начну с того, что я солидарен с Altmer'ом, что "бомбермен это не тетрис и не змейка, эта игра имеет все признаки 'большой' игры" и поэтому я не поленился разработать мини отчёт характеризующий разработку данной игры:
  • Реализация. Для данной программы я выбрал итеративный подход реализации. Хотя основные требования выработал вначале и опубилковал их в первом отчёте.
  • Жизненный цикл разработки данного проекта получается более как эволюционный нежели прототипированный. Хотя элементы прототипирования присутствуют. Например набросок из первого отчёта можно смело считать подобием прототипа. Хотя при подавляющем эволюционировании я многое уже изменил в структуре и принципах взаимодействия классов.
  • Планирование проекта получается по большей части формальное заблаговременное, но так же  имеются элементы инкрементации. Т.к. я могу последовательно размышлять над добавлением или уменьшением тех или иных деталей конкретных элементов.
  • Проектирование я комбинирую с написанием кода.

Теперь нужно сказать о моих окончательных решениях в реализации некоторых деталей, вопросами о которых я задавался в первом отчёте.
  • Я изменил взгляды на цифровое представление поля, но это я поясню в следующей теме о проектировании поля боя, тем более что я это ещё не реализовал окончательно.
  • Определился с динамикой бомбы, что и буду описывать чуть ниже.
  • Модель главного героя будет 3-х мерной и в её реализации мне поможет Almost, который в отличии меня умеет моделить). Насчёт крипов пока неизвестно.
На этом с определёнными деталями пока что всё.

И так, приступим к проектированию и написанию объекта-бомбы.
Начинаю с того, что открываю заготовку приложения для разработки какой-либо 3D штучек(в этот каркас приложения у меня входит создание базы 3D пространства, камер и обработка основных контролов).

Создаю основной класс бомбы - наследника от TGLDummyCube и начинаю размышлять...
Необходимо, чтобы отображался спрайт бомбы, который будет пульсировать пока она не взорвётся по таймеру или по приказу. Таким образом создаём объекты и методы в нашем классе для этих пунктов "правильной" бомбы)

После прототипирования данных элементов класс бомбы начинает выглядить так:

Класс бомбы
unit uBomb;

interface

uses
  ...стандартные модули..., ...модули сцены..., ...свои модули...;

type
  TBomb = class(TGLDummyCube)
  private
    fBombSprite : TGLPlane;
    fRadius : smallint;
    fPulsFactor : single;
    fTimer : single;
    fExploded : boolean; // взорвалась ли
    procedure MainInit;
  public
    procedure DoProgress(const progressTime: TProgressTimes); override;
  public
    constructor CreateAsChild(aParentOwner: TGLBaseSceneObject; aRadius: smallint);
    destructor Destroy; override;
    property Radius: smallint read fRadius;
    property Exploded: boolean read fExploded;
  end;

Т.е. спрайт бомбы будет отображаться на TGLPlain'е, который будет крутиться фронтом всегда к камере(биллборд), как спрайты у систем частиц.

Теперь можно написать следующие обработчики: это создание основных элементов и биллбординг бомбы.

И вот что получилается:

Раздел реализации модуля бомбы
implementation

const

  c_MIN_SIZE = 0.7;
  c_MAX_SIZE = 1;
  c_PULS_FACTOR = 1;

{ TBomb }

constructor TBomb.CreateAsChild(aParentOwner: TGLBaseSceneObject; aRadius: smallint);
begin
  inherited CreateAsChild(aParentOwner);
  fRadius := aRadius;
  MainInit;
end;

destructor TBomb.Destroy;
begin
  FreeAndNil(fBombSprite);
  inherited Destroy;
end;

procedure TBomb.MainInit;
begin
  fBombSprite := TGLPlane.CreateAsChild(Self);
  with fBombSprite do
  begin
    Width := c_MIN_SIZE;
    Height := c_MIN_SIZE;
    Material.MaterialLibrary := BSF.MatLib;
    Material.LibMaterialName := 'bomba';
  end;
  fPulsFactor := c_PULS_FACTOR;
  fTimer := 3;
  fExploded := False;
end;

procedure TBomb.DoExplosion;
begin
  fBombSprite.Visible := False;
  fExploded := True;
end;

procedure TBomb.DoProgress(const progressTime: TProgressTimes);
var
  DTime : single;
  cam_ap : TVector;
begin
  DTime := progressTime.deltaTime;
  if not(fExploded) then
  begin
    // пульсация
    fBombSprite.Width := fBombSprite.Width + DTime * fPulsFactor;
    if fBombSprite.Width > c_MAX_SIZE then fBombSprite.Width := c_MAX_SIZE;
    if fBombSprite.Width < c_MIN_SIZE then fBombSprite.Width := c_MIN_SIZE;
    fBombSprite.Height := fBombSprite.Width;
    if fBombSprite.Width >= c_MAX_SIZE then fPulsFactor := -c_PULS_FACTOR;
    if fBombSprite.Width <= c_MIN_SIZE then fPulsFactor := c_PULS_FACTOR;
    // делаем билборд
    fBombSprite.Up.SetVector(VectorMake(0, 1, 0));
    cam_ap := Self.Scene.CurrentGLCamera.AbsolutePosition;
    fBombSprite.Direction.SetVector(VectorSubtract(cam_ap, fBombSprite.AbsolutePosition));
    // timer
    fTimer := fTimer - DTime;
    if fTimer <= 0 then
      DoExplosion;
  end;
  // обработка взрыва

end;

end.

Запускаем, проверяем - нормалёк)

Сразу хотел бы заметить, что моя версия билборда бомбы не очень корректно смотрится при наличии нескольких бомб на поле - когда камера передвигается бомбы начинают крутиться чтобы всегда смотреть на неё)
Если кто подскажет мне как правильнее сделать билборд, то я буду ток рад)

Теперь можно подумать и о взрыве.

В плане взрыва я решил немножко отойти от привычного типа взрыва классического бомбера и сделал, на мой взгляд, чутка поинтереснее.

Я сделал осколочный взрыв. Т.е. добавил в класс бомбы 4 экземпляра класса осколка, создал некий контроллер системы частиц для эффекта огненного шлейфа, прицепил к осколкам и запустил это дело посмотреть, получилось весьма красиво)

В результате класс бомбы стал выглядеть так:

unit uBomb;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls,
  GLScene, GLWin32Viewer, GLCrossPlatform, BaseClasses, GLPolyhedron,
  GLMaterial, GLCoordinates, GLObjects, GLCadencer, GLContext,
  GLRenderContextInfo, GLBitmapFont, GLWindowsFont, GLHUDObjects,
  VectorGeometry, VectorTypes, GLKeyboard,
  uBombFragment, uBaseGLSceneFrame, uSimpleUtils;

type
  TFragName = (fnNorth, fnSouth, fnEast, fnWest);

  TBomb = class(TGLDummyCube)
  private
    fBombSprite : TGLPlane;
    fNorthFragment : TBombFragment;
    fSouthFragment : TBombFragment;
    fEastFragment : TBombFragment;
    fWestFragment : TBombFragment;
    fRadius : smallint;
    fPulsFactor : single;
    fTimer : single;
    fExploded : boolean; // взорвалась ли
    fDestroyed : boolean; // уничтожена ли полностью(осколки тоже)
    procedure MainInit;
  public
    function IsFragActual(aFN: TFragName): boolean;
    function GetFragPos(aFN: TFragName): TPointFloat;
    procedure FragDoExplosion(aFN: TFragName);
    procedure DoExplosion;
    procedure DoProgress(const progressTime: TProgressTimes); override;
  public
    constructor CreateAsChild(aParentOwner: TGLBaseSceneObject; aRadius: smallint);
    destructor Destroy; override;
    property Radius: smallint read fRadius;
    property Exploded: boolean read fExploded;
    property Destroyed: boolean read fDestroyed;
  end;

И скрин этого дела:

Но о подробностях я пожалуй умолчу, чтобы всётаки сохранять некоторую интригу)

Ну вот. Вроде больше про бомбу говорить нечего) Ведь требований для её реализации оказалось совсем не много.
Но если я чего забыл, то вопросы всегда можно задать в комментах.

8 комментариев:

  1. круто!

    возможно я пропустил, но вот это как работает BSF.MatLib? видимо, BSF - глобальный объект? с чем его едят?))

    биллборды - всегда смотрят на камеру, как ее не крути... в этом их функция... поэтому конечно взрывы будут поворачиваться...

    ОтветитьУдалить
  2. Lampogolovii,
    BSF - это синглтон с главными элементами сцены.
    У меня проблемы-то не с взрывами, а с бомбами(когда ещё не взорвались), которые как хомячки начинают крутиться на камеру)
    А с огоньками-то у взрывов всё нормуль как раз) там-то функция билбордов выполняется как и в любой системе частиц ровно.

    ОтветитьУдалить
  3. Насчет биллбордов:
    Когда делал SpaceSim меня не устроил худ-спрайт (и спрайт) в качестве биллборда, в итоге делал велосипед через TGLPlane. Если интересует - могу копнуть код и выложить основные мысли.

    Выделение кода - адЪ, старый добрый turbo pascal :)

    Первые строки - про проектирование, ЖЦ, прототипирование, итерационный подход и прочее - вышли для меня наиболее актуальными - у меня завтра экзамен по проектированию информационных систем, и все эти термины до боли знакомы.

    Пойду готовиться и впервые читать 256 страниц лекций...

    ОтветитьУдалить
  4. Да я гляжу сейчас не совсем понятна моя проблемка с билбордингом бомб... тут просто надо бы наглядность показать для ясности..
    В следующей теме постараюсь заснять видео и всё будет видно-понятно.

    ОтветитьУдалить
  5. А не скажешь, зачем эта строка в DoProgress ?
    DTime := progressTime.deltaTime;

    Для чего сохраняешь в локальной переменной процедуры - ведь это лишняя итерация, ведь можно же сразу обращаться к progressTime.deltaTime ? Или я не прав ?

    ОтветитьУдалить
  6. LOLO,
    изначально у меня всё было завязано на эту переменную, поэтому я присваивание сделал чтобы просто не переписывать переменную в коде... Да и по объёму куда меньше)

    ОтветитьУдалить
  7. Monax,
    хотелось бы отдельного сообщения по BSF, есть несколько вопросов по его использованию... удобно? просто я в итоге так и не решился на создание синглтона для таких целей... через всю иерархию всегда пробрасываю библиотеку материалов и т.д... не шибко удобно, поэтому интересно услышать про впечатления о синглтоне в этом деле!
    про бомбы и взрывы ясно, но непонятно, что должно быть в итоге... если бомбы спрайты, тогда лучше вращать за камерой... но увидеть динамику и проблему на видео - это всегда нагляднее, да!

    perfect daemon,
    +1, разметка кода действительно напрягает глаза, если честно... очень трудно читать...

    ОтветитьУдалить
  8. Lampogolovii,
    я тоже раньше как-то сообщал менеджерам и т.п. главные общии объекты, но это ваще нагромождает, когда например тройное и более вложение менеджеров... А если ещё и камеру нужно использовать "где-то там" в глубине иерархии?
    Сделал синглтон - стало намного удобнее. Ещё тем более локализовал многие базовые операнты, связанные, например, с инициализацией, в самом синглтоне.

    Насчёт цветовой разметки - всегда писал в старом добром классическом стиле, он мне весьма нравится.

    ОтветитьУдалить