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

Создание ролевой компьютерной игры 7) Программируем главного героя


Информационный Канал Subscribe.Ru

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


7) Программируем главного героя

Вопросы

У ряда товарищей появились проблемы с использованием Turbo/Borland Pascal. Ведь эти программы для Доса и работают в msdos-сессиях со всеми вытекающими.

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

Во-вторых, в XP, вероятно, и на быстрых процессорах Turbo pascal будет выдавать ошибку 200 при попытке скомпилировать и запустить приложение. Это связано с общеизвестной ошибкой в ней и исправляется просто.

Способ хакеров - с помощью любого hex-редактора в системных библиотеках TP под названием Turbo.tpl и Tpp.tpl из каталогов TP\Bin или BP\Bin ищем строку "B9 37 00 F7 F1" и изменяем значение 37 на E6, чтобы получилась "B9 E6 00 F7 F1".

Способ юзеров. Ищем эти библиотеки в сети :) - исправленных версий множество. Задать запрос на яндексе типа "turbo.tpl error 200". Скачиваем и записываем вместо прежних.


Подписчик Никита просит посоветовать питерские институты, где учат професси программистов.

Питерцы, отзовитесь! Напишите в рассылку, ваше мнение.


Насчет того, что мы делаем. Я уже говорил, что мы делаем НЕ игровой движок, а игру.

Но вопрос такой: Что это такое игровой движок? Везде в OpenGL это встечаю, но не понимаю что это такое. Чем это отличается от игры? Разве Doom3 или Need For Speed Underground - это движки какие-то? Не думаю.... Так что это такое?

Весьма условно: Delphi - это движок, а созданная с ее помощью программа - это не движок :) , а прикладная программа (например игра), созданная, если угодно, на движке Delphi.

Движок - это просто виртуальная реализация некоей ограниченной версии физического мира. Запрограммированы законы этого мира и способы их быстрой и красивой визуализации на экране. А вот конкретное наполнение этого мира городами, сценариями развития, существами с собственным поведением, различными областями местности - уже задача дизайнеров и программистов конкретной игры. Как Duke Nukem Forever на основе движка Doom 3 - способы перемещения по трехмерному миру, законы гравитации, качество визуализации взяты из Doom, а сценарий, персонажи, внешний вид мира выдуманы заново. При этом возможности движков могут сильно различаться. Одни - нечто, чуть более функциональное, чем DirectX, другие - готовые редакторы виртуальных миров и собственные языки сценариев итд.

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

Подписчик Дмитрий предложил как один из вариантов сразу задействовать Delphi:

По поводу вопроса про возможности Дельфи работать с текстовой ДОС-графикой - в версии 6 (насчет других не знаю) можно создать приложение типа Console Application. В этом случае формы не подключаются, вывод идет в текстовое окно. Однако возможностей по смене позиции курсора я не обнаружил (то, что в Паскале реализуется функциями Crt). Тоже относится к прямой записи в видеопамять.
То есть можно делать вывод на экран, но только пользуясь операторами Write/WriteLn
По поводу игры: если можно, в одном из ближайших выпусков хотя бы схематично изобразите пожалуйста структуру игры в виде перечня модулей и краткого описания функций каждого из них.
Пользуясь Вашей рассылкой, я начал самостоятельно писать игру, но есть вопросы по поводу разделения разных функций по разным модулям. Пишу на Дельфи 6, уже реализовал один из алгоритмов генерации подземелья с указанного Вами сайта. Для вывода карты использую компонент StringGrid, в каждую ячейку вывожу по одному тайлу (пока без использования цветов). Реализовал скроллинг (пока без использования клавиатуры, на экранных кнопках).

Кстати, очень хорошая идея, со стринггридом.

Дмитрий поднимает важный вопрос аккуратного разделения функций. Поэтому

Версия исходных текстов

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

Сразу хочу предупредить!

Как НЕ надо использовать этот архив. Способов неправильного, глупого ее использования два.

