Roguelike Development FAQ (Damjan Jovanovic)

Материал из Клуб любителей рогаликов
Версия от 08:06, 22 августа 2009; Sanja (обсуждение | вклад) (Убраны дублирующие номера разделов)
Перейти к: навигация, поиск

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

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

  1. Не копируйте весь этот документ или его части в другой FAQ без упоминания источника и авторов. Если Вы можете что-то предложить добавить или подправить - напишите нам и мы с радостью примем Вашу информацию;
  2. Не распространяйте этот документ за деньги.
  3. Roguelike-игры, которые Вы решите делать должны быть свободно распространяемыми.

Не все вопросы здесь часто задаваемые, некоторые - просто полезны. Ничто не мешает Вам задать вопросы в новостной группе или нашем форуме. Если у вас есть идеи, или мы забыли что-то упомянуть, пожалуйста напишите нам и FAQ будет скорректирован.

Содержание

ВВЕДЕНИЕ

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

Что такое Rogue?

Rogue это текстовая игра сделанная в 1970'х, с которой все началось. Вы исследовали подземелья, собирали предметы, сражались с монстрами и становились сильнее. Основной задачей (квестом) было найти Амулет Йендора (Amulet of Yendor).

Что выделило в свое время Rogue из большинства аналогичных текстовых игр, так это то, как был решен вывод на экран. Большинство адвенчурных игр описывали то, что окружает игрока (напр. "Вы в небольшой комнате, с проходом за Вами"); Rogue "рисовала" это, используя текстовые символы. Например, если бы Вы стояли в описанной выше комнате, это выглядело бы так: ("@" - Вы, а "#" - стены):

#####
#   ######
# @
#   ######
#   #
#####

В то время, ПК были без звука, и большинство компьютеров были просто терминалами, подключенными к мэйнфрейму, ограниченные текстовым выводом безо всякой графики. Рисование с помощью текстовых символов было большим новшеством. Для своего времени Rogue была невероятно захватывающей. В конечном счете она значительно распространилась на Unix.

Что такое рогалик?

Это тема, которая вызвала горячие дискуссии в новостной группе rec.games.roguelike.development. Далее следует не строгое определение, но обзор некоторых основных характеристик.

Давайте рассмотрим характеристики Rogue:

  • Одиночная игра (single player)
  • Текстовый вывод (без графики)
  • Произвольно сгенерированные уровни подземелий
  • Пошаговая (ничего не происходит, пока Вы что-нибудь не сделаете)
  • Акцент сделан более на хорошем игровом процессе, чем на хорошей графике
  • Смерть - конечна. Никакой загрузки сохраненных игр, никакого возвращения к жизни. Если Вы умрете, то сможете лишь начать игру с самого начала.

Конечно, есть современные рогалики, которые отличаются по некоторым аспектам:

  • Существуют многопользовательские игры (например, Mangband)
  • Многие игры имеют графический интерфейс. Есть даже рогалики с 3D-графикой (хотя их лишь немногим более трех).
  • Некоторые уровни могут быть предопределенными и/или постоянными (к примеру Nethack использует "level bones": уровни, на которых в случае гибели игрока остается его скелет или призрак и его вещи, на которые может в дальнейшем наткнуться другой персонаж).

Некоторые отличительные черты рогаликов по сравнению с другими играми:

  • Безвозвратная смерть. Очень редко в других играх.
  • Умения. Немного игр за исключением тех же рогаликов и RPG имеют их.
  • Произвольная генерация уровней.
  • Пошаговость.
  • Тема исследования подземелий.

Какие крупнейшие/наиболее известные рогалики есть на сегодняшний день?

ADOM (Сайт): ADOM это проект Томаса Бискапа [Thomas Biskup], созданный им в одиночку. Пронизанный духом вселенной D&D [настольная игра Dungeons & Dragons], ADOM, является фэнтезийной игрой с необычным юмористическим настроением. Он имеет карту мира, созданную вручную Дракалорской Гряды [Drakalor Chain]. Задача игрока найти источник Хаоса, поглотившего эти земли. И чем ближе игрок подбирается к этому источнику, тем больше Хаос разрушает игрока.

Angband (Сайт): "Angband" - это рогалик, основанный на Мории [Moria] и произведении "Властелин Колец" [Lord of the Rings]. Возможно, он имеет больше всего вариантов, чем любой другой рогалик. Часто в вариантах имеются абсолютно другие монстры и цели; не все варианты основаны на произведениях Толкиена.

Moria (): "Шахты Мории" или просто "Мория". Действие происходит в шахтах Мории из "Властелина Колец". Более не разрабатывается; только поддерживается.

Nethack (Сайт): Nethack это современный рогалик, прямой потомок оригинального Rogue. Nethack - игра оригинальная и причудливая; любимое выражением игроков о команде разработчиков Nethacka - "они продумали всё". Основа игры достаточно проста - здесь нет городов или открытых пространств, только одно подземелье, из которого надо вернуться с Amulet of Yender. Однако, некоторые уровни сильно отличаются от типичных для большинства других рогаликов. Игра относительно короткая. Её можно пройти за 8 часов, если сильно погрузиться в процесс - отчасти из-за того, что уровни подземелья однообразны на протяжении всей игры, так что в отличие от других игр у Вас небольшой выбор между беготнёй по исследованным уровням и спуском вглубь подземелья.

Dungeon Crawl (Сайт)

Какие существуют вебсайты/новостные группы, посвященные рогаликам?

Самый лучший ресурс относящийся к рогаликам - http://roguelikedevelopment.org, который содержит список разрабатываемых игр, статьи о разработке и новости о самых последних выпусках старших рогаликов.

ОБЩИЕ ВОПРОСЫ РАЗРАБОТКИ

Как и кем обычно делаются рогалики?

Решением людей во всем мире, имеющих слишком много свободного времени =). Людьми, желающими изучить язык программирования; людьми, которые играли в игры некоторое время и захотели сделать что-то по-своему; людьми, которые хотят сделать игру сами, не имея художественного таланта.

Обычно людьми, изучающими программирование; первоначально это были студенты информатики или аналогичных курсов в колледжах или университетах.

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

На каких языках программирования чаще всего пишутся рогалики?

Раньше писали на языке C, из-за его межплатформенной переносимости и ещё, потому что в те времена большинство других языков программирования ещё не существовало. На сегодняшний день выбор широк: Java, C++, Pascal, скриптовые языки и т.д.

На каких языках программирования/концепциях лучше писать рогалики?

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

Императивные языки, такие как Cи и Pascal, работают хорошо - они долгое время используются для разработки рогаликов, что является достаточным основанием.

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

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

Как сделать вариант рогалика?

Вариант является рогаликом, основанным на исходном коде другой игры, с некоторыми изменениями.

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

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

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

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

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

ВОПРОСЫ ПРОЕКТИРОВАНИЯ

Проектируете ли Вы рогалик перед тем как начать кодировать?

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

Если у Вас были уроки программирования в учебном заведении, то первая вещь, которой Вас должны были научить это "Как составить алгоритм", "Как спланировать Вашу программу" и "Как проектировать, перед началом кодирования". Для некоторых типов коммерческих игр дизайнеры пишут 300-страничные дизайн-документы, ещё до того, как актёры или программисты приступят к работе! Игры, полагающиеся в основном на сюжет, обычно имеют готовый сценарий игры, написанный до дизайн-документа. Разумеется, подавляющее большинство рогаликов бесплатны и такой объём работ невозможно было бы в них вложить, поэтому проектная документация у них как правило практически не проработана. Но примите во внимание, что множество разрабатывающихся рогаликов имеют дизайн-документы на своих домашних страницах.

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

