Метки и управление потоком

Инструкция Label

Инструкции меток (label) позволяют присвоить заданное имя определённой точке в программе. Они существуют исключительно для того, чтобы к ним можно было перейти с помощью вызова (call) или перехода (jump) из скрипта Ren'Py, функций Python или экранов.

label sample1:
    "Здесь метка 'sample1'."

label sample2(a="default"):
    "Здесь метка 'sample2'."
    "a = [a]"

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

label origin:
"a"
label hasblock:
    "b"
"c"
return

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

label global_label:
    "Под глобальной меткой..."
label .local_label:
    "...располагается локальная."
    jump .another_local
label .another_local:
    "И ещё одна!"
    jump .local_label

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

label global_label.yet_another_local:
    "И ещё одна!"
    jump .local_label

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

label another_global:
    "Теперь перейдём к локальной метке, расположенной где-то ещё."
    jump global_label.local_name

Инструкция метки может принимать необязательный список параметров. Эти параметры обрабатываются так, как описано в PEP 570, за двумя исключениями:

Во-первых, значения параметров по умолчанию вычисляются во время вызова.

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

default a = 3

label start:
    menu:
        "Call":
            call label_with_params(5)
        "Jump":
            jump label_without_params
    jump start

label label_with_params(a):
label label_without_params:
    e "a = [a]" # отображает 5 или 3 в зависимости от выбранного пути
    return

Обычно не имеет смысла переходить к метке с параметрами с помощью jump или предыдущей инструкции. Пример меток с параметрами см. в разделе об инструкции call.

Инструкция Jump

Инструкция jump используется для передачи управления указанной метке.

Если присутствует ключевое слово expression, следующее за ним выражение вычисляется, и полученная в результате строка используется как имя метки для перехода. Если ключевое слово expression отсутствует, имя метки для перехода должно быть указано явно.

Можно передать имя локальной метки, как с expression, так и без, либо с префиксом в виде имени глобальной метки ("global_label.local_label"), либо с точкой в начале (".local_label").

В отличие от call, jump не помещает следующую инструкцию в стек вызовов. В результате нет способа вернуться в то место, откуда был совершён переход.

label loop_start:

    e "О нет! Кажется, мы попали в бесконечный цикл."

    jump loop_start

Инструкция Call

Инструкция call используется для передачи управления указанной метке. Она также помещает следующую инструкцию в стек вызовов, позволяя инструкции return вернуть управление инструкции, следующей за call.

Если присутствует ключевое слово expression, следующее за ним выражение вычисляется, и полученная в результате строка используется как имя метки для вызова. Если ключевое слово expression отсутствует, имя метки для вызова должно быть указано явно.

Можно передать имя локальной метки, как с expression, так и без, либо с префиксом в виде имени глобальной метки ("global_label.local_label"), либо с точкой в начале (".local_label").

Если присутствует необязательное выражение from, оно фактически добавляет инструкцию метки с заданным именем сразу после инструкции call. Явная метка помогает гарантировать, что сохранённые игры со стеками возврата смогут вернуться в нужное место при загрузке с изменённым скриптом.

Инструкция call может принимать аргументы, которые обрабатываются так, как описано в PEP 448.

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

label start:

    e "Сначала мы вызовем подпрограмму."

    call subroutine

    call subroutine(2)

    call expression "sub" + "routine" pass (count=3)

    return

# ...

label subroutine(count=1):

    e "Я пришёл сюда [count] раз(а)."
    e "Далее мы вернёмся из подпрограммы."

    return

Предупреждение

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

Использование опции "Add from clauses to calls" при сборке дистрибутива игры может решить эту проблему.

Инструкция Return

Инструкция return извлекает верхнюю инструкцию из стека вызовов и передаёт ей управление. Если стек вызовов пуст, инструкция return перезапускает Ren'Py, возвращая управление в главное меню.

Если инструкции return передаётся необязательное выражение, оно вычисляется, и его результат сохраняется в переменной _return. Эта переменная имеет динамическую область видимости для каждого контекста.

Специальные метки

Следующие метки используются Ren'Py:

start

По умолчанию Ren'Py переходит к этой метке при запуске игры.

quit

Если эта метка существует, она вызывается в новом контексте, когда пользователь выходит из игры.

after_load

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

before_load

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

Как только эта метка вернёт управление, Ren'Py продолжит загрузку. Экран не будет обновляться до завершения загрузки, поэтому анимации не будут воспроизводиться. Когда загрузка завершится, состояние игры будет заменено загруженным состоянием, и поэтому изменения, сделанные в этой метке, будут утеряны.

splashscreen

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

before_main_menu

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

main_menu

Если эта метка существует, она вызывается вместо главного меню. Если она возвращает управление, Ren'Py начнёт игру с метки start. Например, следующий код немедленно начнёт игру, не показывая главное меню.