а) Код используется для "поиграть в игру". Игра еще не сделана, поэтому играть пока не во что.

б) Собрать из исходных текстов проект, убедившись, что он компилируется, но выпуск не изучить. В результате, без понимания, что мы делаем, через несколко выпусков такой подход даст обратный эффект - вообще все станет непонятным.

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

Архив-крохотулю - 1680 байтов - брать тут:

http://russianenterprisesolutions.com/sbo/download/195.zip

Кроме того, подписчик Павел прислал свой вариант создаваемой игры, на Си++. И его комментарий:

PS: да, хочу дать небольшой совет. При размещении основных важных объектов(и особенно "порталов" для перехода на другие уровни) нужно ставить их так, чтобы герой смог до них добраться - иначе примерно в 20% случаев он оказывается "запертым" на уровне - проверено на собственном опыте. Я обхожу это так - создаю "карту доступа"(двумерный массив с такими же размерами, как и сама игровая карта; элементы массива имеют тип bool), который показывает доступность/недоступность ячеек игровой карты. И размещаю объекты(монстры, etc) в соответствии с этой информацией. Сама функция создания карты доступа занимает несколько строк - можете посмотреть в исходниках.

Исходники Павла, 18 kb, можно взять тут:

http://russianenterprisesolutions.com/sbo/download/195.rar


Программируем главного героя

Подготовим новый модуль и назовем его Hero.

  unit Hero;

  interface

  implementation

  end.

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

Характеристики поделим на три уже упоминавшиеся выше группы - базовые параметры, навыки (умения) и основные величины, не попадающие в две предыдущие группы. Каждую из этих групп представим в виде массива.

  type THero = record
       Chars : array[1..MaxChars] of Integer;
       Skills: array[1..MaxSkills] of Integer;
       x,y, HP, MaxHP,
       Exp, MaxExp,
       Level, VisLong: Integer;
       end;

В последнюю группу вошли такие характеристики, как текущее здоровье (HP) и максимально возможное на текущем уровне здоровье (MaxHP), текущее количество опыта (Exp) и количество опыта, которое надо набрать для перехода на следующий уровень (MaxExp), дальность видения в тайлах (VisLong), а также текущий уровень героя (Level). Очевидно также, что герой должен находиться в какой-то точке карты, заданной абсолютными координатами x,y (абсолютными - то есть связанными с левым верхним углом глобальной карты, а не видимого на экране окна).

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

  const MaxChars = 4;
        chrSTR = 1;
        chrDEX = 2;
        chrCON = 3;
        chrIQ = 4;

Кроме того, герой должен как минимум уметь сражаться в ближнем бою и уметь обнаруживать ловушки. Опишем эти навыки схожим образом:

  const MaxSkills = 2;
        skillHandWeapon = 1;
        skillTrapSearch = 2;

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

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

Итак:

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

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

Теперь нам требуется процедура начальной инициализации всех параметров и характеристик определенного героя. Назовем ее InitHero:

  procedure InitHero(HeroNum: Integer);
  var i: Integer;
  begin

  with Heroes[HeroNum] do
    begin
    for i := 1 to MaxChars do
      Heroes[HeroNum].Chars[i] := 0;
    for i := 1 to MaxSkills do
      Heroes[HeroNum].Skills[i] := 0;

    Level := 0;
    MaxHP := HPLevel_Table[Level];
    HP := MaxHP;
    Exp := 0;
    MaxExp := ExpLevel_Table[Level];
    VisLong := 2;
    end;

  end;

Пока все параметры героя установим в ноль и будем развивать их в процессе игры. Единственные исключения - дальность видения VisLong (примем его равным двум тайлам и пока менять по ходу игры не будем), MaxExp - очередное пороговое значение опыта для очередного повышения уровня, и MaxHP - текущее максимальное значение здоровья. Они берутся из заранее подготовленных массивов (таблиц). Ссылки на такие таблицы удобнее всего вынести в дополнительный модуль, хранящий все вспомогательные константы и таблицы (добавим ссылку на него в заголовок реализации Implementation модуля Hero). Назовем этот модуль Tables:

  unit Tables;

  interface

  implementation

  end.