Почему многие рогалики позднее подвергаются серьезному переписыванию?

  1. Слабый дизайн. Если изначально Вы запланировали достаточно, Вы должны обеспечить все, или по крайней мере большинство из того, что Вы хотели осуществить. Потом Вам вероятно потребуется лишь немного переписать.
  2. Смена разработчика. Самые популярные рогалики проходили через длинные цепи разработчиков и групп разработки. Как только кто-то получает код исходника, первое, что он делает - переписывает его, чтобы реализовать свои идеи.
  3. Слабые методы программирования. Большинство переписываний обусловлены багфиксом. Некоторые языки программирования лучше структурированы и лучшие подходят для использования при написании длинных программ наподобие рогаликов. Показано, что когда Ваш код превышает 100000 строк, затраты на его поддержку и отладку превышают затраты на программирование новых вещей. Объектно-ориентированное программирование, структурное и модульное помогают устранить эти проблемы.

Что должно быть спланировано заранее?

  1. Сюжет, тема и содержание. Они определяет практически все.
  2. Мир. Как он будет выглядеть. Как будет велик.
  3. Местность (например город, равнины, подземелья, специальные места...)
  4. Предметы. Основные категории (оружие, броня, зелья, свитки, пища...). Пропустите детали (например: насколько значительно будет Вас излечивать зелье лечения) для поздней проработки. Подумайте о том, как предметы будут использоваться и кем. Спланируйте основные правила (например: Вы можете владеть только 2 предметами оружия, если Вы воин, Вам нужно место для стрел, если Вы применяете лук).
  5. Магическая система. Типы заклинаний. Кто использует заклинания и когда. Как Вы будете изучать заклинания. Сколько они стоят. Какие классы смогут их использовать. Как реализовать заклинания с постоянным эффектом (к примеру зачарование, которое остаётся на предмете, вроде перезарядки посоха).
  6. Системы. Система боя. Система торговли. Взаимодействие с NPC's и монстрами. Система повышения уровня. Как завершается игра и что случается, когда это происходит.

Почему люди любят рогалики?

  • Реиграбельность. Если элементы игры достаточно случайны, то в неё всегда интересно играть и переигрывать, потому что каждый раз это как будто новая игра.
  • Свобода. Здесь её много. Вы можете убить практически любого. Здесь масса действий, которые можно выполнять. Множество стратегий.
  • Комплексность. Много вещей, навыков, классов, заклинаний, большой мир.
  • Сложность игры. Пока она может быть сбивающей с толку, она будет давать Вам потрясающее чувство победы в конце игры или даже далеко до него. Вам всегда нужно быть осторожным - здесь легко погибуть и всё время игры пойдёт насмарку.

Как сделать так, чтобы людям нравился Ваш рогалик? Пусть можно будет легко начать играть, не читая 50-страничные руководства. Оставьте управление простым. Игровой процесс должен быть хорош. В этом главное отличие рогаликов от других игр - акцент на игровом процессе. Когда Ваш персонаж погибает, это всё - его уже нельзя вернуть к жизни. Это делает игру захватывающей в минуты опасности. Сбалансируйте её. Если будет слишком сложно, то люди разочаруются. Если же будет слишком просто, то не возникнет особого интереса играть в неё. Сделайте игру большой с достаточным количеством вещей и монстров, которые было бы интересно исследовать. Напишите интересную историю. И, самое главное, не нужно делать игру похожую на тот или иной уже существующий рогалик. Используйте свежие, новые идеи. Пусть Ваш рогалик будет оригинальным, отличающийся от других.

Как Вам довести до конца создание вашего рогалика?

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

Если Вы уперлись в программировании, работайте над сюжетом или графикой (если Вы их используете). Сделайте перерыв. Это должно быть развлечением.

Если нет, спросите сами себя, почему, и сделайте с этим что-нибудь. Если Вы остановились, попросите других людей помочь Вам.

ВЫВОД И ПРЕДСТАВЛЕНИЕ

Обязательно ли рогалик должен быть сделан в ASCII-тексте?

Это относительный вопрос. Utumno - вариант Angband, Falcon's Eye - вариант Nethack, и это очень хорошие примеры рогаликов с графикой. Angband и Nethack сами поддерживают тайловую графику (хотя она так плоха, что текст - действительно лучше).

Итак, НЕТ, рогалики не обязаны быть в ASCII-символах. Почему 100% из них следуют этим путем (по крайней мере частично)?

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

Как добавить графику?

Существует несколько различающихся графических решений, которые мы рассмотрим.

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

Другой метод использует изометрические тайлы. Они выглядят как квадратные тайлы, повёрнутые на 45 градусов и как будто они рассматриваются сверху и со стороны. Просто взгляните на Utumno, Falcon's Eye или Diablo и Вы поймёте, что имеется в виду. Их сложнее реализовать в программе и рисовать, но они смотрятся лучше. Здесь Вы столкнётесь с гораздо большей проблемой, чем при реализации анимации - Вам понадобятся 8 различных направлений (вверх, вверх-вправо, вправо, вниз-вправо, вниз, вниз-влево, влево и вверх-влево) для игрока и монстров, а также нужно будет создать каждый кадр для каждого действия в этих направлениях. Это потребует большего объёма работ, но выглядеть это будет лучше.

Как сделать 3D рогалик?

