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

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

Вот и выдалось время, которое я посвящу Вам, дорогие читатели, и завершению статей по разработке игры Fly. Давайте вспомним, мы остановились на том, что, необходимо реализовать столкновения, взрывы, выстрелы. Сегодня нам это под силу, так же мы научимся выводить текст на экран, своими силами, без методов класса TCanvas, который сильно тормозит вывод графики.

Ну что, приступим. Давайте для начала создадим класс TFire в модуле uModels. Он будет отвечать за обработку навигации снарядов, их анимацию (если таковая есть) и столкновения с игровыми объектами:

TFire = class(TGameObject)
private
  parrentid: DWORD;
public
  procedure MakeStep(); override;
end;

Это довольно простой класс. Он так же содержит parrentid объекта, который запустил снаряд. А вот его описание:

procedure TFire.MakeStep();
var
  i: Integer;
  pgo: PGameObject;
begin
  if (GetTickCount - lasttime < 10) then exit;

  if (anim.x < pred(animcount.x)) then
    inc(anim.x)
  else
    anim.x := 0;

  //направление выстрела
  if (vec = VEC_UP) then begin
    if (pos.y > -size.x) then
      dec(pos.y, speed)
    else
      remove := true
  end else begin
    if (pos.y < FieldHeight) then
      inc(pos.y, speed)
    else
      remove := true;
  end;

  //проверка попадания
  for i := 0 to pred(gameobjectlist.Count) do begin
    pgo := gameobjectlist.Items[i];
    if (pgo^.id = parrentid) then continue; //если объект родитель
    if (pgo^ = self) then continue; //если сама пуля
    if (pgo^.remove) then continue; //если объект подлежит удалению
    if (pgo^.id = ID_EXP) then continue; //если взрыв
    if (pgo^.id = ID_SHOT) then continue; //если выстрел(можно убрать)
    if (pgo^.id = ID_BOMB) then continue; //если бомба(можно убрать)
    if (hasIntersect(rc, pgo^.rc)) then begin
      //создаем взрыв если попали
      gameobjectlist.Add(TExp.Create(point(pos.x + size.x shr 1, pos.y + size.y shr 1)));
      setbit(pgo^.state, ST_HIT);
      remove := true;
    end;
  end;

  lasttime := GetTickCount();
end;

Дальше создадим два класса TShot и TBomb. Первый будет стрелять пульками вниз или вверх, второй скидывать бомбу. Эти классы будут наследовать класс TFire, для обработки состояния пули, а сами всего лишь будут инициализировать размеры, скорость и имя снаряда:

TShot = class(TFire)
public
  constructor Create(parid: DWORD; p: TPoint; v: Integer);
end;

TBomb = class(TFire)
public
  constructor Create(parid: DWORD; p: TPoint);
end;

Вот описания их конструкторов:

constructor TShot.Create(parid: DWORD; p: TPoint; v: Integer);
begin
  if (v = VEC_UP) then
    inherited Create('shotup') //создаем пулю вверх
  else
    inherited Create('shotdown'); //либо вниз от самолета
  PreparePic(1, 2);
  parrentid := parid;
  pos     := p;
  vec     := v;
  speed   := 3;
  id      := ID_SHOT;
end;

constructor TBomb.Create(parid: DWORD; p: TPoint);
begin
  inherited Create('bomb'); //бомба
  PreparePic(4, 2); //имеет анимацию
  parrentid := parid;
  pos     := p;
  vec     := VEC_DOWN;
  speed   := 2;
  id      := ID_BOMB;
  anim.X  := 0;
end;

Теперь прописываем проверку столкновений у объектов TPlane и THelicopter в методе MakeStep(). В идеале, чтобы не делать такую проверку каждый кадр, размечается область карты сеткой. При движении объекта двигаем объекты по сетке и в это время проверяем ее содержимое. Но для данной игры наш подход вполне подходит, даже при большом количестве объектов:

