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

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


Разработка ролевой игры

30) Что у монстра внутри

Теперь, когда мы полностью определились с перечнем и структурой типов предметов в игре, реализуем упомянутый режим, когда из убитого монстра выпадает некий предмет. Для этого нам потребуется нечто, схожее с кодом процедуры расстановки предметов на генерируемой карте. Ведь в этом коде предметы формируются случайно, и было бы неплохо этим кодом воспользоваться и в текущем случае. Для этого давайте выделим соответствующую часть процедуры MapGeneration в отдельную процедуру GenerateRandomItem (в модуле GameItem), которая будет выдавать случайно созданный предмет. Правильнее оформить ее в виде функции, но к сожалению версия Borland Pascal еще не умела поддерживать функции, которые возвращают значения сложных типов данных. В число параметров также добавим начальные координаты предмета на земле и текущий уровень генерации карты (MapLevel) - он нужен нам для подсчета случайно разбрасываемой суммы.

  { ----------------- }
  procedure GenerateRandomItem( var gi: TGameItem; x,y, ml: Integer );
  begin
   gi := ItemTypes[random(MaxItemTypes)+1];
   gi.x := x;
   gi.y := y;

   if gi.ID = 7 then
      begin
      gi.Ints[intMagikType] := random(mintMax)+1;

      case gi.Ints[intMagikType] of

        mintStormStf : gi.Ints[intMagikNum] := 5;

        mintHealingStf: gi.Ints[intMagikNum] := 10;

      else gi.Ints[intMagikNum] := 1;

      end;

      end else
   if gi.ID = 8 then
      begin
      gi.Ints[intMoney] := random(ml*10)+1;
      end

  end;

А в процедуре MapGenerate код сократится:

  if random(100) = 0 then
    begin
    inc(n);
    if n <= MaxItems then
       begin
       GenerateRandomItem(Items[n],x,y,MapLevel);
       end;
    end;

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

  ...
  if Monsters[m].HP <= 0 then
     begin
     ShowInfo(Monsters[m].Name + STR_MON_KILL);
     IncXP( H,Monsters[m].XP );

     n := GetFreeItemNum;
     if n>0 then
       GenerateRandomItem(Items[n],Monsters[m].x,Monsters[m].y,CurMap);
     end;

  end;

Теперь после уничтожения любого монстра на его клетке останется какой-то предмет. Его можно подобрать и, например, продать. А где?

Магазины

Пусть магазины будут особым типом тайлов (tileShop), при вступлении на которые герой будет оказываться внутри магазина, в соответствующем интерфейсе:

  tileGrass = 1;
  tileGround = 2;
  tileStairsUp = 3;
  tileStairsDown = 4;
  tileTrap = 5;
  tileLive = 6;
  tileShop = 7;

  tileFirstStopTile = 8;
  tileTree = tileFirstStopTile;
  tileStone = tileFirstStopTile+1;
  tileLast = tileFirstStopTile+1;

Показываться на карте они будут, как и лежащие на земле деньги, символом '$', только не золотого, а зеленого цвета:

  const TileRecords: array[1..tileLast] of TTileRecord =
     (
     (C: ' . ' ; Clr:Green),
     (C: ' _ ' ; Clr:Brown),
     (C: ' < ' ; Clr:LightGray),
     (C: ' > ' ; Clr:LightGray),
     (C: ' . ' ; Clr:Green),
     (C: ' * ' ; Clr:LightCyan),
     (C: ' $ ' ; Clr:LightGreen),
     (C: ' ^ ' ; Clr:LightGray),
     (C: ' ! ' ; Clr:LightGreen)
     );

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

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

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

  ...
  tileLive = 6;
  tileMagicShop = 7;
  tileWeaponShop = 8;

  tileFirstStopTile = 9;
  ...

  const TileRecords: array[1..tileLast] of TTileRecord =
     (
     (C: ' . ' ; Clr:Green),
     (C: ' _ ' ; Clr:Brown),
     (C: ' < ' ; Clr:LightGray),
     (C: ' > ' ; Clr:LightGray),
     (C: ' . ' ; Clr:Green),
     (C: ' * ' ; Clr:LightCyan),
     (C: ' ^ ' ; Clr:LightGray),
     (C: ' ! ' ; Clr:LightGreen),
     (C: ' $ ' ; Clr:LightGreen),
     (C: ' $ ' ; Clr:LightCyan)
     );