label main_menu:
    return
after_warp

Если эта метка существует, она вызывается после warp, но до выполнения инструкции, к которой был совершён переход. Пожалуйста, обратитесь к разделу Переход к строке.

hide_windows

Если эта метка существует, она вызывается, когда игрок скрывает окна правой кнопкой мыши или клавишей H. Если она возвращает true, скрытие отменяется (предполагается, что скрытие уже произошло). В противном случае скрытие продолжается.

Ren'Py также использует следующие метки для отображения некоторых из специальных экранов:

  • main_menu_screen

  • load_screen

  • save_screen

  • preferences_screen

  • joystick_preferences_screen

Функции меток и управления потоком

renpy.call_stack_depth()

Возвращает глубину стека вызовов текущего контекста — количество вызовов, которые были выполнены, но из которых ещё не произошёл возврат или которые не были удалены из стека вызовов.

renpy.dynamic(*variables, **kwargs)

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

Поддерживаются переменные в именованных хранилищах.

Если переменные переданы как именованные аргументы, значение аргумента присваивается переменной с соответствующим именем.

Примеры вызовов:

$ renpy.dynamic("x", "y", "z")
$ renpy.dynamic("mystore.serial_number")
$ renpy.dynamic(players=2, score=0)
renpy.get_all_labels()

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

renpy.get_return_stack()

Возвращает список, представляющий текущий стек возврата. Стек возврата — это список имён инструкций.

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

renpy.has_label(name)

Возвращает true, если name является допустимой меткой в программе, или false в противном случае.

name

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

renpy.mark_label_seen(label)

Отмечает указанную метку как уже выполненную в системе текущего пользователя.

renpy.mark_label_unseen(label)

Отмечает указанную метку как ещё не выполненную в системе текущего пользователя.

renpy.pop_call()

Удаляет текущий вызов из стека вызовов без возврата в место вызова. Также отменяет изменения значений dynamic переменных так же, как это сделала бы инструкция return в Ren'Py.

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

renpy.seen_label(label)

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

renpy.set_return_stack(stack)

Устанавливает текущий стек возврата. Стек возврата — это список имён инструкций.

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

Наиболее распространённое применение этой функции — использование:

renpy.set_return_stack([])

для очистки стека возврата.

Контексты

Контексты используются внутри Ren'Py для управления изменяемым и сохраняемым состоянием игры. Контексты включают:

  • текущую выполняемую инструкцию Ren'Py,

  • стек вызовов, как описано выше, а также имена и предыдущие значения динамических переменных, созданных renpy.dynamic(),

  • изображения, отображаемые в данный момент (и информация о них, такая как их атрибуты, применённые к ним трансформации и т. д.),

  • отображаемые экраны и переменные внутри них,

  • аудио, которое воспроизводится или находится в очереди.

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

Ren'Py также создаёт новые контексты в рамках повтора (replay) и при скрытии интерфейса.

Создание языка экранов значительно уменьшило потребность в создании контекстов.

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

renpy.call_in_new_context(label, *args, **kwargs)

Эта функция создаёт новый контекст и начинает выполнение Ren'Py-скрипта с указанной метки в этом контексте. Откат в новом контексте отключён, а сохранение/загрузка будут происходить в контексте верхнего уровня.

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

Принимает необязательный именованный аргумент:

_clear_layers

Если True (по умолчанию), слои очищаются перед началом нового взаимодействия. Если False, слои не очищаются. Если передан список, очищаются только слои из этого списка.

renpy.context()

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

Объект сохраняется и участвует в откате.

renpy.context_dynamic(*variables)

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

Поддерживаются переменные в именованных хранилищах.

Примеры вызовов:

$ renpy.context_dynamic("x", "y", "z")
$ renpy.context_dynamic("mystore.serial_number")
renpy.context_nesting_level()

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

renpy.invoke_in_new_context(callable, *args, **kwargs)

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

Дополнительные позиционные и именованные аргументы передаются в вызываемый объект.

Контекст, созданный этой функцией, не может выполнять Ren'Py-скрипт. Функции, изменяющие поток выполнения Ren'Py-скрипта, такие как renpy.jump(), обрабатываются внешним контекстом. Если вы хотите вызвать Ren'Py-скрипт, а не Python-функцию, используйте вместо этого renpy.call_in_new_context().

Принимает необязательный именованный аргумент:

_clear_layers

Если True (по умолчанию), слои очищаются перед началом нового взаимодействия. Если False, слои не очищаются. Если передан список, очищаются только слои из этого списка.

renpy.jump_out_of_context(label)

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

renpy.reset_all_contexts()

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

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

Из-за того, насколько полно эта функция сбрасывает Ren'Py, она немедленно завершает текущую инструкцию.

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