Рассылка закрыта
При закрытии подписчики были переданы в рассылку "Особенности национального бизнеса" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
Создание ролевой компьютерной игры 10) Программируем монстров
Информационный Канал Subscribe.Ru |
Создание ролевой компьютерной игры
10) Программируем монстровМы приблизились к, пожалуй, самой ответственной части программы - программированию сражений с монстрами, ключевому элементу любой ролевой игры и игровой системы. Однако, несмотря на достаточную и объективную трудоемкость задачи, мы незамедлительно приступим к ее реализации, причем начнем с наиболее очевидных и простых элементов. Прежде всего, конечно, подготовим модуль Monster:
unit Monster;
interface
implementation
end. Монстр будет характеризоваться, в первом приближении, своим названием, координатами на карте (x,y), а также, как планировалось ранее, здоровьем, количеством получаемого героем за победу над монстром опыта, уровнем монстра, атакой, обороной, дальностью зрения (расстояния, с которого монстр обнаруживает героя) и способом перемещения (точнее, вероятностью случайного шага в сторону в ходе преследования героя, когда тот обнаружен):
type TMonster = record Что означают эти параметры? Поле ID - некий уникальный идентификатор, позволяющий отличить один тип монстров от другого. Значение полей Ad1, Ad2 задает величину поражения, которое наносится монстром герою. Поражение вычисляется по общепринятой в ролевых играх схеме - бросается N кубиков, каждый из которых описывается M гранями, и выброшенные результаты суммируются. Например, сочетание значений Ad1 = 2 и Ad2 = 6 означает, что бросаются два шестигранных кубика. Если выпадет, допустим, 1 и 4, то суммарное поражение составит 5 единиц. Соответственно, поля Dd1 и Dd2, которые можно условно отнести к толщине шкуры, определяют величину, которую монстр отражает (вычитает) из поражения, которое ему нанесено. Так, если герой наносит монстру с характеристиками Dd1 = 3 и Dd2 = 2 удар, равный 11 единицам, а значения трех виртуальных двугранных кубиков (таких кубиков конечно не существует, но это неважно) составили значения 1, 2 и 2 (сумма равна 5), то реальный ущерб, нанесенный монстру, окажется равным 11-5 = 6 единицам. Уровень монстра (Level) позволит корректно определять количество опыта, получаемое героем при победе. Если уровень героя выше, опыт будет меньше - и наоборот. Пока мы сделали самого сильного монстра седьмого уровня, для масштабной игры с множеством локаций потребуется гораздо большее число монстров. Монстра с уровнем здоровья (HP), меньшим или равным нулю, будем считать мертвым и на карте не показывать. Теперь подготовим массив типов монстров (именно типов, а не самих монстров - на карте может быть одновременно несколько монстров одного типа). Для начала сделаем семь типов монстров, в дальнейшем их можно будет произвольно увеличивать и изменять (в данном случае под типом имеется в виду не тип Паскаля, а тип монстра в игровом смысле - способ его реализации может отличаться от создания типа данных):
const MaxMonsterTypes = 7; Названия монстров разместим в модуле Texts (на него надо добавить ссылку в разделе Interface):
STR_MONSTER1 = ' Крыса ' ; Тут важно, что эти константы в DOS-версии должны быть записаны, конечно, в DOS-кодировке. Массив действующих на карте монстров будем хранить в модуле Monster - в переменной под названием Monsters:
const MaxMonsters = 50; Константа MaxMonsters задает максимально допустимое число одновременно действующих монстров на карте. Теперь этих монстров нам надо добавить в игру - определить, в каких точках карты они будут поджидать героя. Процесс их расстановки будет случайным - на свободные тайлы. Воспользуемся уже подготовленной заранее процедурой FreeMapPoint. Далее требуется определить, каким способом монстры будут подбираться для каждого уровня пещеры. Самый простой вариант - просто отбирать тех монстров, уровень которых соответствует текущему уровню пещеры. Это требует дополнительных усилий от проектировщика игры, однако в плане программной реализации в учебном примере данный подход смотрится оптимальным. Однако может вызвать определенные затруднения задача случайного выбора из массива MonsterTypes монстров одного уровня. Ведь исходно число типов монстров с одинаковым значением поля Level нам неизвестно, кроме того, в MonsterTypes они могут следовать в произвольном порядке. Если бы мы использовали Delphi, где имеется концепция динамических массивов, задачу удалось бы решить весьма легко. Однако TurboPascal по современным меркам весьма ограничен, поэтому воспользуемся более прямолинейным и нехитрым, хотя и не очень быстрым методом. Начнем перебирать все элементы массива MonsterTypes, и при нахождении типа монстра с нужным нам уровнем бросать виртуальный кубик. Таким образом мы выберем не первый попавшийся подходящий элемент MonsterTypes, а случайный, хотя равномерность этого случайного выбора в значительной степени будет зависеть от числа граней нашего условного кубика. Следующая процедура заполняет монстрами текущую локацию, используя условный шестигранный кубик:
procedure GenerateMonsters; Сразу отметим, что данный код не отличается ни оптимальностью, ни элегантностью. Во-первых, мы используем оператор перехода GoTo, противоречащий принципам структурного программирования (хотя основоположники этого метода делали исключение для GoTo, разрешая использовать переход для выхода из нескольких вложенных циклов). Во-вторых, бесконечный цикл теоретически может выполняться сколь угодно долго и завешивать программу - в случае, например, когда разработчик забыл подготовить тип монстров для одного из уровней игры. Читателю предлагается самостоятельно улучшить данную процедуру. Генерацию монстров будем выполнять сразу после генерации карты в главной части программы, не забыв подключить в список модулей Monsters:
program LearningRPG;
uses Map, LowLevel, Hero, Game, CRT, Monsters;
var k: Char;
begin
VideoInitialize; А как показывать монстров? В соответствии с упомянутым подходом "послойной" отрисовки тайлы-предметы-монстры-герой. Укажем в процедуре ShowGame новый вызов ShowMonsters:
procedure ShowGame; Процедуру ShowMonsters реализуем в модуле Monsters. Она должна просканировать список монстров, определить, какие из живых созданий находятся в видимой части окна, и вывести их на экран. Функция проверки, лежит ли некая точка в видимой части, наверняка будет нам полезна в будущем, поэтому реализуем ее в модуле Map:
function VisiblePoint(x,y: Integer): Boolean; Здесь важна первая проверка на видимость самого тайла (исследовал ли его герой). Если ее не ввести, то видимыми (и отображаемыми на карте) могут быть элементы игры (например, монстры), хоть и расположенные в текущей видимой части карты, но находящиеся на неисследованных тайлах - то есть отображаемые на темных, туманных точках. Процедура ShowMonsters может быть закодирована так:
procedure ShowMonsters; Отображение монстра на экране (процедура ShowMonster) связано с особенностями реализации этого процесса в конкретной операционной системе и поэтому должно быть расположено в модуле LowLevel. Сам "монстр" передается по ссылке - для экономии ресурсов, что важно для ДОС-реализации. Рисовать монстров будем таким же способом, как и тайлы - на основе уникального идентификатора станем обращаться к массиву, определяющему способ представления типа монстров на экране. Собственно, при подготовке такого массива нам пригодится уже задействованный в массиве TileRecords тип TTileRecord:
const MonsterRecords: array[1..MaxMonsterTypes] of TTileRecord = Процедура отображения монстра будет не сложнее вывода тайла:
procedure ShowMonster(var mns: TMonster); Главное, чтобы вызывалась она после отрисовки карты. На этом мы завершили подготовку к вводу монстров в игру. Откомпилируем программу (возможно, появятся сообщения о необходимости добавления ссылок на подключаемые модули) и запустим ее. Вы можете походить по карте, поплутать между монстрами - пока они находится в спящем состоянии, то есть недвижимы и спокойны. Оживлением игровых существ мы займемся попозже - это типичная задача, связанная с разработкой искусственного интеллекта. Пока же продолжим насыщать игру новыми элементами. Теперь на очереди ловушки. Исходный код текущей версии: http://russianenterprisesolutions.com/sbo/download/2995.zip 4836 байтов Письма. От Дмитрия.
Хочу пояснить, что Вашу рассылку я использую не столько как руководство к действию, сколько по принципу "внимательно прочитай и сделай по-своему". То есть воспринимаю скорее как руководство к размышлению и один из вариантов решения поставленной задачи. Отлично! Очень рад, - конечно, мои тексты - не более, чем руководство к действию! :) И чем оригинальнее будет ваша игра, тем лучше! 1) Уже давно хочу предложить такой вид процедуры для проверки, свободен или нет тайл карты:
function IsFreeTile(Tile: Integer): Boolean; WalkableTiles описана следующим образом:
const
Это множество содержит значения констант для различных типов местности. Аналогично можно описывать самые разные варианты для проверок принадлежности какого-либо объекта заданному множеству. В качестве примеров можно привести проверку кода нажатой клавиши - входит ли она в набор допустимых, проверку, является ли предмет режущим оружием и т.д. Единственная причина, по которой я так не сделал - опасался, что в крупной игре число проходимых тайлов может быть больше 255. Не знаю, как в последних версиях Дельфи, но в старых проверка на наличие в множестве была ограничена 255 элементами. 2) В одном из первых выпусков Вы упоминали (и продолжаете упоминать) модуль Defines.inc, однако так и не разъяснили его значение и содержание. В 67 выпуске вроде я рассказывал о нем подробнее? В этом файле хранятся константы для директив условной компиляции, набор для всего проекта. Если мы включаем этот файл в каждый pas-модуль проекта, то потом нам достаточно изменить только одну строчку в этом файле, чтобы весь наш проект полностью пересобрался с другими настройками. Хотим, с текстами на русском или английском, для Дос или Виндовс - меняем лишь настроечные константы в Defines.Inc. Эта практика популярна в Си - там весь проект базируется на подобных заголовочных файлах, куда вынесены все описания, настройки и интерфейсы. Чуть-чуть подправили интерфейс- весь проект перестроился автоматически. Ну, если конечно исходный код написан правильно :) 3) От стринггрида я сам уже давно отказался, и сделал в графике. Добавлена опция для отображения темно-серым цветом тех тайлов, которые в данный момент не видны, но герой их уже видел. Для этого каждый тайл карты снабжен дополнительным полем IsExplored логического типа. При выводе тайла на экран сначала проверяется, что тайл был исследован. Если нет, ничего не выводится. Если исследован, проверяется его видимость, и соответственно выводится в цвете или темно-серым. Про стринггрид - следующее письмо. От Boba.
У StringGridа есть событие "OnDrawCell". Оно происходит каждый раз, когда перерисовывается каждая ячейка таблицы. В процедуру передаются такие параметры, которые нам нужны:
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow:
// *
// к стати, в данном случае в таблице можно отображать ЛЮБОЙ
MonsterPower:=IntToStr(TStringGrid(Sender).Cells[ACol,ARow]);
// таким образом содержимое ячейки = ' 100 ' , а отображаться будет
Далее, можно вставить в ячейку картинку.
procedure TForm1.StringGrid1DrawCell(Sender: ...
Но я люблю через TBitmap или TImage.
public В событии создания формы:
MyBm:=TBitmap.Create; // перед использованием BitMap ' а его нужно
for i:=0 to 10 do
SetLength(aDynBm,FileCount( ' *.bmp ' )); // Задаем сколько хотим
// ** функции FileCount( ' *.bmp ' ) вроде нету в Дельфи, так что ее
// примечание: перед загрузкой партинки в Битмап желательно А далее, в обработчике перерисовки ячейки OnCellDraw нашего Стринг-грида рисуем картинки:
TStringGrid(Sender).Canvas.BrushCopy(Rect,MyBm,Rect(1,1,17,17),clFuch Синтаксис процедур BrushCopy и CopyRect изучайте в хелпе, скажу только, что у BrushCopy можно задать прозрачный цвет. Также, ссылка от Боба на сайт с правилами ролевых игр: http://www.rpg-zone.net/portal/news.php Кстати, насчет быстрой графики и работы с TImage - я давал чуть раньше ссылку на набор компонентов G32 (g32.org) - он работает в СОТНИ раз быстрее стандартных дельфийских компонентов имаге-имагелист и к тому же гораздо более удобен в работе. Сделал, кстати, российский пацан.
Важно! Первым заметил Артём: В данной реалихации проверрки прокрутки есть ошибка
if (abs(Heroes[CurHero].x - GameMap[CurMap].LocalMapLeft) < при такой реализации поркрутка будет вестись только в случае движения героя влево или вверх( при движении же вниз или вправо перерисовка карты происходить не будет). В данных условиях проверяеться только на уменьшение границы пересечения а на увеличение проверки нет необходимо добавить ещё 2 усовия вот так будет это смотреться
if (abs(Heroes[CurHero].x - GameMap[CurMap].LocalMapLeft) < можно использовать другой вариант проверки, но тогда будет необходимо внести некоторые поправки в понимание некоторых констант в настоящий момент константа SCROLL_DELTA описывает границы в которые НЕ МОЖЕТ попадать наш Герой.
0000000000 0 - область ограниченная константой SCROLL_DELTA если же принять что константа SCROLL_DELTA описывает границы в которых ДОЛЖЕН НАХОДИТСЯ Герой, то собственно вид границ будет таким
.......... 0 - область ограниченная константой SCROLL_DELTA Тогда проверку можно записать так
if (abs(Heroes[CurHero].x - (LOCAL_MAP_WIDTH div 2 +
Здесь константа SCROLL_DELTA описывает на сколько "шагов" можно
двинуться от центра видимой карты в ту или иную сторону, при выходи
из этой зоны вызваеться прокрутка. Поэтому константу я принимаю равным 2 const SCROLL_DELTA=2; Во Вложении находиться вариант для Delphi, с собраным модулем CRT для консольного режима (модуль мне пришлось немного подправить, он не мой), пока только без ремарок. Надеюсь в скором будущем всё появиться Артём, огромное спасибо за письмо и CRT ! Брать этот модуль с остальным кодом Артёма здесь: http://russianenterprisesolutions.com/sbo/download/LearnRPG.zip 10444 байтов А ошибку со скроллом кстати я заметил позже, и исправил ее где-то дальше по тексту, посмотрим в следующих выпусках, всплывет она или нет. Полезные ссылки. Сайт по технологиям гибкого моделирования софта - http://www.agilemodeling.com/ Гибкие - agile - методики были придуманы в противовес "тяжелым", которые подразумевают явно выделенные и продолжительные этапы формирования требований к проекту, проектирования, после чего техзадание замораживается. Однако в реальности требования заказчика постоянно меняются, а внедрение тяжелых методик типа CMM или RUP (RUP впрочем скорее полуэджил) сам по себе длительный процесс. Наиболее известна пожалуй методика экстремального программирования - http://www.xprogramming.ru/ , а по гибкому моделированию есть сайт на русском http://www.agilemodeling.org.ua/ Правда, в Core Practices на agilemodeling ничего серьезно не говорится о метамоделировании, построении онтологического словаря программной модели, некотором минимуме операционных определений, о стратегиях выявления паттернов, поэтому agile как бы "с другой стороны" пытаются решить проблему нехватки метаинформации за счет динамической адаптации к требованиям стэйкхолдера, однако корешки проблем все равно остаются и торчат из каждой практики. Также, если вы плохо знаете английский, можно почитать о методологиях разработки здесь: http://www.maxkir.com/ Можно искать в яндексе, только не agile (это для гугля - agile ключевое слово), а гибкая разработка, гибкая технология. Наверняка немало полезных ссылок с сайтов по экстремальному программированию.
Вот еще несколько англоязычных ресурсов: Полупопсовую презентацию на тему управления ИТ-проектами можно найти тут, в разделе Best Practices & Case Studies: http://www.performanceweb.org/CENTERS/Project_Management/index.htm Полезно посмотреть учебные курсы на эту тему. При этом разработка софта хотя и имеет свою специфику, тем не менее чем выше - абстрактнее - уровень управления этим процессом, тем менее влияют особенности программной сферы на этот род деятельности, и на первый план выходят классические проектные аспекты. Однако минимальный объем теоретических (лучше конечно практических :) знаний по программированию все равно необходим. Почему я заговорил на эту тему? Уверен, подписчикам понравятся современные зарплаты руководителей программных проектов :) Если разработчик - ну от 1000 до 2000 долл, то руководитель проекта уже побольше, вот недавно встретил - 100 тысяч рублей :) При этом на самом деле программирование достаточно знать на уровне нашего базового курса, или даже немного меньше для такой деятельности. Эта тема интересна - управление программными проектами? Если да, то будем и ей уделять внимание. (c) 2004-2005 Сергей Бобровский bobrovsky@russianenterprisesolutions.com
Школа программирования с нуля
Дизайн рассылки: Алексей Голубев - Web-дизайн и web-программирование |
Subscribe.Ru
Поддержка подписчиков Другие рассылки этой тематики Другие рассылки этого автора |
Подписан адрес:
Код этой рассылки: comp.soft.prog.prognull.game Архив рассылки |
Отписаться
Вспомнить пароль |
В избранное | ||