В завершение процедуры MapGeneration запишем следующий код установки двух новых тайлов магазинов:

  ...

  FreeMapPoint(x,y);
  GameMap[CurMap].Cells[x,y].Tile := tileMagicShop;
  FreeMapPoint(x,y);
  GameMap[CurMap].Cells[x,y].Tile := tileWeaponShop;

  end;

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

  TrapTileSet = [tileTrap];
  LiveTileSet = [tileLive];
  ShopTileSet = [tileMagicShop,tileWeaponShop];

а в процедуре перемещения героя MoveHero добавим проверку вступления на это множество:

  if GameMap[CurMap].Cells[ Heroes[CurHero].x,Heroes[CurHero].y].Tile in
  ShopTileSet then
     begin

     end;

Пусть в таком случае будет вызываться подпрограмма из модуля LowLevel (так как начнется непосредственная работа с интерфейсом магазина), назовем ее GoToShop. В качестве параметра такой процедуры передадим тайл, чтобы знать, какой магазин создавать:

  with GameMap[CurMap].Cells[ Heroes[CurHero].x,Heroes[CurHero].y] do
   if Tile in ShopTileSet then
     begin
     GoToShop(Tile);
     end;

Подготовим шаблон процедуры в модуле LowLevel:

  { ----------------- }
  procedure GoToShop(til: Integer);
  begin

  end;

Мы должны сформировать для каждого из магазинов предлагаемую ими номенклатуру товаров. Как это сделать?

Определим внутри данной процедуры локальный массив предметов магазина:

  procedure GoToShop(til: Integer);
  const MaxShopItems = 5;
  var ShopItems: array[1..MaxShopItems] of TGameItem;
  begin

  end;

Он должен заполняться случайно и в зависимости от типа продаваемых предметов. Чтобы упростить процесс подбора товаров, выполним предварительно подсчет числа профильных предметов в массиве ItemTypes.

  for n := 1 to MaxShopItems do
   case til of

   tileWeaponShop:
     begin
     ni := 0;
     for i := 1 to MaxItemTypes do
       if ItemTypes[i].IType in
            [itemHandWeapon, itemArmor, itemAmmo, itemRangedWeapon] then
          inc(ni);
     end;

   tileMagicShop:
     begin
     ni := 0;
     for i := 1 to MaxItemTypes do
       if ItemTypes[i].IType in [itemMagik] then
          inc(ni);
     end;

  end;

Далее мы выбираем какой-то случайный номер в диапазоне от 1 до ni, который и станет номером очередного n-го предмета (типа), предлагаемого в магазине.

  tileWeaponShop:
    begin
    ni := 0;
    for i := 1 to MaxItemTypes do
      if ItemTypes[i].IType in
           [itemHandWeapon, itemArmor, itemAmmo, itemRangedWeapon] then
         inc(ni);

    ni := random(ni)+1;
    for i := 1 to MaxItemTypes do
      if ItemTypes[i].IType in
           [itemHandWeapon, itemArmor, itemAmmo, itemRangedWeapon] then
      begin
      dec(ni);
      if ni = 0 then
         begin
         ShopItems[n] := ItemTypes[i];
         break
         end;
      end;

  tileMagicShop:
    begin
    ni := 0;
    for i := 1 to MaxItemTypes do
      if ItemTypes[i].IType in [itemMagik] then
         inc(ni);

    ni := random(ni)+1;
    for i := 1 to MaxItemTypes do
      if ItemTypes[i].IType in [itemMagik] then
      begin
      dec(ni);
      if ni = 0 then
         begin
         ShopItems[n] := ItemTypes[i];
         break
         end;
      end;

Массив товаров у нас сформирован. Правда, пока в товарах не хватает главного параметра - цены.


Далее: Расширяем возможности магазинов.

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

http://russianenterprisesolutions.com/sbo/download/2126.zip 14308 байтов


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

Школа программирования с нуля
Все предыдущие выпуски базового курса тут:
http://russianenterprisesolutions.com/sbo/

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


В избранное