//проверка столкновений
for i := 0 to pred(gameobjectlist.Count) do begin
  pgo := gameobjectlist.Items[i];
  if (pgo^ = self) then continue;
  if (pgo^.remove) then continue;
  if (pgo^.id = ID_BOMB) then continue;
  if (pgo^.id = ID_SHOT) then continue;
  if (pgo^.id = ID_EXP) then continue;
  if (hasIntersect(rc, pgo^.rc)) then begin
    //создаем взрыв если столкнулись
    gameobjectlist.Add(TExp.Create(point(pos.x + size.x shr 1, pos.y + size.y shr 1)));
    setbit(pgo^.state, ST_HIT);
    setbit(state, ST_HIT);
  end;
end;

Наши объекты стали вполне самостоятельны. При столкновении или попадании умирают. Но давайте сделаем взрывы, для более эффектного зрелища. Создаем класс TExp в модуле uModels, он будет просто создавать анимацию взрыва на месте столкновения объектов, а после проигрывания анимации удалять себя из списка объектов:

TExp = class(TGameObject)
public
  procedure MakeStep(); override;
  constructor Create(p: TPoint);
end;

Вот его описание:

constructor TExp.Create(p: TPoint);
var
  r: Integer;
const
  //разные взрывы, кол-во кадров
  exps: array[0..5] of Integer = (
    30, 10, 10, 30, 30, 60
  );
begin
  r     := random(6);
  inherited Create('exp' + IntToStr(r));
  PreparePic(exps[r], 2);
  id    := ID_EXP;
  vec   := 0;
  pos   := point(p.x - size.x shr 1, p.y - size.y shr 1);
end;

procedure TExp.MakeStep();
begin
  if (GetTickCount() - lasttime < 50) then exit;

  //проигрываем анимацию. и удаляем объект
  if (anim.x < pred(animcount.x)) then
    inc(anim.x)
  else
    remove := true;

  lasttime := GetTickCount();
end;

У нас будет 6 разных картинок с анимацией взрывов с именами exp0..exp5. Мы просто хаотично при создании объекта выбираем номер файла.
Давайте теперь поработаем над выводом текста. У нас имеется картинка со шрифтом, тоже обязательно с маской. У меня он выглядит так:

Напишем вывод текста. Возвращаемся в файл uGraphics и в класс TRender добавляем метод:

procedure TextOut(pos: TPoint; str: string);

опишем его:

procedure TRender.TextOut(pos: TPoint; str: string);
var i : Integer;

  //вытаскиваем символ и рисуем его
  procedure OutChar (X, Y: Integer; ch : Char; PosX: Integer);
  var
    chRect : TRECT;
    wrkI : integer;
  begin
    wrkI          := ord(ch) - 33;
    chRect.Left   := wrkI mod 101 * 10;
    chRect.Top    := 0;
    chRect.Right  := chRect.Left + 10;
    chRect.Bottom := chRect.Top + 16;

    BitBlt(Screen[SCR_MASK].Canvas.Handle, X + PosX, Y, 10, 16, Font.Canvas.Handle, chRect.Left, 16, SRCAND);
    BitBlt(Screen[SCR_NORM].Canvas.Handle, X + PosX, Y, 10, 16, Font.Canvas.Handle, chRect.Left, 0, SRCCOPY);
    BitBlt(Screen[SCR_MAIN].Canvas.Handle, X + PosX, Y, 10, 16, Screen[SCR_MASK].Canvas.Handle, x + posx, y, SRCAND);
    BitBlt(Screen[SCR_MAIN].Canvas.Handle, X + PosX, Y, 10, 16, Screen[SCR_NORM].Canvas.Handle, x + posx, y, SRCPAINT);
  end;

begin
  //рисуем строку
  for i := 1 to length (str) do
    OutChar (pos.X, pos.Y, str [i], (i - 1) * 10);
end;