Теперь надо определить размеры таких таблиц. Они определяются, очевидно, максимально достижимым в игре уровнем героя. Без глубокого и продолжительного тестирования вряд ли возможно даже приблизительно определить порядок высоких уровней героя. В коммерческих ролевых системах типа D&D все вопросы балансировки решены очень хорошо, но именно поэтому за использование этих систем надо платить - покупать лицензию.

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

  const MaxPlayerLevel = 7;

  const ExpLevel_Table: array[0..MaxPlayerLevel] of Integer =
    (
    10, 20, 50, 100, 250, 500, 1000, 3000
    );

  const HPLevel_Table: array[0..MaxPlayerLevel] of Integer =
    (
    10, 20, 30, 50, 80, 130, 210, 340
    );

Отметим, что N-й элемент каждого массива соответствует максимальному соответствующему значению для уровня N. Так, для уровня 0 надо брать элементы таблиц с индексом 0, для уровня 7 - элемент с индексом 7.

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

Особенностей формирования и корректировки этих таблиц мы коснемся позже, а пока создадим еще одну процедуру инициализации всех героев:

  procedure InitHeroes;
  var i: Integer;
  begin
  for i := 1 to MaxHeroes do
    InitHero(i);
  CurHero := 1;
  end;

Обратите внимание, что переменная CurHero, определяющая индекс текущего героя в массиве Heroes, получает начальное значение.

Эту процедуру мы вызовем из главной программы, в начале блока инициализации, добавив также ссылку на модуль Hero:

  program LearningRPG;

  uses Map, LowLevel, Hero;

  begin
  Randomize;

  VideoInitialize;
  MapGeneration(1);
  InitHeroes;

  ShowMap;
  readln;
  end.

Пока в выводимой на экран информации ничего нового не возникло (точнее, экран по прежнему пуст, так как все тайлы пока не видны - флажок IsVisible содержит значение false). Так произошло потому, что хотя мы подготовили массив героев, но не определили их координаты (точнее, координаты единственного главного героя). Как задать эти координаты? Удобнее всего это сделать в процедуре инициализации InitHero. Просто поставим героя в некоторую случайную точку в отображаемой на экране части карты, и сделаем видимой вокруг него некоторую область.

Допишем процедуру InitHero так:

  ...

    repeat
      x := GameMap[CurMap].LocalMapLeft + LOCAL_MAP_WIDTH div 3 +
           random(LOCAL_MAP_WIDTH div 3);
      y := GameMap[CurMap].LocalMapTop + LOCAL_MAP_HEIGHT div 3 +
           random(LOCAL_MAP_HEIGHT div 3);
    until FreeTile(GameMap[CurMap].Cells[x,y].Tile);

  end;

x и y получают значения, лежащие в центральной трети видимой части карты. Чтобы процедура могла обращаться к переменной GameMap, в заголовок Implementation надо добавить ссылку на модуль Map.

Следом за определением координат героя вызовем процедуру SetHeroVisible (пока неопределенную), которая делает видимыми тайлы вокруг героя на расстоянии от него, определенном параметром VisLong:

  procedure SetHeroVisible(HeroNum: Integer);
  var i, j: Integer;
  begin
  for i := Heroes[HeroNum].x-Heroes[HeroNum].VisLong to
  Heroes[HeroNum].x+Heroes[HeroNum].VisLong do
  for j := Heroes[HeroNum].y-Heroes[HeroNum].VisLong to
  Heroes[HeroNum].y+Heroes[HeroNum].VisLong do
    GameMap[CurMap].Cells[i,j].IsVisible := true;
  end;

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


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

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

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


Subscribe.Ru
Поддержка подписчиков
Другие рассылки этой тематики
Другие рассылки этого автора
Подписан адрес:
Код этой рассылки: comp.soft.prog.prognull.game
Отписаться
Вспомнить пароль

В избранное