Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Создание ролевой компьютерной игры 41) Квесты


Школа программирования

41) Программирование ролевой игры: Квесты

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

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

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

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

Нередко квесты следуют один за другим в цепочке - получить доступ к квесту Б можно, лишь выполнив квест А, и так далее. Мы ограничимся двумя базовыми типами квестов - уничтожением некоторого злодея и доставкой предмета. А вот кому предметы доставлять и кого спасать? Ведь у нас в игре существуют только герои и монстры.

Добавим в игру новый класс существ - назовем их мирными жителями или нейтральными персонажами. Никаких кардинальных переделок устраивать не будем, обойдемся тем, что есть - просто введем новый тип монстров, который будем в программе считать мирным, неагрессивным.

  const MaxMonsterTypes = 8;
         MonsterTypes: array[1..MaxMonsterTypes] of TMonster =
         (
  ...
  (Name: STR_MONSTER8;
   ID:8; x:0; y:0;
   HP:15; maxHP:15; XP:30; Level:-1;
   ad1:4; ad2:10; dd1:2; dd2:6;
   ViewZone:5;
   RandomStep:3)

  );

Текстовая константа:

  STR_MONSTER8 = ' Мирный житель ' ;

Обратите внимание, что значение поля Level установлено в -1, чтобы мирный житель не возникал на картах в процессе автоматической генерации. Нам также надо запретить этому типу "монстров" атаковать персонажей, а им в свою очередь - нападать на мирных жителей. Для этого создадим множество значений поля ID типа TMonster, которое будет хранить номера, относящиеся к мирным существам. Ведь, вполне возможно, нам недостаточно будет лишь одного описания для использования множества дружелюбных существ.

  const GoodMonsterSet = [8];

Способ отображения этих жителей в виде синей буквы O укажем в массиве MonsterRecords:

  const MonsterRecords: array[1..MaxMonsterTypes] of TTileRecord =
     (
     (C: ' p ' ; Clr:LightRed),
     (C: ' % ' ; Clr:Yellow),
     (C: ' @ ' ; Clr:LightGreen),
     (C: ' # ' ; Clr:LightMagenta),
     (C: ' & ' ; Clr:LightCyan),
     (C: ' j ' ; Clr:LightGray),
     (C: ' A ' ; Clr:LightBlue),
     (C: ' O ' ; Clr:LightBlue)
     );

Соответствующую проверку на мирность монстра вставим в начало процедуры HeroAttack:

  { --------------------------- }
  procedure HeroAttack( var H: THero; m: Integer );
  var i, d, dam, skin: Integer;
  begin
  if Monsters[m].ID in GoodMonsterSet then
     begin
     ShowInfo(STR_GOOD_MONSTER);
     Exit
     end;
  ...

и HeroShot:

  ...
       m := IsMonsterOnTile(x,y);
       if m > 0 then
          begin
          if Monsters[m].ID in GoodMonsterSet then
              begin
              ShowInfo(STR_GOOD_MONSTER);
              Exit
              end;
  ...

Текстовая константа:

  STR_GOOD_MONSTER = ' Это мирный житель! ' ;

Также подправим функцию CanAttack:

  { --------------------------- }
  function CanAttack(mi, hi: Integer): Boolean;
  begin
  CanAttack := (Distance( Heroes[hi].x,Heroes[hi].y,
                         Monsters[mi].x,Monsters[mi].y ) = 1) and
               (not (Monsters[mi].ID in GoodMonsterSet));
  end;

Кроме того, запретим добрым монстрам выслеживать героев:

  { --------------------------- }
  procedure MonstersAttack;
  var i,k: Integer;
  begin
  for i := 1 to MaxMonsters do
   if not (Monsters[i].ID in GoodMonsterSet) then
    for k := 1 to MaxHeroes do
     if (Monsters[i].HP > 0) and
        (Heroes[k].HP > 0) and
        CanAttack(i,k) then
        begin
        MonsterAttack(i, Heroes[k]);
        break
        end;

  end;

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

  {$I DEFINES.INC}

  unit Quest;

  interface

  implementation

  end.

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

Продолжение про квесты - в следующем занятии.

Исходный код текущей версии для Turbo Pascal (всегда проверен и работоспособен, главный файл - main.pas):

тут, 6106.zip, 17078 байтов


(c) 2004-2006 Сергей Бобровский bobrovsky as russianenterprisesolutions.com

Школа программирования с нуля
Все предыдущие выпуски базового курса всегда тут:
http://www.infiltration.ru/p/

Дизайн рассылки: Алексей Голубев - Web-дизайн и web-программирование


В избранное