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

Создание ролевой компьютерной игры 36) Отыгрыш партией


36) Отыгрыш партией

Партия

До сих пор мы обходились одним героем, хотя в программу везде заложена потенциальная возможность управления группой (говорят - партией) персонажей. Покажем, как от одного героя перейти к произвольному числу, не исключено, и объемной армии "своих" персонажей (что, впрочем, может перевести игру из разряда ролевых в разряд стратегических). Пусть в игре у нас сможет действовать три персонажа. Для этого прежде всего увеличим до 3 значение константы-размера массива игроков Heroes:

  const MaxHeroes = 3;
  var Heroes: array[1..MaxHeroes] of THero;
  ...

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

В момент генерации героя необходимо заполнить все элементы массива Heroes. В принципе, делать это можно и в другие игровые моменты, как нередко происходит в коммерческих играх - персонажи присоединяются к герою не сразу. Для этого каким-то несложным способом достаточно определить, существует ли i-й герой (элемент массива Heroes) или же его можно не учитывать. Можно в типе THero ввести специальное поле, можно обойтись, например, условным признаком (если значение Level меньше нуля, значит, соответствующий слот массива Heroes пуст).

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

В процедуре GenerateHero создаем клоны:

  ...

  for i := 2 to MaxHeroes do
    begin
    Heroes[i] := Heroes[1];
    SetHeroVisible(i);
    end;

  SetHeroVisible(CurHero);
  end;

Визуализацию всех героев мы делаем, чтобы они были исходно видны на карте.

Вместо одного героя выведем на карте всех персонажей сразу. Сейчас единственный главный персонаж выводится с помощью процедуры ShowHero с номером нужного персонажа. Создадим в процедуре ShowGame дополнительный цикл с проверкой видимости i-го героя с точки зрения текущего персонажа:

  { ----------------- }
  procedure ShowGame;
  var i: Integer;
  begin
  ShowMap;
  ShowItems;
  ShowMonsters;
  for i := 1 to MaxHeroes do
    if VisiblePoint(Heroes[i].x,Heroes[i].y) then
      ShowHero(i);
  ShowHeroInfo(CurHero);
  end;

Только, чтобы различить "текущего" героя (определяемого значением переменной CurHero) от всех остальных, откорректируем процедуру ShowHero.

  { ----------------- }
  procedure ShowHero(HeroNum: Integer);
  begin
  GoToXY(
  WINDOW_LEFT+Heroes[HeroNum].x-GameMap[CurMap].LocalMapLeft,
          
  WINDOW_TOP+Heroes[HeroNum].y-GameMap[CurMap].LocalMapTop );

  if HeroNum = CurHero
     then TextColor( White )
     else TextColor( Yellow );
  Write( ' X ' )
  end;

Главный на данный момент герой покажется на карте белым, любой другой партиец - желтым.

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

Чтобы режим партийной игры стал действовать, остается реализовать возможность переключения от одного персонажа к другому. Потенциально это сделать легок (достаточно лишь изменить значение переменной CurHero!), а "привяжем" мы такое изменение к клавише Tab.

  case k of

  ...

    #9: NextHero;

Процедура NextHero (модуль Game)

  { ----------------- }
  procedure NextHero;
  begin

  end;

должна просто корректно изменить значение текущего игрового персонажа - перейти к следующему герою. Только для прокрутки карты к новому персонажу понадобится вызов процедуры ScrollMap.

  { ----------------- }
  procedure NextHero;
  begin
  inc(CurHero);
  if CurHero > MaxHeroes then
     CurHero := 1;
  ScrollMap;
  end;

Внесем изменения и в процедуру MonstersStep, где возможность атаки пока рассматривается лишь в отношении лишь текущего персонажа. Мы расширим спектр рассматриваемых монстрами в качестве потенциальных жертв объектов всеми живыми героями:

  { ----------------- }
  procedure MonstersStep;
  var i, k: Integer;
  begin
  for i := 1 to MaxMonsters do
   for k := 1 to MaxHeroes do
    if (Monsters[i].HP > 0) and
       (Heroes[k].HP > 0) and
       CanTrace(i, k) then
       begin
       MonsterStep(i, k);
       break
       end;

  MonstersAttack;
  end;

