Techlog #2. Experimental. Practical Example. Quests [ru]
Added 2023-05-03 19:00:00 +0000 UTC
Разберём пример кода, который связан с системой квестов в игре.
Вот самый класс, который предназначен для инициализации экземпляра класса в нашей игре. Разберём поля в первом блоке и зачем они:
id - уникальный идентификатор. Конкретно мне он нужен, чтобы я мог находить экземпляр при нему в случай резкой необходимости.
name - название, которое отображается в окне с квестами для игрока.
description - непосредственно описание, которое отображается там же. steps - лист с шагами у квеста. В игре вы могли заметить, что у квестов могут быть шаги.
reward - лист с вознаграждениями. По умолчанию пустой лист.
availability - строка с неким булевым выражением для проверки доступности квеста.
К подобному решению я пришёл ни сразу, а как только столкнулся с тем, что между обновлениями мне приходилось исправлять какие-то названия, а так же исправлять доступность квестов и награды за них. Дело в том, что при инициализации экземпляра он остаётся в базе игры таким навечно и для того чтобы что-то исправить - вам надо через костыли находить его в модуле store и вручную переписывать там нужные поля. Я решил, что не хочу с этим возиться и единственное значение, которое я хочу чтобы там оставалось навечно справедливым - это его флаг done. То есть индикация того, выполнен квест или нет.
Пройдёмся по остальным методам класса:
Названия большинства полей звучат и выглядят исчерпывающе. Если вы знакомы с классами чуть больше, то вы должны понимать, что getter и setter - довольно стандартные вещи для класса. Поэтому я их инициализировал для тех полей, в которых, я в теории, могу допустить ошибку. Например допустить опечатку в названии или передумать насчёт наград за этот квест.
Практически идентично выглядит класс и для шагов квеста. Это, по сути, такие же квесты, но только поменьше.
Как выглядит создание экземпляра.
Я пришёл к паттерну с использованием label для объявления подобных переменных. Вся идея в том, что при запуске/загрузке игры они каждый раз инициализируются заново и я могу легко исправлять приведённые раннее ошибки.
Вы можете заметить здесь несколько других методов вроде set_variable и is_quest_done. Это мелкие утилитарные функции. 
Идея в том, чтобы я не перезаписывал экземпляр полностью, если он уже был ранее объявлен, и не перезаписать его значение done, которое является ключевым для игры.
Я не придумал способа умнее, чем записывать функции в виде строк для поля reward и availablity. Дело в том, что я хочу чтобы после завершения квеста выполнялся ряд функций, которые будут выдавать награды моему персонажу: Повышение статов, завершение других квест, добавление новых шагов и вообще чего угодно, на что у меня хватит фантазии.
Функция eval является стандартной практически в каждом языке программирования. Она преобразовывает вашу строку в рабочий код. Если бы я писал туда просто сами функции, то в лист записывались бы просто результаты их выполнения и это не имело бы вообще никакого смысла.
Точно такая же ситуация с availability. Зачем оно вообще нужно? Часто бывает такая ситуация, что я хочу чтобы квест стал доступен только после того как игрок выполнит несколько условий. Как только они все будут удовлетворены - квест игрок таки получит.
is_quest_done - простой метод для проверки того, есть ли квест в данный момент у персонажа в квестах и выполнен ли он при этом. В примере кода выше он нужен для того, чтобы игрок зайдя в новую версию игры - уже сразу получил этот квест если он будет для него доступен, так как в ранних версиях игр его даже и не было объявлено.
Что такое label_translation[название_ключа]? Дело в том, что RenPy не позволяет вам добавлять переводы стандартными методами для строк которые вы объявляете внутри python кода. Поэтому я завожу отдельный dictionary для переводов подобных строк.
Выглядит это примерно следующим образом:
У каждого персонажа в игре, есть собственный подобный словарик из значений. После - они все склеиваются в один и объявляются самыми первыми после запуска игры.
Все переменные в игре компонуются по отдельным лейблам и по итогу оказываются в ключевых лейблах для RenPy вроде after_load и start.