События - TrueCat17/Ren-Engine GitHub Wiki

Система событий

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

  1. Переменная rpg_event принимает значение (строку) из следующего списка возможных значений:
    • "enter" - вход в место (не в локацию, ВАЖНО!),
    • "action" - нажатие игроком "кнопки действия",
    • "sit_down" - игрок сел,
    • "stand_up" - встал,
    • "take" - подобрал предмет в инвентарь,
    • "use" - использовал подобранный предмет,
    • "remove" - выбросил/выложил предмет из инвентаря,
    • "no_exit" - попытка выйти через заблокированный выход.
  2. Переменная rpg_event_object - имя объекта, если обрабатывается событие инвентаря (take, use, remove).
  3. Берётся список меток вида location_name__place_name, для текущего места в текущей локации, причём вместо части имени допускаются символы ? (1 любой символ) и * (0 и более любых символов).
    Если персонаж не находится внутри какого-либо зарегистрированного места, метка будет иметь вид location_name__unknown. Перед этим для добавления в список ищутся метки вроде quest__location_name__place_name для каждого активного в данный момент квеста.
  4. Из него удаляются несуществующие метки и повторы (остаются только последние среди повторов).
  5. Проходимся по каждому элементу (начиная с начала) и вызываем метку с таким названием.
  6. Если метка просто завершает своё выполнение, то считаем обработку события завершённым.
  7. Если же переменная rpg_event_stop была установлена в False - переходим к следующей метке из того списка.

Также при входе в локацию с именем some_location выполняется метка on__some_location (если есть).
Символы ? и * в этом случае не являются заменителями других символов.

Примечание: если несколько мест пересекаются (в том числе и одно место внутри другого), и персонаж во время события стоит на этом пересечении, то выбор какого-то определённого места не гарантируется и зависит от фазы Луны.
Не полагайтесь на то, что если в данный момент выбирается что-то одно, то оно будет выбираться всегда.
Старайтесь вообще избегать пересечения областей у мест.


Цикл обработки событий

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

label start:
	call my_prologue
	call rpg_loop

Этот цикл в rpg_loop бесконечен (while True), поэтому его вызов должен быть последним.
Слово "цикл" употребляется здесь не просто так. Именно из него вызываются метки для реакции на события, и именно к нему возвращается управление после их завершения.

Соответственно, вызов jump (вместо call) внутри любой метки не приведёт к возврату управления, из-за чего игра будет считаться завершённой, и откроется главное меню.
Не используйте jump в RPG (повтор для тех, кто невнимательно читал RPG-Содержание).


Почему всё так сложно?

Эта система должна быть универсальной и расширяемой.

Для начала, она позволяет реализовывать простые вещи простыми средствами (см. пример-1).

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

Кроме того, эта система позволяет "уточнять" поведение. Т. е. для примера из прошлого абзаца всё ещё есть возможность сделать метку my_location__closed-2, и именно ей будет передано управление в начале (для места closed-2). Она может, например, проверить какое-то условие, и если оно выполняется, то запустить какую-то специфическую часть сценария, а иначе - вернуть управление более "общей" метке (см. пример-3).

Тут следует отметить, что чем больше символов нужно поставить на место символов-заменителей, тем позже будет идти название этой метки в списке меток. Благодаря этому в прошлом абзаце и происходит сначала вызов my_location__closed-2, и лишь затем *__closed*, а не наоборот.

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

И наконец, она позволяет менять стандартное поведение на то, что нужно вам!
Нужно, чтобы перед "стандартным" именем location_name__place_name добавлялся номер дня вроде day5? Легко.
Добавить и время суток day/night/some_other? Запросто.
Вызвать стандартную версию, если отсутствует специфическая метка? Уже в комплекте.
Об этом повествует последний абзац, а пока несколько примеров.


Пример-1

Простейший случай.

Игрок находится на локации square, в месте path-1.
Игрок подбирает предмет "key".
Есть метка square__path-1, больше никаких похожих меток нет.
Следовательно, она и будет вызвана (в случае её отсутствия никаких ошибок бы не было).

label square__path-1:
	if rpg_event != "take" or rpg_event_object != "key":
		return
	
	"Что это за ключ?"
	window hide

Пример-2

Обработка нескольких мест в одной метке.

Следующая метка будет подходить и для обработки места closed в локации house, и для обработки места closed-2 в локации my_location.

Это будет происходить из-за того, что и house__closed, и my_location__closed-2 подходят под шаблон *__closed*.

label *__closed*:
	me "Я нахожусь в локации <" + cur_location_name + ">, в месте <" + cur_place_name + ">."
	window hide

Пример-3

Специализация и возврат к обобщенной метке.

# специализированная метка для конкретного места в конкретной локации
label my_location__closed-2:
	if day_name == 'sunday':      # выходной день
		$ rpg_event_stop = False    # продолжение обработки события более общей меткой
		return                      # выход из текущей метки
	# и ничего не мешает вообще удалить это условие, если такое поведение не нужно
	
	"Обычная закрытая дверь среди десятка таких же."
	"Внезапно я услышал за ней голоса, и мне захотелось отойти от неё."
	window hide

# общая метка
label *__closed*:
	"Закрыто."
	window hide

Изменение стандартного поведения

Для получения списка меток движок использует функцию get_place_labels.
Вы можете заменить её реализацию на свою, чтобы модифицировать её поведение.

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

def get_place_labels():
	usual_label = cur_location_name + '__' + (cur_place_name or 'unknown')
	
	res = []
	for quest in get_started_quests():
		res.extend(get_glob_labels(quest + '__' + usual_label))
	res.extend(get_glob_labels('day' + str(day_num) + '__' + usual_label))
	res.extend(get_glob_labels(usual_label))
	return res

В начале мы просто определяем, как будет выглядеть название места, которое мы ищем.
Это будет что-то вроде square__path-1 или house__unknown.

Далее мы создаём список, который будет результатом работы этой функции, и добавляем в него:

  • метки вида quest__square__path-1 для каждого активного в данный момент квеста quest,
  • метки вида day5__square__path-1 для текущего дня, номер которого берём из переменной day_num (это не стандартная переменная, вы сами должны создать и изменять её, если она вам нужна),
  • метки вида square__path-1.

И в конце возвращаем этот список как результат работы этой функции.
Напоминание:

  • Названия несуществующих меток будут проигнорированы, это не будет считаться за ошибку.
  • В случае повторов будут удалены все повторы, кроме последнего. Благодаря этому та же метка *__closed* будет обработана в последнюю очередь, несмотря на то, что будет попадать в список при каждом запросе меток вида quest__square__closed-2 и day4__square__closed-2.

Теперь чуть подробнее о функции get_glob_labels.
Это стандартная функция, которая возвращает список меток, под шаблон которых подходит переданный аргумент.
Допустим, есть 5 меток: house__closed*, house__street, square__closed-2, square__closed-*, *__closed*.
Тогда вызов get_glob_labels('square__closed-2') вернёт список ['square__closed-2', 'square__closed-*', '*__closed*'], причём обязательно в таком порядке, т. к. первый элемент совпадает полностью, у второго нужно заменить 1 символ, а у последнего - 8.
Остальные же 2 метки будут проигнорированы, т. к. они совсем не подходят под запрос.


Далее ->
<- Назад

⚠️ **GitHub.com Fallback** ⚠️