Инструкции 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.