Предварительно добавим в этот класс картинку шрифта Font: TBitmap; и проинициализируем ее в конструкторе:

Font := imagelib.Get('font');

Использовать этот метод очень просто, я создал в классе TGameObjectList метод сбора статистики и пару переменных, давайте их выведем на экран. Открываем модуль uGame и в MainLoop() пишем:

//выводим текст на экран
if (gameobjectlist.GetCount(ID_SHIP) <> 0) then begin
  render.TextOut(point(10, FieldHeight - 70), 'life: ' + inttostr(player.life));
  render.TextOut(point(10, FieldHeight - 50), 'count obj: ' + inttostr(gameobjectlist.count));
  render.TextOut(point(10, FieldHeight - 30), 'die: ' + gameobjectlist.GetStatistics());
end else begin
  render.TextOut(point((FieldWidth - 90) shr 1, 200), 'Game Over');
end;

Довольно просто, не так ли?! Теперь, давайте откомпилируем наш проект и посмотрим что получилось.

На этом, серия по созданию игры Fly подошла к концу. Желаю Вам творческих успехов и новых идей.

p.s. Ради интереса поменяйте значения констант MAX_PLANE и MAX_HELI в файле uConstants на значение побольше, увидите много взрывов в небе, очаровательное зрелище! Удачи!

Исходные коды к данной статье fly (part 6). Кнопки управления – стрелки клавиатуры, пробел.

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

Ответ

  1. Анонимно:

    а как размечать область сеткой? Разъясните пож-та этот метод

    Thumb up Thumb down +2

    • Очень просто. Создаем класс TGameField например. Которому отдаем управление за загрузкой фона. Он так же будет иметь сетку (map[n][m]) где n – ширина игрового поля \ ширину самого маленького игрового объекта в игре, и m – высота игрового поля \ высоту самого мальнького объекта в игре. Или можно взять за основу размеров ячейки самый маленький квадрат например 16х16. Дальше все просто берем наш самолет например, и вписываем его в эту сетку, от начала его координат кратной шагу сетки по его длинну кратной сетке. Таким образом мы заполнили сетку. И при перемещении другого объекта в одну из занятых ячеек произойдет столкновение.

  2. Анонимно:

    Скажите, где уничтожаются самолеты и вертолет при выстрелах и столкновениях?

    Thumb up Thumb down 0

    • самолеты например уничтожаются

        if (vec = VEC_UP) then begin
      	    if (pos.y > -size.x) then
      	      dec(pos.y, speed)
      	    else
      	      remove := true
      

      при залете за экран..
      вообщем за удаление объекта отвечает флажок remove := true;

      • Анонимно:

        У меня самолеты и вертолет не уничтожаются при столкновении, а если remove := true засунуть в обработчик столкновения то будет уничтожаться только один самолет из двух столкнувшихся, второй улетит…
        На вертолеты это вообще не распространяется

        Thumb up Thumb down 0

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

            if (tstbit(state, ST_HIT)) then begin
              clrbit(state, ST_HIT);
              dec(life);
              if (life < = 0) then remove := true;
            end;
          

          а вот тут столкновение:

            for i := 0 to pred(gameobjectlist.Count) do begin
              pgo := gameobjectlist.Items[i];
              if (pgo^ = self) then continue;
              if (pgo^.remove) then continue;
              if (pgo^.id = ID_BOMB) then continue;
              if (pgo^.id = ID_SHOT) then continue;
              if (pgo^.id = ID_EXP) then continue;
              if (hasIntersect(rc, pgo^.rc)) then begin
                gameobjectlist.Add(TExp.Create(point(pos.x + size.x shr 1, pos.y + size.y shr 1)));
                setbit(pgo^.state, ST_HIT); //объекту, с которым столкнулись
                setbit(state, ST_HIT); //наш объект
              end;
            end;
          
  3. Mr Donald:

    Не работает анимация бомбы

    Thumb up Thumb down 0

Ответить

Comments

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