Инструкции Python

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

С версии 8.4 Ren'Py поддерживает Python 3.12. Версия Python, используемая Ren'Py, может быть обновлена в будущих релизах. Скрипты Ren'Py должны быть совместимы с Python 3.12 и более поздними версиями.

Примечание

Если вы знаете Python, вы сможете этим воспользоваться. Однако не всё, что вы знаете о Python, будет применяться напрямую. Например, пакеты Python, не входящие в состав Ren'Py, могут не работать внутри Ren'Py.

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

Наконец, хотя многие инструкции имеют эквиваленты на Python, эти эквиваленты могут быть хуже. Например, Ren'Py может предсказывать инструкцию show и загружать изображения заранее, но он не может предсказать функцию renpy.show().

Python

Инструкция python принимает блок кода Python и выполняет его, когда управление доходит до этой инструкции. Простая инструкция Python может быть очень простой:

python:
    flag = True

При необходимости инструкции Python могут становиться более сложными:

python:
    player_health = max(player_health - damage, 0)
    if enemy_vampire:
        enemy_health = min(enemy_health + damage, enemy_max_health)

Существует два модификатора для инструкции Python, которые изменяют её поведение:

hide

Если указан модификаator hide, инструкция Python выполнит блок кода в анонимной области видимости. Эта область видимости будет потеряна по завершении блока Python.

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

in

Модификатор in принимает имя. Вместо выполнения в хранилище по умолчанию, код Python будет выполнен в хранилище с указанным именем.

Однострочная инструкция Python

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

Однострочная инструкция Python начинается с символа доллара $ и включает в себя всё остальное на этой строке. Вот несколько примеров однострочных инструкций Python:

# Установка флага.
$ flag = True

# Инициализация переменной.
$ romance_points = 0

# Увеличение значения переменной.
$ romance_points += 1

# Вызов функции, предоставляющей функциональность Ren'Py.
$ renpy.movie_cutscene("opening.ogv")

Однострочные инструкции Python всегда выполняются в хранилище по умолчанию.

Инструкция Init Python

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

init python:

    def auto_voice_function(ident):
        return "voice/" + ident + ".ogg"

    config.auto_voice = auto_voice_function

    if persistent.endings is None:
        persistent.endings = set()

init 1 python:

    # Плохая концовка всегда разблокирована.
    persistent.endings.add("bad_ending")

Между init и python можно указать числовой приоритет. Если приоритет не указан, используется 0. Инструкции init выполняются в порядке приоритета, от самого низкого до самого высокого. Инструкции init с одинаковым приоритетом выполняются в Unicode-порядке по пути к файлу, а затем сверху вниз внутри файла.

Чтобы избежать конфликтов с Ren'Py, разработчикам следует использовать приоритеты в диапазоне от -999 до 999. Приоритеты меньше 0 обычно используются для библиотек и настройки тем. Обычные инструкции init должны иметь приоритет 0 или выше.

Инструкции init python также могут принимать опции hide или in.

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

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

Классы, созданные в Ren'Py и не наследующие ничего или явно наследующие object, а также их подклассы, не поддерживают __slots__. Попытка их использования приведёт к некорректной работе отката (rollback) в старых версиях Ren'Py и вызовет ошибки в новых.

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

Инструкция Define

Инструкция define устанавливает значение для одной переменной во время инициализации. Переменная считается константой, и её не следует изменять после установки. Например:

define e = Character("Eileen")

эквивалентно (за исключением некоторых преимуществ, см. ниже):

init python:
    e = Character("Eileen")

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

define character.e = Character("Eileen")

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

define config.tag_layer["eileen"] = "master"

В дополнение к =, define может принимать ещё два оператора. Оператор += добавляет и обычно используется для конкатенации списков. Оператор |= обычно используется для объединения множеств. Например:

define config.keymap["dismiss"] += [ "K_KP_PLUS" ]
define endings |= { "best_ending" }

Инструкция define может принимать опциональный приоритет инициализации, указанный между define и именем переменной. Если приоритет не указан, используется 0. Например:

define -1 config.image_directories = [ "images", "dlc/images" ]

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

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

Инструкция Default

Инструкция default устанавливает значение для переменной, если эта переменная не определена при запуске игры или после загрузки новой игры. Например:

default points = 0

Когда переменная points не определена при запуске игры, эта инструкция примерно эквивалентна:

label start:
    $ points = 0

Фактически переменная устанавливается до запуска заставки (splashscreen) и главного меню. Когда переменная points не определена при загрузке игры, это эквивалентно:

label after_load:
    $ points = 0

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

default schedule.day = 0

Как и в случае с инструкцией define, Lint предлагает проверки и оптимизации, связанные с инструкцией default.

Примечание

Настоятельно рекомендуется использовать default для каждой переменной в вашей игре, которая может изменяться. Если вы используете init python или define для объявления переменной, то когда игрок проходит игру и изменяет эту переменную, а затем возвращается в главное меню и начинает новую игру, переменная не будет иметь значения, установленного в init python, и таким образом данные из прошлой игры "протекут" в новую. Если же вы создадите эти переменные в метке start, они будут отсутствовать при загрузке файла сохранения, который был создан до их добавления.

Имена в хранилище (Store)

Место, где Ren'Py по умолчанию хранит переменные Python, называется хранилищем (store). Важно убедиться, что имена, которые вы используете в хранилище, не конфликтуют.

Инструкция define присваивает значение переменной, даже когда она используется для определения персонажа. Это означает, что невозможно использовать одно и то же имя для персонажа и для флага.

Следующий ошибочный скрипт:

define e = Character("Eileen")

label start:

    $ e = 0

    e "Hello, world."

    $ e += 1
    e "You scored a point!"

