Hanagatari
  1. 1. Лицензия и скачивание
    1. 1.1. Известные косяки
    2. 1.2. Игровые юниты
  2. 2. В двух словах
  3. 3. Статистика
    1. 3.1. CLOC – Count Lines of Code
    2. 3.2. Классы и структура
      1. 3.2.1. Основа

Два дня назад, в субботу, было день рождение одного моего друга. За неделю до этого я задумался о том, что бы ему подарить – пожалуй, самый сложный процесс во всём этом деле :) Вдруг я вспомнил, что незадолго до этого он мне мельком рассказал о своей идее аркадной игры – с тем расчётом, чтобы игра была незаморочистой, но в то же время необычной.

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

В общем, за 6 дней до часа Икс я начал писать эту несложную аркаду и пару дней спустя понял, что поставил себе этакий «developer's challenge». Вообще, это был первый такой интенсивный блицкриг, который я себе устроил (не менее примечательный Швак – всё же более обстоятельный проект).

Получившуюся игру я с первого дня спонтанно стал называть Hanagatari (花語) – история цветов (или цветка, кому как больше нравится – в японском нет множественного числа).

http://proger.i-forge.net/Мои проги/Delphi/Hanagatari/screenshot.png

Лицензия и скачивание

Всё содержимое архива ниже, кроме того, что сделано не мной (графика, для которой нет исходников) я выкладываю как «общественное достояние» (in public domain). Можете использовать как угодно, единственное пожелание – обратная ссылка :)
Ну, и можете также черкануть пару строк в (#comments комментах)).

Архив Hanagatari можно скачать здесь (вместе с исходниками).

Известные косяки

Собственно, знакомый косяк один – после нескольких минут игры она падает с Access violation – видимо, глюк с системой событий. Но блицкриг с проектом закончился и исправление этой ошибки пока не придвидится.

Игровые юниты

В задумке было создать больше юнитов и добавить так называемые «disasters» (события, направленные против игрока, чтобы он не чувствовал себя одиноким), но на них времени не хватило (хотя поддержка нужных моментов в движке уже есть).
С задумкой и заметками можно ознакомиться в файле Illustrations & other\Design notes.txt.

Вот список элементов, присутствующих в этой версии игры.

  1. Почвы, в порядке улучшения свойств: глина → песок → почтва → чернозём. Каждый тип почвы имеет бонус (отрицательный или положительный) к здоровью растущих на нём цветов. Некоторые цветы не могут расти на неплодородных почвах (Медонос не растёт на песке и глине, а Провидцу нужен чернозём).
  2. Виды цветов, каждый имеет свои особенности:
    Первоцвет
    Базовый юнит, может цвести, создавая другие 5 видов цветов.
    Ползущий цветок
    Похож на Первоцвета, но может цвести (ползти) туда, где не долетают семена первого (то есть за забором).
    Медонос
    Обеспечивает приток маны.
    Боб
    В задумке должен улучшать почву со временем и давать ей перманентный бонус, но пока только имеет способность на 2-м уровне убирать тропинки под собой.
    Провидец
    Даёт больше информации о юнитах, не принадлежащих игроку – каждый уровень Провидца увеличивает количество этой инфы. В задумке он также должен предсказывать наступление disasters.
    Сорняк
    Этот юнит не создаётся игроком (хотя в перспективе – почему нет?). Неприхотливое растение, в отличии от растений игрока может расти уже на занятых ячейках. Может Расселяться, случайным образом создавая росток нового Сорняка и попутно повреждая растения игрока в радиусе 1 клетки.
  3. Объекты карты: свет (холмик, бонус к HP), тень (дерево, лоза – пенальти к HP), тропинка (пенальти к HP, но Боб убирает тропу), озеро (в задумке растения рядом более плодородны и есть специальный тип растений-ирригаторов), ограда (преграда для семян Первоцвета).

По задумке количество HP измеряется в процентах (хотя внутренне есть максимальное и текущее значение, пок которым этот процент вычисляется). В зависимости от этого значения эффективность цветка варьируется – 100% – номинальная, 50% – половинчатая (например, Медонос будет приносить не 2 единицы маны за ход, а 1) и т.д.

В файле Illustrations & other\Design notes.txt есть больше информации о задумке.

В двух словах

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

Всё дело писалось на Delphi 7 без использования сторонних библиотек, не считая моего любимого TPNGObject и портированных мною на Юникод потомков стандатрных TStringsW и TCustomIniFileW.

Большинство графики я рисовал сам в Illustrator CS5 и Photoshop CS. В целом разработка заняла 6 дней (с 20 Марта по утро 26 Марта) – на то время я насколько мог отодвинул остальные свои дела и занимался только игрой.

Статистика

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

CLOC – Count Lines of Code

Статистика кода, который я написал за эти 6 дней с нуля (то есть без учёта портированных юнитов и моих StringUtils и Utils):

41 uniqe files.

http://cloc.sourceforge.net v 1.53  T=1.0 s (19.0 files/s, 6585.0 lines/s)
-------------------------------------------------------------------------------------
File                                              blank        comment           code
-------------------------------------------------------------------------------------
./Components/HGMap.pas                              355             19           1556
./Components/HGConfig.pas                           174             34            760
./HGObjects.pas                                     109             15            416
./HGAbilities.pas                                    97             18            371
./MainForm_.pas                                      48              3            243
./HGUtils.pas                                        39             15            238
./Components/HGPackage.pas                           49              7            218
./Components/HGGame.pas                              51              2            217
./Components/HGAbilityPanel.pas                      47              2            206
./Components/HGLogBox.pas                            42              2            191
./Components/HGCellInfoPanel.pas                     44              2            189
./Components/HGFlowerInfoPanel.pas                   44              2            183
./DebugInfoForm_.pas                                 29              2            105
./Components/HGPlayer.pas                            26              6             97
./HGCells.pas                                        21              5             69
./Components/HGMapGenerator.pas                      17              4             68
./CellInfoForm_.pas                                  12              1             52
./IntroForm_.pas                                     11              2             30
./Hanagatari.dpr                                      3              1             16
-------------------------------------------------------------------------------------
SUM:                                               1218            142           5225
-------------------------------------------------------------------------------------

Для сравнения – статистика по коду UverseWiki:

-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
PHP                            201           2771           1321          11512

Хотя, конечно, нельзя напрямую сравнивать число строк на интерпретируемом языке вроде PHP и на Delphi.

Классы и структура

Во всём проекте определён 41 класс, включая вспомогательные (вроде расширенного для конкретной ситуации TObjectList), плюс 20 небольших классов для игровых объектов (цветов, способностей и ячеек), 3 фрейма и 4 формы. Их структура почти полностью сложилась стихийно, но мне нравится, как это получилось в результате.

Очень кратко опишу общую логику игры.

Всё исходит от игровой карты. Карта делится на ячейки (cells), каждая ячейка делится на слоты – каждый слот может занимать некий один объект, например, цветок или забор. Слоты имеют имена, поэтому одна ячейка может содержать одновременно и забор, и цветок, но не два цветка одновременно (хотя это не относится к сорнякам – они помещаются в отличный от «цветкового» слот, чтобы создать конкуренцию игроку).

Сам класс карты (THGMap) разбит на три из-за своего объёма (THGBasicMap, THGInteractiveMap и THGSelectableMap).
Каждый объект в слоте в игре называется flower и происходит от базового классаTHGFlower. Фактически, если иерархию представить в виде дерева, то цветок будет его листьями: карта → ячейка → слот → цветок.

Для обслуживания карты и собственно механизма игры есть вспомогательные классы «движка». Это, например, основные классы:

THGConfig
Центр, где сосредоточена информация о текущем состоянии игры и ссылки на основные объекты, например, на карту;
THGGame
«Центр управления полётами», который сам не хранит никаких данных (для этого используется класс конфига), но координирует деятельность всех классов;
THGPlayer
Класс «игрока», хранящий инфу о накопленных ресурсах;
THGPackage
Абстрактный класс, обеспечивающий доступ к игровым ресурсам. В простейшем случае, как это сделано сейчас, от него наследуется потомок, напрямую обращающийся к файловой системе. Возможные варианты развития – некий архивный файл со всем этим добром в одном месте;
THGAbility
В отличии от предыдущих этот класс более тесно связан с игровым процессом – он используется для создания «способностей», то есть действий, которые игрок может выполнять с помощью цветов – иначе говоря, магия, хотя она не обязательно требует ману. Зато почти всегда у неё есть период перезарядки (cooldown);
THGMapGenerator
Абстрактный класс для генерации (загрузки) карт в собственно объект карты. От него можно наследовать либо случайный их генератор, либо загрузчик из файла.

Основа

В основе игрового движка Hanagatari лежат 2 основопологающих принципа: система событий и система бонусов.

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

например, если цветок получил новый уровень, то он отправляет событие flower level up и заинтересованные объекты его обрабатывают – например, если открыта форма с информацией об этом цветке, то она обновляется, или если к игре подцеплен лог, то он отображает игроку собщение о повышении уровня.
Это позволяет очень сильно снизить связность.

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

Система бонусов контролирует то, как объекты влияют друг на друга. Например, объект медонос даёт приток маны каждый момент игрового времени (tick). Но как реализовать это на практике? При создании цветка прибавлять в полю игрока нужное значение, а при удалении – убавлять? А как быть, если из всех возможных бонусов нужно выбрать максимальный, а не сумму? И как быть с многопоточностью?
Для решения этих задач и существует система бонусов, которая состоит из трёх классов:

THGBonusMap
Центральный класс, потомок от TOBjectHash (комбинация THashedStringList из юнита IniFiles и TObjectList из Contnrs). Он написан с прицелом на thread safety. В основном он ищет нужную бонусную корзину и переправляет вызовы своих методов ей (через критические секции);
THGBonusBasket
Та самая бонусная корзина. Одна цель бонуса (например, приток маны каждый ход) имеет одну корзину. Корзина в свою очередь содержит список всех поставщиков бонусов – то есть игровые объекты, обеспечивающие этот бонус. Каждому поставщику сопоставлено конкретное значение бонуса в виде объекта THGBonusItem (например, +2 единицы маны за ход).
THGBonusItem
Каждый поставщик бонуса имеет по одному такому объекту для каждой цели бонуса.

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