Вложенный цикл со счетчиком k перебирает всех персонажей и проверяет, может ли за ним охотиться i-й монстр. Схожую корректировку надо внести и в процедуру атаки:

  { --------------------------- }
  procedure MonstersAttack;
  var i,k: Integer;
  begin
  for i := 1 to MaxMonsters do
   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;

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

  { ----------------- }
  procedure HeroDied;
  var I: Integer;
  begin
  ShowInfo(STR_HERO_DIED);

  for i := 1 to MaxHeroes do
   if Heroes[i].HP > 0 then Exit;

  ClrScr;
  Halt;
  end;

Отметим, что возможность игры, когда один из героев (в частности, текущий) погиб, у нас полноценно не поддерживается. Дело в том, что ранее мы ошибочно предполагали, что если герой уничтожен, то игра закончена и действие продолжается. Теперь же нам надо дополнить игру учетом того факта, что некоторый герой в ней участия не принимает (физически отсутствует). Прежде всего это необходимо отметить в процедуре передачи управления следующему за текущим герою NextHero - не просто увеличить значение CurHero, а дополнительно анализировать, не является ли новый герой на самом деле мертвым?

  { ----------------- }
  procedure NextHero;
  begin
  repeat
   inc(CurHero);
   if CurHero > MaxHeroes then
      CurHero := 1;
  until Heroes[CurHero].HP > 0;

  ScrollMap;
  end;

Цикл будет продолжаться до тех пор, пока не будет найден здоровый персонаж. Кроме того, после уничтожения одного из героев можно например указать в качестве его координат достаточно удаленную позицию, чтобы труп не привлекал внимание монстров. Более корректно, впрочем, откорректировать процедуру поиска монстром жертвы - атаковать имеет смысл только живого героя. Функция CanTrace, ответственная за это, модифицируется так:

  { ----------------- }
  function CanTrace(mi, hi: Integer): Boolean;
  var d, dd: Integer;
  begin
  d := Distance( Monsters[mi].x,Monsters[mi].y,
                 Heroes[hi].x,Heroes[hi].y );

  CanTrace := false;

  if Heroes[hi].HP <= 0 then Exit;

  dd := RollDice(3,6);
  if dd <= Heroes[hi].Chars[chrCHA] then
     Exit;

  CanTrace := (d <= Monsters[mi].ViewZone) and (d > 1);
  end;

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

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

  { ----------------- }
  procedure ShowHero(HeroNum: Integer);
  begin
  GoToXY(
  WINDOW_LEFT+Heroes[HeroNum].x-GameMap[CurMap].LocalMapLeft,
          
  WINDOW_TOP+Heroes[HeroNum].y-GameMap[CurMap].LocalMapTop );

  if HeroNum = CurHero
     then TextColor( White )
     else TextColor( Yellow );

  if Heroes[HeroNum].HP > 0 then
     Write( ' X ' )
  end;

И, наконец, после уничтожения некоторого героя сдвинем номер текущего героя к следующему - чтобы исключить ситуацию, когда убит текущий герой, а значение CurHero по прежнему указывает на несуществующий персонаж:

  { ----------------- }
  procedure HeroDied;
  var I: Integer;
  begin
  ShowInfo(STR_HERO_DIED);

  for i := 1 to MaxHeroes do
   if Heroes[i].HP > 0 then
      begin
      NextHero;
      Exit;
      end;

  ClrScr;
  Halt;
  end;

Теперь наша игра дополнилась возможностью играть группой-партией, а при увеличении числа персонажей она приблизится к жанру стратегических игр!

Далее - добавляем в программу режим реального времени.


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

тут, 3156.zip, 15485 байтов


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

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

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


В избранное