Пока есть немного рогаликов с 3D-реализацией, одна из них, к примеру: Egoboo (http://egoboo.sourceforge.net).

Здесь есть несколько идей. Хорошей кросс-платформенной 3D библиотекой является OpenGL. Обычно задачи с ее применением разрабатываются на C/C++. Её можно легко выучить, но для хорошей реализации требуется аппаратный 3D-ускоритель. Хорошее бесплатное программное обеспечение для моделирования/анимации трудно найти. Можно попробовать Blender и Anim8or.

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

Тем не менее, математика 3D трудна и включает геометрию и тригонометрию, линейную алгебру, векторы и матрицы.

Какие библиотеки позволяют позиционировать курсор и изменять цвета текста?

Большинство языков программирования имеют собственные библиотеки. В C и C++, Вы можете использовать "curses". Это та межплатформенная библиотека, которая была первоначально использована в Rogue. Найдите "pdcurses" и "ncurses" в Internet. Большинство МС-DOS C/C++ компиляторов имеют библиотеку "conio.h", которая делает то-же самое.

Для оконных систем, вопрос более сложен... функции, которые устанавливают шрифт и меняют цвета, получают до 20 параметров каждая. Вы найдете что Angband, например, использует растровые шрифты (битовые изображения) вместо векторных (true-type).

Как узнать, какие клавиши были нажаты?

В библиотеках curses и conio.h (см. предыдущий вопрос) есть функции, которые возвращают значения нажатых клавиш.

В системе Windows Ваша программа получает сообщения, которые говорят, какая клавиша была нажата.

Как насчет мыши?

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

Windows и X-Windows посылают вашей программе сообщения, которые говорят, какая кнопка была нажата и где.

СЮЖЕТ И СОДЕРЖАНИЕ

Какие сюжеты возможны в рогаликах?

Moria и Angband основаны на произведении Толкиена "Властелин колец", но за исключением нескольких персонажей, предметов и существ, взятых оттуда, они реализуют очень небольшую часть произведения. Nethack едва ли имеет какой-либо сюжет (за исключением одного момента, который Вы узнаете в начале: "Вы ищете Амулет Йендора, легендарный артефакт, который предоставляет бессмертие владельцу").

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

Полезные идеи:

  • Вы пойманы в мире виртуальной реальности, где компьютер сошел с ума, и создает монстров, которые могут убить Вас. Цель: остаться в живых и найти путь в реальный мир.
  • Вы в постапокалиптическом будущем (никаких пушек, или немного, но с ограниченными боеприпасами). Здесь идёт война между людьми и роботами. Вы на проигрывающей стороне...
  • Историческая обстановка наподобие древнего Египта.

Какая тематика и игровой мир возможны в рогаликах?

Действие большинства рогаликов происходит в средние века. Есть также несколько на тему научной фантастики, происходящие в будущем.

Относительно неисследованые тематики:

  • Дикий Запад
  • Дальний восток
  • Далёкое прошлое (каменный век)
  • Альтернативная история (например, Атлантида)
  • Наши дни

Чем/как мне сделать хорошую атмосферу?

Атмосфера (существительное): психологическая среда, чувство и оттенок создаваемая чем-то. В рогаликах различная атмосфера. В Angband'е это часто темнота и отчаяние - Вы на километры под землей с гаснущим источником света, смерть рядом и нет способа избежать ваших врагов... В Nethack'е, она отчасти вызывающая, но отчасти забавная. Diablo - очень темные и зловещие - ваш случай безнадежен.

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

Что такое квесты и как они отличаются от других форм задач?

Квесты - это то, что Вы должны сделать в игре за особое вознаграждение. Например, как только Вы завершите квест "убить Моргота", Вы завершите Angband. Квесты имеют либо какое-то вознаграждение (золото, предметы,..), либо элемент продолжения сюжета.

Какие существуют стандартные типы квестов?

  • "Убей <количество> <тип монстра>"
  • "Поймай <монстр> живым"
  • "Найди <вещь>"
  • "Отправляйся в <место>"
  • "Принеси <количество> золотых монет"
  • "Полностью исследуй и составь карту <регион>"
  • Любой из вышеописанных квестов нужно выполнить до того, как это сделает твой противник. Например, добраться до города быстрее оппонента.
  • "Поговори с <персона>"
  • Любой квест из вышеописанных, но дающий что-то особенное (например, поиск уникальной вещи, связанной с сюжетом)

Дополнительно можно прочитать статью Создание сюжета.

Как мне сделать хорошие квесты?

Сделайте их важными и обеспечьте хорошее обоснование их выполнения. Включите их в сюжет. Давайте хорошие вознаграждении. Избегайте повторения.

Пример: избегайте случаев наподобие "убить 5 орков". Почему? Может быть это важно, если Вы состоите в гильдии наемных убийц, ну а если нет? С другой стороны, если орки ограбили Вас и взяли 500 золота, это гораздо важнее и Вы хотите их убить.

Как мне сделать хороший рандомизатор квестов?

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

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

МЕХАНИКА ПОДЗЕМЕЛИЙ

Как представлены темницы?

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

Этот FAQ освещает только традиционные реализации, такие как двумерное тайловое подземелье. Если Вы создаёте что-то другое (вроде 3D-рогалика), то этот текст все равно можно использовать.

Во первых, подземелья состоят из строк и колонок тайлов. Если в Ваших планах создание подземелья с переменными размерами, то используйте динамические массивы, вместо обычных статических.

Здесь будет описаны лишь основы системы, но они очень универсальны и хорошо работают.

  • тип тайла (т.е. стена, трава, пол, лестница)
  • свойства тайла (Можно ли пройти по нему? Он освещён? Он исследован игроком?)
  • список вещей на тайле
  • монстр на тайле
  • любые другие данные (например, ловушка, дыра в полу, лестницы)

Вам нужно сделать Ваши тайлы занимающих как можно меньше памяти. В Angband'е тайл занимает около 16 байт. Не надо делать их намного больше, чем этот; однажды Вы поймёте почему.

Как это работает?

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

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

  • В большинстве рогаликов карта затемняется в областях, в которых Вы ещё не побывали. Это легко реализовать. В начале установите флаг "исследовано" в false для всех тайлов, а когда какой-либо будет открыт, установите флаг в true. Каждый ход будут отображаться только тайлы с флагом исследования, установленном в true.
  • В большинстве рогаликов Вы можете видеть монстров только если они в вашей "линии обзора" (LOS) (так что, если они будут за препятствиями, либо слишком далеко от Вашего источника света, то отображаться они не будут). Каждый ход устанавливайте флаги освещения всех тайлов в false. Затем пересчитывайте освещение игрока установкой флагов "освещён" в true соответствующих тайлов, затем отрисовывайте только тех монстров, которые стоят на тайлах с флагом "освещён", установленном в true.
  • Создать столкновение игрока и монстра со стенами это просто. Если тайл является проходимым (т.е. это не стена или что-то подобное), то установите флаг "проходимый" в true. Когда игрок или монстр попытается пройти куда-нибудь, проверьте тайл в этом направлении. Если флаг "проходимый" в нём установлен в false, не давайте игроку или монстру пройти туда. Это позволит Вам сделать невидимые барьеры или видимые стены, через которые действительно нельзя пройти, если Вы разделите тип тайла и флаг проходимости. Это должно занять 1 байт или меньше.

Для списка вещей используйте связанный список. Это позволит держать большое количество вещей на одном тайле. Сохраните указатель pointer на начало списка. Указатель использует до 4-х байтов памяти.

Для монстров то же самое. Если у Вас может быть больше одного монстра на тайле, Вам также понадобится связанный список. Иначе, можно просто оставить указатель на монстра, либо найти какой-нибудь другой способ быстрого определения монстра, занимающего тайл. Это жизненно необходимо для боя, а также для определения столкновений игрок-монстр и монстр-монстр. Указатель занимает до 4-х байтов памяти.

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

Итак, моя система использует 11 байтов. Если я решу сделать подземелье размером 100 на 100 тайлов, то мне понадобится 100*100*11 = 110 000 байтов памяти. Как только ширина и высота увеличиваются, это число увеличивается в квадрате, так что будьте осторожны, чтобы не допустить момента когда память закончится. Если Вам нужно уменьшить размеры тайла, используйте битовые поля. Они дают Вам меньше 1 байта памяти для переменной.

Как генерируются подземелья?

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

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

Все это звучит довольно просто, но если Вы попытаетесь это запрограммировать, Вы узнаете, что все не так просто.

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

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

  1. Полностью заполните подземелье непроходимыми стенами.
  2. Выберите случайное число для количества комнат.
  3. Выберитесь случайное положение в подземелье (x, y).
  4. Произвольно выберите длину (ширину) комнаты (RoomLength).
  5. Произвольно выберите высоту (длину) комнаты (RoomHeight).
  6. Если площадь этой комнаты (вычисленная как RoomLength*RoomHeight) больше, чем максимальная площадь комнаты, вернуться к шагу 4.
  7. Если комната не помещается в подземелье, или перекрывается с другими комнатами, вернуться к шагу 3.
  8. Заполните прямоугольник с углами в (x, y) и (x + RoomLength, y + RoomHeight) пустым пространством.
  9. Возвращаться к шагу 3 пока не будут созданы все комнаты.
  10. Произвольно выберите стены в 2 произвольно выбранных комнатах или проходах.
  11. Алгоритм поиска пути проследит путь (проход) от одной стены до другой. Если пути не существует, вернуться к шагу 10.
  12. Повторять начиная с шага 10, пока каждая комната не станет достижимой.

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

Как они это делают? Прежде, чем будет создана комната, произвольно выбирается другая комната. Протягивается проход от этой комнаты, и затем в конце этого прохода добавляется создаваемая комната. "Dungeon building algorithm" ("Алгоритм построения подземелий") Mike Anderson'а на сайтах Roguelike News описывает это достаточно хорошо, следующее - просто итог.

  1. Полностью заполните подземелье сплошными стенами.
  2. Сделайте в подземелье комнату, аналогично описанному ранее.
  3. Произвольно выберите где-нибудь в подземелье стену комнаты (или прохода).
  4. Решите, что формировать, проход или комнату.
  5. Проверьте, достаточно ли пространства, чтобы сделать проход или комнату. Если комнаты мешают, уменьшите длину прохода/комнаты, чтобы присоединить ее к той комнате. Если проход/комната помещается в подземелье, вернуться к шагу 3.
  6. Сделайте проход/комнату (заполняя его пустым пространством).
  7. Вернуться к шагу 3 пока не будут сделано достаточно комнат/проходов.

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

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

Как удостовериться, что все комнаты и коридоры доступны?

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

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

Пример на языке Си:

(Сначала вызывается "FloodFill" с координатами тайла, затем "AreAllRoomsFilled")
void FloodFill(int x, int y)
{
  if ((Dungeon[x][y].Content == FLOOR) && (Dungeon[x][y].Flags != 1))
    Dungeon[x][y].Flags = 1;
  else
    return;
  FloodFill(x+1, y);
  FloodFill(x-1, y);
  FloodFill(x, y+1);
  FloodFill(x, y-1);
}
bool AreAllRoomsFilled(void)
{
  for (int Count1 = 0; Count1 < DungeonWidth; Count1++)
    for (int Count2 = 0; Count2 < DungeonHeight, Count2++)
      if ((Dungeon[Count1][Count2].Content == FLOOR) && (Dungeon[Count1][Count2].Flags != 1))
        return false;
  return true;
}

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

Как обнаруживать столкновения?

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

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

Есть несколько типов обнаружения столкновения: между созданиями, между созданием и миром (напр. стеной) и между предметами/заклинаниями и созданиями (напр. летящая стрела).

Обнаружение столкновения существо-мир простое - оно исходит из поиска особенностей карты в определенном месте. В рогаликах карта обычно делается из тайлов. Каждый тайл принадлежит к определенному типу (напр. стена, трава, пол, вода). Если Вы хотите узнать, может ли монстр встать на конкретный тайл, проверьте, флаг "проходимость" ("passable") тайла - если он true, существо может переместиться, в противном случае - нет. Если Вы используете для карты изображения и Вы хотите избежать тайлов, Вы можете использовать квадратичное дерево (quadtree): изначально, это дерево, где каждое поддерево (subtree) имеет 4 дочерних ветви. Чтобы его построить, рекурсивно разделите карту на четверти, и включите характеристики карты в соответствующую четверть; подчетверти сохраняются как дочерние текущей четверти в дереве. Разбиение прекращается когда у Вас остается достаточно небольшое количество характеристик в каждой подчетверти. Чтобы проверить, что находится в определенной позиции, Вы спускаетесь по квадратичному дереву (подобно пути у Вас было двоичное дерево, просто здесь Вы переходите к поддереву, которое содержит локацию), пока Вы не дойдете до листа. Затем Вы проверяете каждое свойство на этом листе, чтобы увидеть, является ли это пространством куда хочет переместиться создание. Для мира 3D, Вы обычно используете восьмеричные деревья (octrees), которые рекурсивно подразделяют пространство каждый раз на 8 октантов, а потом загружают многоугольники в каждый из квадрантных листов (leaf quadrants).

Для обнаружения столкновения существо-существо нужно 2 вещи: найти где находится конкретный монстр (или игрок) и найти кто или что находится в данном месте на карте. Чтобы найти где находится монстр или игрок, загрузите координаты карты с каждым монстром и с игроком. Таким образом, задав монстра, Вы можете быстро обнаружить где он расположен на карте. Чтобы обнаружить кто или что находится в конкретном месте карты немного труднее. Вы должны помнить, что в рогалике может быть много монстров и что монстры могут быстро меняться (многие могут умереть за это время, или многие могут быть созданы за то же время, особенно если они быстро размножаются). Простой алгоритм, который просматривает всех монстров, чтобы найти где-нибудь на карте одного, собирается начинает мучительно подвисать, когда у Вас 1000 монстров (и таким образом делается 1000 сравнений для каждого, когда он перемещается или атакует). Традиционно, рогалики используют простую систему, где каждый тайл на карте имеет указатель на монстра, стоящего на нем (или на NULL если на нем нет монстра), и Вы можете немедленно найти этого монстра просто делая обратную ссылку на этот указатель. Эта система затратна, поскольку занимает память, и у Вас никогда нет монстра в каждом тайле карты, но она самая быстрая. Хранение отсортированного массива или двоичного дерева монстров обычно не так быстро, поскольку Вы должны сортировать дерево или массив каждый раз, когда монстр перемещается. Хорошая техника должна подразделить карту на области, очень похожие на тайлы, за исключением того, что в каждой области может быть несколько монстров. Каждая область имеет список указателей на монстров, которые в ней находятся; если монстр находится на границе нескольких областей, каждая из этих областей должна указывать на монстра. Теперь Вам нужно только проверять столкновения между монстрами в одной области вместо каждого монстра на карте. Для 3D игр без тайлов, рассмотрите обнаружение столкновений разграничивающих сфер, разграничивающих параллелепипедов и многоугольников.

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

Как Вы создаете город?

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

Не забывайте иногда обновлять ассортимент предметов в ваших магазинах и генерировать горожан.

Как сделать дикую местность?

Подумайте ещё раз. В большинстве рогаликов дикая местность не имеет реального назначения.

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

Вам нужно будет наполнить квадратную область случайными элементами, используя для этого фрактальный алгоритм. Разделите квадрат на 4 части: верхний левый, верний правый, нижний левый и нижний правый. Заполните каждый их этих квадратов теми элементами, какими хотите. Затем рекурсивно поделите каждый квадрат на 4 меньших квадрата. Элемент, который заполнит каждый из этих квадратов, будет определён случайным выбором элемента из случайно выбранного соседнего квадрата. Процесс должен продолжаться до тех пора, пока квадраты не станут размером в 1 тайл.

Это лишь общий обзор. Фракталы гораздо более сложны.

Как сделать неизменные подземелья / миры?

Стабильное подземелье, как упоминалось раньше, - подземелье, которое не изменяется. В Angband'е, каждый раз, когда Вы приходите на уровень 1, он будет выглядеть совершенно иначе, что нереалистично. В Nethack'е, уровни сохраняются, так что уровень 1 всегда выглядит одним и тем же. Это и есть стабильность.

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

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

Конечно, это все не так просто. Когда Вы не знаете сколько структур (напр. предметы, монстры), будет записано в файл, Вы должны их сосчитать, а затем записать количество прежде, чем запишете эти структуры. Таким образом, когда Вы читаете данные из файла, Вы просто считываете их количество, и так Вы точно знаете сколько структур нужно прочитать из файла.

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

Как Вы храните действительно БОЛЬШОЙ мир?

Вы могли бы сделать большой мир и сделать его "постоянным" (т.е. уровни не изменяются каждый раз, когда Вы посещаете их), но это занимает много дискового пространства. Например, если каждый тайл в вашем игре занимает 20 байтов, и ваше подземелье - 256*256 тайлов, и у вас есть 100 уровней, они займут около 125 мегабайт диска, исключая предметы и монстров.

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

Сжать файлы уровней - очень просто. Есть иного методов сжатия, но мы обсудим тот, который хорошо уменьшает размер файлов, в то же время не отнимая слишком много времени. Он называется RLE (Run Length Encoding, полное название Repeated Running Length Encoding - кодирование длин повторов), и используется во многих файловых форматах. Он заменяет повторяющуюся последовательность данных на количество и один образец этих данных. Например, строка "AAAAA" будет заменена на "5A".

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

  • Больших объёмах
  • Аналогичных/редко или мало различающихся данных

Другими словами, сжатие малого количества данных с большим количеством разнообразия будет работать хуже и может даже увеличить конечный размер ("ABC" будет сохранено как "1A1B1C", что вдвое больше!).

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

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

Наилучший тип данных, чтобы хранить счётчик является одно-байтовая переменная, но она может хранить максимальную величину 255. Так что проследите за тем, чтобы Вы не превысили максимум, поскольку иначе вы получите в переменной неправильную величину.

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

В Си, если Вы включаете stdlib.h в свою программу, Вы получаете srand() процедуру, которая дает число между 0 и 65535, чтобы использовать его как произвольное зерно чисел, так и rand() процедуру, которое получает само произвольное число. Если Вам нужно выбирать любое зерно, используйте таймерную функцию, чтобы получить величину в этом дипазоне. Так что stdlib.h библиотека дает Вам 65536 уникальных "произвольных" последовательностей чисел, что позволяет создать 65536 уникальных уровней подземелий. Если ваша игра использует только 100, нет шансов, что Ваш игрок встретит два похожих уровня. Также, Вы должны держать список использованных "зерен", чтобы генерировать свои уровни без повторов. Если Вам нужен большой ряд уровней, найдите другие библиотеки функций случайных чисел с большими возможностями.

Теперь эти подсказки уменьшат размер вашего сохраняемого файла. Зерно случайных чисел, используемое srand() занимает 2 байта. Если Вы решите использовать этот метод, запомните не использовать то же самое зерно для ваших монстров и предметов (т.е. восстанавливайте значение зерна генератора после того, как сгенерируется формат подземелья), поскольку Вы не захотите иметь точно тех же монстров в точно тех же местах каждый раз, когда Вы заходите на уровень.

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

Есть конечно путь решить и это. Вы можете решить сохранять только определенное количество изменений, и независимо от того какие изменения происходят после, отменять изменения, которые происходили раньше всех. Например, если Вы храните 10 изменений, то когда Вы делаете 11-е изменение, первое изменение теряется. Одна проблема - то, как это может быть использовано. Например, если Вы прокапываетесь сквозь 10 стен, и копаете еще на одну стену дальше, первая стена будет появляться снова и захватывать Вас. Вы можете, вероятно, использовать это, чтобы перехватить монстра или сделать что-то свое. Это может решаться хранением многих изменений так что игрок вероятно никогда не встретит эту ситуацию. Даже если бы Вы хранили 1000 изменений, и каждое требует 20 байт, Вы будете расходовать около 20 килобайт самое большее, что всё ещё значительно меньше, чем 1,3 мегабайта, которые Вы будете иметь без всякого сжатия.

Как реализовать действие событий, происходящих далеко от игрока?

Смотря каких событий.

Монстры

Монстры на том же уровне, что и игрок должны "обрабатываться" (т.е. решать куда идти, кого атаковать, какие подбирать вещи и тому подобное) каждый ход. Вам просто нужно иметь список всех монстров на уровне и каждый ход проходя весь список решать, что будет делать каждый монстр. Монстры на других уровнях обычно не делают ничего. Когда уровень загружен, Вы можете обработать монстров, скажем, 50 раз (без их способности чувствовать игрока, иначе они убьют его!), просто сделайте это реалистичным. В этом случае монстры не остановятся в том же месте и будут выполнять те-же действия, которые они выполняли пока игрок был на этом уровне. Вы также можете оставить в памяти уровень выше и уровень ниже текущего местонахождения игрока (к примеру на эти уровни можно попасть с помощью, скажем, телепортирующих ловушек) и обрабатывать монстров также и там. Это сделает игру более реалистичной: пока игрок отсутствовал на этаже, на нём произошло большое количество изменений и большинство монстров поменяются/разбредутся.

Заклинания

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

Город

Город меняется достаточно часто. Каждое обновление происходит через определённое количество ходов или каждый раз при посещении.

Что такое LOS и как и зачем Вы должны ее делать?

LOS (анг. Line Of Sight, "линия зрения"), - способ определить что видно игроку/монстру.

В большинстве рогаликов, подземелье начинается в полной темноте. Когда Вы его обходите, Вы открываете его часть. Но Вы видите только монстров, которые освещены вашим источником света в данный момент. Эта освещенная область обычно круговая (или восьмиугольная - Angband). Также, свет распространяется достоверно - когда на его пути что-нибудь оказывается, образуются тени, Вы не можете заглядывать за углы, и так далее. Другой важный момент - то, что некоторые источники света освещают большую площадь чем другие - обычный факел освещает круг радиуса 1, у Фиала Галадриэли (the Phial of Galadriel) освещаемый радиус 3 и так далее.

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

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

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

Как сделать "отслеживание лучей"? Просто нарисуйте линию и пройдите вдоль нее. Очень хорош алгоритм линии Брезенхема (Bresenham's line) - он может оперировать с целыми числами, и не использовать никаких умножений или делений.

Хороший путь оптимизации этих алгоритмов - использовать тот факт, что круг симметричен относительно своих горизонтального и вертикального диаметров, а также диаметров, проведенных под 45 и 135 градусов. Итак, Вам нужно всего лишь оценить (x;y) от 0 до 45 градусов затем отслеживать лучи к (x;y), (y;x), (-x;y), (y;-x), (x;-y), (-y;x), (-x;-y) и (-y;-x). Это сделает алгоритмы быстрее.

Еще один метод называется подсчет тени (shadow casting). Он считается наилучшим: используется только O(n*n) времени и каждый тайл посещается только однажды, в то время как при нормальном подсчете лучей (raycasting) используется O(n*n*n) времени и делается необязательным посещение некоторых тайлов. Также он может оперировать с препятствиями, шириной менее 1 тайла. К несчастью, он самый трудный, включает вычисления обратных градиентов и операции с числами с плавающей запятой (хотя можно обойтись без этого, используя дробное представление чисел вместо десятичных разрядов или используя таблицу поиска).

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

###
.@.#
###

когда в рогаликах, Вы обычно видите:

####
.@.#
####

"Хаки" могут достаточно быстро это исправить. Если все тайлы перед угловым освещены, то угловой тайл тоже освещается. (Примечание: у алгоритма подсчета теней (shadowcasting) в принципе нет такой проблемы).

ПРЕДМЕТЫ

Как Вы храните список всех предметов в игре?

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

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

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

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

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

Как хранятся вещи в инвентаре игрока?

В предыдущем вопросе обсуждалось как вообще хранить вещи. Ещё один момент, который нужно помнить - это если игрок/монстр переносит больше одной одинаковой вещи (например, 5 стрел), Вам нужно позволить объединять эти вещи в кучу [stack]. Так, Вам понадобится такой атрибут как количество (для стрел, это будет 5). Если Вы хотите проверить общий вес инвентаря, не забывайте умножать вес каждой вещи на их количество, чтобы получить правильный вес. Иначе, у Вас будет вес одной вещи.

  1. Статический массив [static array]
  2. Динамический массив [dynamic array]
  3. Связанный список [linked list]
  4. Двоичное дерево [binary tree]

Статический массив - это простейший массив с фиксированным количеством элементов. Его легко читать и записывать в файл, потому что он является единичным беспрерывным куском и Вы всегда знаете его длину. Вам только нужно сохранить количество элементов массива, чтобы знать когда прекратить чтение из файла. К тому его легко сортировать. Недостатком метода является постоянный недостаток памяти. Если массив состоит из 50 элементов, а Вы переносите только 5 предметов, то 45 элементов будут впустую занимать память. Также, Вы не сможете переносить больше 50 вещей, так что Вам придётся постоянно проверять это при покупке/поднятии/каких-либо других действиях, добавляющих вещи в инвентарь.

Динамический массив это массив, который может быть любого размера, поэтому он может создаваться и освобождаться в любой точке вашей программы (с помощью "new" и "delete" в С++ и "malloc()" и "free()" в С). При создании его Вы определяете его размер. К сожалению, Вы не можете изменить размер массива когда он уже создан (можно использовать calloc(), но это не всегда срабатывает). Так что, когда Вы примете решение добавить или убрать вещь из инвентаря, Вам нужно будет создать другой массив с нужным количеством элементов, а затем скопировать всё из старого массива, который потом удаляется. Это медленно. Тем не менее, сортировка динамического массива и запись его в файл не сложнее, чем при работе со статическим.

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

Двоичное дерево это самый лучший выбор. Оно не требует сортировки (это происходит автоматически при добавлении вещи), в нём самый быстрый поиск (O(log n) времени), добавление и удаление вещей. Простой рекурсивный алгоритм может пройти все вещи и записать их в файл. К сожалению, это самая сложная в использовании структура.

Как сделать случайно генерируемые предметы?

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

МОНСТРЫ

Каких монстров можно использовать в своей игре?

Это зависит от контента и сюжета. Большинство монстров в рогаликах берутся из произведений Толкиена: орки, тролли, варги, хоббиты, гномы, эльфы, драконы и другие. Традиционные существа из различных мифологий и религий также используются: наги, медузы, горгоны, ангелы, демоны и другие. Некоторые взяты из "Dungeons and Dragons", как например, кобольды. Используется множество животных: от обычных обитателей пещер вроде летучих мышей и пауков, до нелепых личей и муравьев.

Создайте ваш собственный набор, если их недостаточно.

Как создать ИИ монстров?

ИИ (AI, artifical intelligence - искусственный интеллект) не так прост для реализации. Есть несколько различных способов, которые Вы можете использовать.

Система состояний [state machine] это, возможно, простейший вариант создания ИИ. Для начала возьмите кусочек бумаги. Затем, нарисуйте состояния (т.е. атаковать, куда-либо идти, убегать, воровать), обведите их кружком и нарисуйте стрелочки (с направлениями) от каждого состояния к каждому состоянию к которому Вы хотите придти. Эти стрелочки называются переходами. Подпишите каждый переход условием при котором он происходит.

Например:

     +-------------------+    +--------------+
     |   игрок далеко    |    | снова здоров |
     |                   v    v              |
 +-АТАКА             БЕЖИМ К ИГРОКУ       УБЕГАЕМ
 |   ^                   |                   ^
 |   | достаточно близко |                   |
 |   +-------------------+  почти при смерти |
 +-------------------------------------------+

Разумеется, в реальной игре это будет более сложно. Здесь должно быть намного больше решений и больше действий (одно для каждой вещи / заклинания). Как же всё это реализовать? Вместо использования обычных массивных "if" и "switch" сделайте двумерный массив с размерами, представленными значениями ширины и высоты. Вот таблица переходов.

                     АТАКА        ПРЕСЛЕДОВАНИЕ      УБЕГАЕМ
АТАКА                             игрок далеко   почти при смерти
ПРЕСЛЕДОВАНИЕ  достаточно близко
УБЕГАЕМ                           снова здоров

У Вас будет переменная для каждого монстра, которая скажет Вам в каком он состоянии. Об этом Вам говорит строка из таблицы переходов. Вы, с помощью теста проходите по этой строке и смотрите, выполняется ли условие. Если да, то установите это состояние в наивысший приоритет. К примеру, вы состоянии "АТАКА" . Посмотрите на самую левую колонку в поисках "АТАКА" (на самом верху). Тем посмотрите на строку. В первой колонке ("АТАКА") пустое место, так что тут нечего проверять. В колонке "ПРЕСЛЕДОВАНИЕ" находится "игрок далеко". Пусть это будет правдой - игрок убегает. В колонке "УБЕГАНИЕ" располагается "почти при смерти". Пусть это тоже правда - монстр серьёзно ранен. Жизнь монстра важнее, чем преследование игрока, так что система состояний включает состояние "УБЕГАЕМ" (разумеется, Вы можете сделать жизнь монстра менее важной :-) Здесь поведение базируется на состояниях. Это также можно реализовать с помощью большого количества "if". Если же Вы хотите избежать их, то используйте указатели на функции так же, как в Вашей таблице переходов и сделайте функцию для каждого действия/состояния.

Система состояний очень хороша и, в отличие от быстрого ИИ, созданного с помощью хаков [hacks], она отлично справляется со сложными ситуациями и не требует больших обработок или вычислений. Она также используется в других ситуацих программирования, так что ознакомление с ней полезно.

Другие способы, такие как нейронные сети, описаны в вопросах 8.x

Как сделать хороший ИИ монстров?

Предыдущий вопрос описывает как сделать ИИ. В этом мы обсудим как его улучшить и сделать интереснее.

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

Вот описание того, как сделать некоторые вещи.

Атаки: дается несколько различных атак, как монстр узнает какую выбрать? Если игрок далеко, монстр должен использовать дистанционные атаки и заклинания, если может. Он должен использовать такую атаку, которая (как он думает) нанесет наибольшие повреждения. Если он не знает, которая наилучшая, может быть он должен попробовать несколько атак и отсортировать их в соответствии с тем, какая срабатывает лучше всех.

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

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

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

Управление группой: монстр, предпочтительно сильный, может возглавить группу таких же/аналогичных монстров. Каждый из них следует за лидером (см. "следование за лидером"), и лидер решает куда идти и что делать (каждый монстр содержит указатель на лидера). Если лидер умирает, группа распадается.

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

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

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

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

Выслеживание (Stalking): монстр следует за игроком, оставаясь достаточно далеко, чтобы не быть увиденным. Когда игрок ослабевает, монстр атакует.

Воровство (Stealing): монстр крадет деньги/предмет у игрока, затем убегает или использует заклинание телепортации.

Брождение (Wandering): монстры ходят по округе и собирают предметы и золото.

Как они будут находить/следовать за игроком?

Давайте посмотрим. Игрок всегда перемещается. Монстр всегда перемещается. Другие монстры всегда перемещаются. Двери открываются и закрываются, вокруг возникают новые препятствия. Эта по-видимому простая проблема "как монстр следует за игроком" является действительно реальным вызовом.

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

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

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

Отслеживание звука также работает. Для каждого действия, которое создает шум, вычислите сумму звука и распространяйте подобно запаху (за исключением того, что звук как бы "не имеет длительности", так что это действует только на один ход, а затем исчезает). Монстр идет в направлении тайла с самой верхней суммой звука. Может быть каждый раз, когда они слышат звук, они начинают лучше прислушиваться и становятся более осведомленными о месте назначения? Что хорошо в звуке и запахе - Вы можете иметь магию и предметы, которые изменяют их, давая новые стратегии (т.е. заклинаний невидимости, более не достаточно, поскольку некоторые монстры могут унюхать Вас и атаковать в любом случае).

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

Быстрый алгоритм и объяснение поиска пути. Это нечто вроде алгоритма Дейкстры. Мы имеем здесь два списка: "открыто" и "закрыто". Список "открыто" хранит тайлы, которые возможно годны для короткого пути, и список "закрыто" хранит тайлы, где мы не пройдем, так что они не будут повторно обработаны. "Цена перемещения" - количество шагов монстра необходимых для достижения этого тайла, на пути ассоциированном с этим тайлом. Новые тайлы добавляются только в список "открыто" если они в нем не существуют, и тайлы замещают другие только если они имеют более короткий путь. Алгоритм таков:

  1. Найти тайл цели.
  2. Поместить тайл начала в список "открыто". Его цена - ноль.
  3. Пока список "открыто" не пуст, и путь не найден:
    1. Возьмем тайл из списка "открыто" с минимальной ценой перемещения. Назовем его "текущим" тайлом.
    2. Если это тайл цели, путь найден. Завершить цикл.
    3. Найдем тайлы на который можно немедленно пройти из этого тайла. Это должны быть тайлы вокруг текущего, которые не содержат препятствий. Назовем их "преемники".
    4. Для каждого преемника:
      1. Установите родителя преемника в "текущий" тайл.
      2. Установите цену преемника в цену родителя + 1, (для диагонального перемещения, и кв. корень от 2 вместо 1, если он дальше, чем пойти по диагонали в вашей игре).
      3. Если преемник не существует в или списке "открыто" или списке "закрыто": добавьте его в список "открыто". В противном случае, если цена преемника является ценой одного из тайлов в одном из списков: удалите случаи преемника из списков и добавьте преемника в список "открыто" В противном случае, если цена преемника - выше, чем того же самого тайла в одном из списков: отделайтесь от преемника
    5. Удалите текущий тайл из списка "открыто", и поместите в список "закрыто".
  4. Если пока цикл завершен потому что список "открыто" пуст, здесь нет пути.
  5. Если это не так, последний тайл взятый из списка "открыто", и его родители, описывают самый короткий путь (в обратном порядке - т.к. от игрока к монстру - и Вы должны читать список тайлов от конца к началу).

Как разнообразить действия монстров?

Большой проблемой монстров в рогаликах является их большая предсказуемость. Вы знаете, что один удар меча убивает любую мышь, одна стрела убьёт любого йика [yeek - монстр из Angband], а убийство дракона потребует хорошего вооружения и заклинаний. Даже если есть будет множество монстров, увидев одного, считай что видел их всех.

Увеличение различных способов позволит создавать монстров более уникальных и добавит больше разнообразия им. Создание монстров переносящих вещи это один из них. Итак, хороший ИИ монстров. Вот ещё несколько вариантов. Здесь мы поговорим о генетике и нейронных сетях.

В реальности, люди и животные имеют гены, отличающие их от множества им подобных. Некоторые быстрее других, некоторые умнее, некоторые здоровее и т.д. Что происходит в период жёсткого выживания и смертей. Итак, когда игрок заходит в подземелье и начинает убивать монстров, он видит, что они не остаются такими же, они становятся сильнее со временем, потому что слабый погибает, а сильнейший выживает и производит потомство более сильных детей. Как же реализовать генетику монстров? Решите, какие "гены" должны быть. Возможно это дополнительные очки здоровья. Сопротивления к различным стихиям и заклинаниям. Более сильная атака и защита. Поумнение и в конечном итоге способность использовать магию? Затем решите какие гены будут за это отвечать. К примеру, каждый ген здоровья влияет на очки здоровья на 5%. За каждый хороший ген здоровья очки здоровья будут повышаться на 5%, а за плохой ген здоровья понижаться на 5%. Так что если орк имеет 4 гена здоровья и все они "хорошие", орк будет иметь 120% от обычного количества очков здоровья. Если они все "плохие", то он получит только 80%.

Теперь, используем генетические алгоритмы, когда Вы создаёте монстра, случайно выберите 2 родителей. Как-нибудь скомбинируйте родительские гены (обычно используя случайный выбор и поиск среднего значения). Если родителей не хватает, создайте гены случайно. Как это поможет? Ну, орки с 80% очками здоровья будут быстро убиты игроком, так что только некоторые смогут оставить слабое потомство. Поэтому дети будут более здоровыми, потому что только более здоровые родители смогли выжить для размножения. Так как они становятся здоровее, то они более опасны для игрока.

Нейронные сети это возможость для компьютера обучаться. Они сложны для понимания, поэтому они редко используются в играх, но простейшие из них можно создать с небольшими затратами. Что в основном происходит: нейронная сеть получает несколько входных сигналов [inputs] и выдаёт несколько выходных сигналов [outputs]. В отличие от программы которую Вы создаёте, нейронная сеть учится из опыта и исправляет допущенные ошибки, так что Вы должны получить действительно серьёзного оппонента.

  • Текущее состояние здоровья (количество очков здоровья в процентах)
  • Текущее состояние здоровья оппонента (видимо тоже в процентах)
  • Расстояние до оппонента
  • Урон от предыдущей атаки
  • Движение к оппоненту
  • Движение от оппонента (убегание :-)
  • Какая-либо атака
  • Заклинание

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

Как Вам сделать Вашу систему боя?

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

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

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

Принцип боя очень простой. Убей столько оппонентов, сколько возможно, сам оставаясь в живых. Когда Вы умираете? Каждый рогалик (и большинство других игр), которые я видел, используют показатель "хитпойнты" ("hit point") (или "единицы здоровья" ("health point")), сокращенно HP. Вы имеете определенное количество HP и максимум. Максимум возрастает, когда Вы достигаете следующего уровня, а текущие HP возрастают, когда Вы отдыхаете/выпиваете снадобья лечения/используете лечебные заклинания и так далее. Когда Ваши текущие HP максимальны, Вы полностью здоровы. Когда они падают до 0, Вы умираете. Повреждения уменьшает их на количество полученных повреждений, а лечение увеличивает их на количество приобретенных единиц здоровья. Как Вам это рассчитать? Вместо медленного и проблематичного пути контроля переполнений (checking overflows) как обычно это делается в рогаликах, сделайте так: Когда происходит поражение (hit): Если повреждение больше или равен текущим HP, HP падают до нуля и Вы умираете. В противном случае вычесть повреждение из текущих HP. Когда Вы лечитесь: Если приобретаемое здоровье больше разности между вашим максимумом и вашими текущими HP, ваши текущие HP принимают значение максимума HP. В противном случае Вы складываете приобретаемое здоровье с текущими HP.

Когда Вы обрабатываете Ваших монстров, ИИ монстра решает, какое действие предпринять.

Как Вы представляете, храните и обрабатываете монстров?

Это хороший вопрос, но я не думаю, что много статей было написано об этом.

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

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

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

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

Большинство существ хранятся в некотором типе файла данных. Они считываются когда Вы загружаете игру. В игре, существа меняются часто - игрок убьет, могут размножаться, создаваться, вызываться, и так далее. Итак, Вы должны начать планировать отсюда. Связанный список достаточно хорош для хранения существ в игре; с размером массивов проблемы возникнут очень быстро. Если Вы программируете, используя объектно-ориентированное программирование, связанные списки могут сохранить любой объект, производный от базового класса. Новые существа добавляются в связанный список легко, удаление мертвых также легко.

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

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

Непредсказуемые вещи могут случиться. Можно использовать "подсчет ссылок" (см. www.memorymanagement.org). В основном, каждый монстр может иметь счет своих "потребителей", первоначально установленный в ноль. Когда что-то новое указывает на монстра, увеличьте счет на 1. Каждый ход, те предметы/заклинания/все, что указывает на монстра должны проверять: если монстр - мертв (выделяйте как-нибудь в монстре не удаляя его из памяти) и если так, удаляйте такие указатели и уменьшайте счет на 1. Только когда счет потребителей монстра является нулем, можно благополучно удалять из списка и памяти.

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

ЗАКЛИНАНИЯ

Так ли уж нужна магия в рогалике?

И да, и нет.

Магия это важнейший элемент в некоторых рогаликах. Классы, вроде мага или священника, существуют только благодаря магии. Магия даёт больше действий, выбора, стратегий и разнообразия игре. Некоторые вещи невозможны без магии (например, городской портал).

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

Как представить систему магии?

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

  • Стоимость его кастования, в единицах маны
  • Уровень, который Вы должны иметь, чтобы его использовать
  • Длительность (для заклинаний, изменяющих статус, очарования (enchantments) и т.п.)
  • Значение (наносимый ущерб / восстанавливаемые единицы здоровья и т.п.)
  • Количество созданий, на которых оно действует
  • Специальные эффекты, которые Вы используете, чтобы отрисовывать на экране
  • Ограничения для классов (не все классы могут знают заклинания)

Сделайте перечень заклинаний. Затем магические книги, посохи, свитки или любые предметы, которые могут впоследствии как-либо ссылаться на заклинания из этого перечня.

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

Что такое хорошая магическая система?

Ваша собственная.

По традиции, большинство рогаликов работают с магией аналогично "Dungeons and Dragons", за некоторым исключением. Каждое заклинание, имеет уровень. Вы должны находиться на том же или более высоком уровне (опыта или мудрости), чтобы узнать заклинание, и Вы должны быть классом, который может изучать заклинания, и имеет соответствующую книгу заклинания. У вас есть определенная сумма магической силы - маны. Она возрастает, когда Вы увеличиваете уровень. Каждое заклинание использует некоторое количество маны, и когда она заканчивается, Вы не можете кастовать заклинание. Мана восстанавливаются отдыхом и некоторыми зельями.

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

Как зачаровывать объекты или создавать "зачарованные" объекты?

Каждая вещь, которая может быть зачарована, должна иметь указатель на связанный список зачарований [enchantments]. Когда зачарование применено, оно добавляется в список с длительностью действия и эффектом, применённым к объекту; каждый ход длительность уменьшается и когда она заканчивается (достигает 0), зачарование снимается и эффект пропадает. Постоянные зачарования (типа проклятий) должны убираться почти так же; просто их длительность не уменьшается (пока не будет произнесено "снятие проклятия").

Как сделать случайные заклинания?

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

ПРОЧЕЕ

Как насчет скриптовых языков?

Языки скриптов получили большое внимание в последнее время. Angband собираются начать переписывать на Lua. Некоторые рогалики даже написаны на скриптовых языках полностью.

Есть много скриптовых языков: Perl, Lisp, Lua, Python, Ruby, Java и так далее.

  • Язык скриптов - обычно работает через интерпретатор, не компилятор;
  • Типы данных определяются автоматически (т.е. Вы не определяете к примеру длинное целое);
  • Много передовых техник программирования, например, связанные списки управляются автоматически;

Почему языки скриптов - хороши? Они позволяют Вам иметь дело с событиями быстро и легко. Это жизненно важно, если Вы имеете более-менее длинный сюжет в Вашей игре - сочинение длинной речи и списка действий (для вашего персонажа) на обычном языке программирования - трудно, если не невозможно, поскольку Вы можете быстро делать это на языке скриптов. Также предметы и заклинания, могут быть легко реализованы. Например, сабля, которая при ударе отравляет неприятеля, и излечивает Вас на 10 пунктов, когда неприятель - мертв, в своем скрипте требует только 2 строки:

ON_HIT Poison
ON_SLAY Heal(10)

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

Как заставить NPC разговаривать?

В Angband, "разговор" это специальная атака, наносящая 0 урона, а её именем являются слова, появляющимися на Вашем экране!

Для создания чего-нибудь более понятного, Вам понадобятся скрипты.

Как воспроизвести сюжетную линию?

Большинство рогаликов ее не имеют, поскольку фиксированный сюжет не вполне произволен - при переигрывании игры теряется большая часть удовольствия от нее, когда Вы знаете чего ожидать. У ADOM'а, тем не менее, фиксированный сюжет, и это действительно не умаляет игры (по крайней мере когда Вы первый раз играете в нее :-)).

В основном, используйте скрипты.

Возможно-ли делать деньги на рогалике?

Конечно Вы можете. Посмотрите на "Dungeon Hack" или "Diablo", к примеру. Бой в реальном времени и хорошо выглядящая графика очень рекомендуются, если Вы конечно хотите достойной оплаты. Но все основные рогалики бесплатны, и все за исключением того же Adom также разрешают свободное распространение и модификацию исходного кода - то, что вероятно необходимо, если Вы собираетесь писать свою игру сами.

Как реализовать групповую тактику (т.е. как сделать, чтобы в Вашей группе [партии] было несколько подобных игроков, а не только один)?

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

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

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

Как сделать borg'а? Почему Вы хотите его сделать?

Borg может рассматриваться как просто специальный тип ИИ монстра, который управляет игроком.

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

Работа над borg'ом возможно еще труднее, чем работа над самой игрой. Вы должны научить borg'а когда и как использовать каждый предмет, каждое заклинание, каждое действие, а он должен узнать, что против какого монстра в каких ситуациях хорошо действует... Если Вам все еще интересно, посмотрите borg'а из Angband'а.



Авторы:

  • David Damarell (описание Nethack, вопрос о деньгах от рогаликов)
  • Bridget (список новостных групп)
  • Jens Baader (список рогаликов и новостных групп)
  • Philip Swartzleonard (кто делает рогалики)
  • Greg McIntyre (описание ADOM, Egoboo, коррекции C++).

Источник: Roguelike Dev FAQ.
Перевод:

  • Бужинский Дмитрий (aka Bu)
  • Ждановских Сергей (aka Alchemist)
  • Sanja.