не будет работать, потому что переменная e используется и как персонаж, и как флаг. Другие сущности, которые обычно помещаются в хранилище, — это переходы (transitions) и трансформации (transforms).

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

Другие именованные хранилища

Именованные хранилища (named stores) предоставляют способ организации функций и переменных Python в модули. Размещая код Python в именованных хранилищах, вы можете минимизировать вероятность конфликтов имён. Каждое хранилище соответствует модулю Python. Хранилище по умолчанию — это store, в то время как доступ к именованному хранилищу осуществляется как store.named.

Именованные хранилища можно создавать с помощью блоков python in (или их вариантов init python и python early), а также с помощью инструкций default, define или transform. Переменные можно импортировать по отдельности с помощью from store.named import variable, а само именованное хранилище можно импортировать с помощью from store import named.

Доступ к именованным хранилищам можно получить, указав опцию in в инструкциях python или init python (или python early), которые выполняют содержащийся в них код Python в указанном именованном хранилище.

Например:

init python in mystore:

    serial_number = 0

    def serial():

        global serial_number
        serial_number += 1
        return serial_number

default character_stats.chloe_substore.friends = {"Eileen",}

label start:
    $ serial = mystore.serial()

    if "Lucy" in character_stats.chloe_substore.friends:
        chloe "Lucy is my friend !"
    elif character_stats.chloe_substore.friends:
        chloe "I have friends, but Lucy is not one of them."

    python in character_stats.chloe_substore:
        friends.add("Jeremy")

Из блока python in к хранилищу по умолчанию («внешнему») можно получить доступ, используя либо renpy.store, либо import store.

Именованные хранилища участвуют в сохранении, загрузке и откате так же, как и хранилище по умолчанию. Специальные пространства имён, такие как persistent, config, renpy... не поддерживают и никогда не поддерживали создание в них дочерних хранилищ.

Константные хранилища

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

init python in mystore:
    _constant = True

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

Переменные в константном хранилище можно изменять на этапе инициализации. Только после завершения этапа инициализации (включая инструкции, такие как define, transform и т.д.) хранилище должно рассматриваться как константное.

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

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

Следующие хранилища по умолчанию объявлены константными:

_errorhandling
_gamepad
_renpysteam
_warper
audio
achievement
build
director
iap
layeredimage
updater

JSONDB

class JSONDB(filename, default=None)

JSONDB — это двухуровневая база данных, которая использует JSON для хранения своих данных. Она предназначена для использования разработчиками игр для хранения данных в базе, которая может контролироваться системой версий как часть игрового скрипта. Например, здесь можно хранить информацию, связанную с каждой репликой, что может изменять способ её отображения.

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

База данных должна содержать только данные, которые Python может сериализовать в JSON. К ним относятся списки, словари (с ключами-строками), строки, числа, True, False и None. См. документацию Python о совместимости, о том, как данные преобразуются между двумя форматами, и о различных связанных с этим подводных камнях.

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

Как и другие персистентные данные, JSONDB не участвуют в откате (rollback).

JSONDB следует создавать во время инициализации (в блоке init python или инструкции define), и он будет автоматически сохранен на диск при условии, что в словаре установлен хотя бы один ключ. Например:

define balloonData = JSONDB("balloon.json", default={ "enabled" : False })
Это создаёт JSONDB, который хранится в файле balloon.json и имеет

значение enabled по умолчанию, равное False. Значения второго уровня можно использовать как обычные словари:: screen say(who, what):

default bd = balloonData[renpy.get_translation_identifier()]

if bd["enabled"]:

use balloon_say(who, what)

else:

use adv_say(who, what)

if config.developer:
textbutton "Dialogue Balloon Mode":

action ToggleDict(bd, "enabled")

Конструктор JSONDB принимает следующие аргументы:

filename

Имя файла, в котором хранится база данных. Путь указывается относительно каталога игры. Рекомендуется, чтобы имя файла заканчивалось на ".json".

default

Если не None, должен быть словарем. Когда создается новый словарь второго уровня, этот объект поверхностно копируется и используется для инициализации нового словаря. Новый словарь будет сохранен как часть базы данных, только если в нём будет сохранён хотя бы один ключ.

Собственные и сторонние модули и пакеты Python

Ren'Py может импортировать модули и пакеты, написанные на чистом Python. Собственные модули и пакеты — те, что написаны для игры — можно размещать непосредственно в каталоге игры. Сторонние пакеты можно размещать в каталоге game/python-packages.

Например, для установки пакета python-dateutil можно перейти в корневой каталог игры и выполнить команду:

pip install --target game/python-packages python-dateutil

В любом случае, модуль или пакет можно импортировать из блока init python:

init python:
    import dateutil.parser

Не все пакеты Python совместимы с Ren'Py. Вы сами должны проверять устанавливаемые пакеты и убеждаться в их работоспособности.

Откат и Isinstance

Код Python, определённый в .rpy файлах, преобразуется для обеспечения работы отката. Код Python, импортированный из .py файлов, — нет. В результате объекты, созданные в Python, не будут работать с откатом и, вероятно, не должны изменяться после создания.

В рамках этого преобразования Ren'Py заменяет классы dict, list и set собственными версиями, поддерживающими откат. Эти классы являются подклассами встроенных классов, поэтому все методы будут работать. Однако эти классы не будут соответствовать экземплярам встроенных в Python классов dict, list и set.

Для решения этой проблемы Ren'Py предоставляет доступ к оригинальным классам Python.

class python_dict

Класс python_dict — это встроенный класс dict из Python.

class python_list

Класс python_list — это встроенный класс list из Python.

class python_set

Класс python_set — это встроенный класс set из Python.