Всегда приятно понимать, что именно делает та или иная строка кода, а не бездумно повторять их за учебником – поэтому попытаемся разобраться, как устроены экраны в Ren'Py, и как можно с ними работать.
Зачем мне все это, я просто хочу добавить в меню кнопку, которая делает "бип"! Ну, сегодня вам хочется добавить только одну кнопку, завтра – поменять фон главного меню в зависимости от условий, послезавтра – добавить туда красивую анимированную вставку... Как говорится, дай человеку рыбу, и он будет сыт весь день, научи человека читать простой код создания экранов, и он будет с легкостью кастомизировать под себя любые игры.
Немного о слоях. Содержимое окна новеллы, написанной на Ren'Py, выводится по слоям. Стандартный набор слоев, который предоставляет Ren'Py:
master (самый нижний),
transient,
screens,
overlay (самый верхний). Каждый из них отвечает за определенный набор объектов, которые выводятся только на этом слое. Так, например, на слое
master по умолчанию выводится все, что было вызвано с помощью операторов
show/hide и
scene – то есть, все спрайты, картинки, фоны и т.д.
Порядок слоев определяется внутри переменной
config, может быть изменен и задает то, какие элементы будут расположены поверх каких. Так, например, когда вызывается экран внутриигрового меню, ваши фоны, спрайты и диалоги никуда не пропадают. Слой
screens, на котором лежит меню, рисуется поверх слоя
master, на котором лежат спрайты и прочая.
Вы можете создавать свои собственные слои и вставлять их в любое место в списке стандартных. Можно переопределить список слоев целиком, работая с атрибутом
layers переменной
config:
Код
init python:
config.layers = [ 'master', 'transient', 'my_new_layer_below_screens', 'screens', 'overlay' ]
...но если вам надо добавить только один слой, более простой и безопасный способ – воспользоваться функцией renpy.add_layer:
Код
init python:
renpy.add_layer("my_new_layer_below_screens", below = "screens")
Полный список атрибутов можно почитать тут:
https://www.renpy.org/doc/html/other.html#renpy.add_layer.
Что такое экраны? Зачем они нужны? Как их пощупать? Объекты, которые мы видим на экране любой renpy-игры, называются общим термином displayables ("то-что-можно-показывать"). Текст, статичная картинка, фреймы, кнопки – все это дисплейабл-объекты разных типов. Их можно бросать на слой в голом виде:
Код
show somesprite
show text "Привет, мир!"
...а можно предварительно объединить экраном, если их нужно показывать и прятать сразу группой.
tl;dr: экраны – это контейнеры для объектов.
Где можно найти экран в Ren'Py? Везде. Если вы замечаете объединенные в группу объекты, скорее всего перед вами объект screen. Диалоговая коробка с текстом и портретом? Экран
say. Главное меню? Экран
main_menu. Маленькая менюшка перемотки и быстрого сохранения? Экран
quick_menu внутри экрана
say!
Если порядок отображения для экрана не задан в стилях, то выводятся они порядке очереди вызова – то, что вызвано последним, кладется на самый верх стопки.
Как вызывать/прятать экраны? Для файла скрипта проще всего будет использовать оператор
show с ключевым словом
screen:
Код
label start:
# показывает и прячет экран my_new_screen
show screen my_new_screen
hide screen my_new_screen
# показывает и прячет экран another_one с анимацией dissolve
show screen another_one
with dissolve
hide screen another_one
with dissolve
# показывает экран parametrized на слое my_layer вместо слоя screens, передавая в вызов параметры param_one и param_two
show screen parametrized(_layer = "my_layer", param_one = "test", param_two = False)
Из примера выше видно, что экраны можно вызывать с параметрами (которые потом используются внутри самого экрана, но об этом позже). Кроме того, есть набор т.н. ключевых слов – параметров, предусмотренных самим Ren'Py. Они начинаются с символа "_", и параметр
_layer, который принимает имя слоя, на котором нужно отображать экран – один из таких.
Полный список можно посмотреть по ссылке:
https://www.renpy.org/doc/html/screen_python.html#renpy.show_screen Другой способ вызвать/прятать экран – с помощью питоновской функции
renpy_show/renpy_hide.
Код
# показывает и прячет экран my_screen
renpy.show_screen("my_screen")
renpy.hide_screen("my_screen")
# показывает экран another_one с анимацией dissolve
renpy.show_screen("another_one")
renpy.with_statement(dissolve)
# показывает экран parametrized на слое my_layer вместо слоя screens, передавая в вызов параметры param_one и param_two
renpy.show_screen("parametrized", _layer = "my_layer", param_one = "test", param_two = False)
Экранируя ее значком $, можно пользоваться ей и внутри скрипта, но вообще она лучше подходит для кусков кода на питоне. Например, если вы решите написать какую-нибудь симпатичную новую функцию для своей новеллы, и внутри нее нужно будет вызвать или спрятать какой-нибудь экран.
Третий способ – вызов/скрытие экрана через экшены
Show/Hide. Использовать его следует строго в комбинации с кнопками любых видов (imagebutton, textbutton, button). Экшены выполняются по нажатию, наведению фокуса на кнопку и потере фокуса кнопкой.
Код
textbutton "Кнопка":
hovered Show("hover_screen")
action Show("my_screen", transition = dissolve, my_param = "test")
unhovered Hide("hover_screen", transition = dissolve)
В примере выше мы показываем экран my_screen по нажатию кнопки "Кнопка", используя анимацию
dissolve и с параметром my_param. В отличие от предыдущих случаев для указания анимации нам не нужна дополнительная команда – за это отвечает встроенный в экшен
Show параметр
transition. При наведении курсора на кнопку мы показываем экран hover_screen, при отведении курсора – прячем его с анимацией
dissolve.
Оператор call. Есть еще один способ напрямую вызывать экран. Оператор
call – это два в одном, он отвечает и за вызов, и за скрытие экрана. Его поведение отличается от поведения пары
show-hide. Экран, вызванный с помощью
show (если он не модальный), не будет мешать прочим действиям игрока, и будет висеть в окне до тех пор, пока не встретится команда
hide. Экран, вызванный с помощью
call, приостановит смену слайдов, будет ждать взаимодействия с пользователем, и только тогда скроется. За взаимодействие считается, например, возвращение результата с помощью экшена
Return(), или прыжок на метку с помощью
Jump().
Код
screen notification_screen:
frame:
vbox:
textbutton "Кнопка раз" action Jump("sublabel")
textbutton "Кнопка два" action Return()
label start:
call screen notification_screen
"Если ты видишь этот текст, то ты нажал кнопку два, чтобы скрыть окно."
label sublabel:
"Если ты видишь этот текст, то ты нажал кнопку раз, чтобы прыгнуть на метку. Как побочный эффект, скрылось окно."
"Конец."
От поведения модального окна, вызванного
show, поведение окна, вызванного с помощью
call, отличается тем же – модальное окно исчезнет только после прямой команды
hide, тогда как called-окну достаточно конца взаимодействия.
У оператора
call тоже есть python-аналог:
renpy.call_screen() Несмотря на то, что пишется в документации,
with <transition> с
call не работает (на момент написания тестировалось в версии 6.99.9).
Создание экранов. Объявляют новый экран с помощью оператора
screen.
Код
screen my_screen:
# внутренности экрана, стили и прочее.
Лирическое отступление: то, каким будет любой созданный объект, задается с помощью параметров и свойств.
Параметры – это то, без чего объект не может быть объектом (стул будет стулом, если у него есть ножки, сиденье и спинка). Свойства – это то, что отличает объект от других (стул может быть деревянный или пластиковый, сиденье – мягким или жестким...)
Отступление от лирического отступления: "Объект не может существовать без параметров? Но как же, ведь есть
необязательные параметры!" Да, пользователю
не обязательно их задавать (потому что у них есть некоторое значение по умолчанию), но для существования объекта они все еще
обязательны.
Чтобы еще больше вас запутать контекстами: пользователь может приказать screen-объекту принимать свои собственные параметры!
Ладно, вернемся на рельсы, хватит лирических отступлений.
Для экрана my_screen, объявленного выше, my_screen (имя) – это значение параметра. Оператор
screen принимает единственный параметр: имя экрана. Свойств у оператора
screen пять:
modal: если это свойство установлено в True, экран будет модальным. Модальный экран блокирует действия пользователя с элементами вне этого экрана (за исключением стандартных хоткеев). Как пример – встроенный экран да/нет при сохранении, загрузке и выходе из игры у Ren'Py является модальным.
tag: задает экрану тег. Несколько разных экранов могут делить один и тот же тег на всех, и тогда при вызове любого из них по имени, закрываются все остальные, имеющие тот же тег. На примере внутриигрового меню: экраны настроек, сохранения, загрузки – это три разных экрана, имеющие один и тот же тег. Если мы вызовем экран настроек, потом из него вызовем экран сохранения, экран настроек спрячется автоматически.
zorder: отвечает за порядок расположения экранов на слое. Принимает числовые значения, чем больше число, тем выше положение экрана относительно других. По умолчанию у всех экранов zorder – 0.
variant: если кратко, то эта переменная указывает на тип устройства, к которому будет привязан этот экран (пк, планшеты, и т.д.) Подробнее можно почитать в мануале. https://www.renpy.org/doc/html/screens.html#screen-variants
style_prefix: задает префикс стилям всех дочерних объектов экрана, если у них не задан стиль на более глубоком уровне.
Работу с префиксами и суффиксами и стилизацию объектов в деталях разберем позже, это тема отдельного гайда.
Стоит помнить, что совсем не обязательно перечислять сразу все свойства при объявлении объекта. Можно ни одного, можно часть.
Управляющие операторы. Язык работы с экранами включает в себя набор операторов, которые помогают управлять содержимым экрана.
Условный оператор if работает абсолютно так же, как
if в прочих блоках. Если условие выполняется, заключенный в него блок тоже выполняется. Если нет – блок будет проигнорирован.
Код
screen testscreen_for_if_statement:
if some_variable == 1:
text "Если значение переменной some_variable равно 1, вы увидите этот текст."
elif some_variable == 2:
text "Если предыдущее условие ложно, но значение переменной some_variable равно 2, вы увидите этот текст."
else:
text "Если оба предыдущих утверждения ложны, вы увидите этот текст."
Условный оператор showif похож на оператор
if: если условие соблюдено, то пользователь видит заключенный в это условие блок. Однако работает
showif несколько иначе.
Во-первых, блок в ложном условии оператора
showif полностью отработает и отрендерится, но будет скрыт, тогда как блок в ложном условии оператора
if будет проигнорирован компилятором.
Вторая большая польза – и большое отличие
showif в том, что как только условие выполнится, для блоков внутри этого условия сработает событие
show (и, соответственно, когда условие станет ложным, для них сработает событие
hide). Это удобно использовать в связке с ATL (трансформациями), которые умеют запускать анимацию по каждому триггеру событий
show и
hide.
Оператор
showif триггерит три вида событий:
appear: срабатывает, если при первом вызове экрана условие истинно, сразу отображая дочерний блок.
show: срабатывает при каждом обращении условия из ложного в истинное
hide: срабатывает при каждом обращении условия из истинного в ложное
Оператор
if при изменении истинности условий события не триггерит.
Код
transform cd_transform:
xalign 0.5 yalign 0.5 alpha 0.0
on appear:
alpha 1.0
on show:
zoom .75
linear .25 zoom 1.0 alpha 1.0
on hide:
linear .25 zoom 1.25 alpha 0.0
screen countdown():
default n = 3
vbox:
textbutton "3" action SetScreenVariable("n", 3)
textbutton "2" action SetScreenVariable("n", 2)
textbutton "1" action SetScreenVariable("n", 1)
textbutton "0" action SetScreenVariable("n", 0)
showif n == 3:
text "Three" size 100 at cd_transform
elif n == 2:
text "Two" size 100 at cd_transform
elif n == 1:
text "One" size 100 at cd_transform
else:
text "Liftoff!" size 100 at cd_transform
label start:
call screen countdown
Оператор for – это итератор. С его помощью можно заставить определенный блок кода отработать несколько раз или последовательно пройти по массиву или словарю данных. Захотели мы, например, анимировать падающий снег, и для этого нам нужно вывести на экран сотню изображений снежинок. Оператор
for в этом поможет.
Код
screen snow:
frame:
for i in xrange(100):
add "snowflake.jpg" at snowflake_animation
Кроме повторения блоков, цикл
for можно использовать для прохода по массивам и/или словарям с данными. Скажем, для создания экрана инвентаря – вывода списка того, что завалялось у героя в карманах:
Код
inv_array = [ "корзина", "картина", "картонка" ]
inv_dict = { "001": "корзина", "002": "картина", "003": "картонка" }
screen inventory_screen:
frame:
has vbox
text "Массив:"
# в переменную i будет помещен элемент массива, соответствующий текущему шагу, начиная с самого первого элемента массива, заканчивая последним.
for i in inv_array:
text "[i]"
text "Словарь:"
# в переменную id будет помещаться ключ текущего шага, в item – соответствующий этому ключу элемент
for id, item in inv_dict.items():
text "Объект [id]: [item]"
В примере выше inv_array – массив, inv_dict – словарь, их структура и работа с ними отличается. Словарь позволяет сделать привязку элементов к ключу. Перед тем, как запускать цикл по словарю, нужно превратить его в итерабельный объект, для этого используется метод
.items(). Он возвращает массив пар (ключ, элемент), т.е., для нашего примера – массив: [ ("001", "корзина"), ("002", "картина")... ]. На каждом шаге цикл будет получать кортеж из двух переменных (tuple), соответственно, для хранения тоже нужно две переменных: id, item.
В отличие от массивов, словари – неупорядоченная структура. Элементы в словаре не будут держаться в том порядке, в каком вы их туда внесли. Если для вас это важно, придется дополнительно использовать сортировку.
Оператор default задает значение переменной по умолчанию. Это удобно, если внутри экрана мы используем некоторую переменную, которую можем не передать в качестве аргумента, или не объявить заранее. Если не использовать оператор
default, и попытаться вызвать экран с несуществующей переменной, это вызовет ошибку.
Код
screen my_screen:
default some_var = 42
frame:
text "Значение переменной: [some_var]. Переменная будет всегда определена, даже если ее не передали в качестве параметра и не объявили внутри экрана."
Оператор on позволяет отслеживать срабатывание событий и при срабатывании выполнять заданные вами действия. В качестве параметра
on принимает имя отслеживаемого события, одного из четырех допустимых:
show: срабатывает при каждом вызове экрана с помощью
show или
call, если этот или все экраны с таким же тегом до этого были скрыты.
hide: срабатывает при каждом скрытии экрана с помощью
hide, если до этого был вызван этот или любой экран с таким же тегом.
replace: срабатывает если текущий экран подменяет экран с таким же тегом.
replaced: срабатывает, если текущий экран подменяется новым экраном с таким же тегом.
Код
screen test_screen:
default text = ""
frame:
text "[text]"
on "show" action SetVariable("text", "Сработало событие show")
on "replace" action SetVariable("text", "Сработало событие replace")
Оператор use позволяет включать один экран внутрь другого. В качестве параметра принимает имя включаемого экрана. Включаемый экран унаследует все параметры, переданные родительскому.
Один и тот же экран можно включить в другой в нескольких экземплярах.
Код
screen main_scr:
frame:
has vbox
for i in xrange(1,4):
use child_scr(i)
screen child_scr(i):
text "Экран [i]"
label start:
show screen main_scr
pause
У оператора
use есть единственное свойство,
id. Оно работает в том случае, если два экрана с одинаковым тегом включают в себя один и тот же дочерний экран. Если родительские экраны сменяют друг друга, то на дочернем это не сказывается, и он сохраняется в том состоянии, в котором был до подмены.
В примере ниже у дочернего экрана common есть анимированный элемент. Если подменить один родительский экран (s1) на другой (s2) до завершения анимации, анимация не будет прервана.
Код
transform trf:
xpos 500
linear 5.0 xpos 0
screen common:
text "Test" at trf
screen s1:
tag s
use common id "common"
text "s1" ypos 100
screen s2:
tag s
use common id "common"
text "s2" ypos 100
label start:
show screen s1
pause
show screen s2
pause
return
Связка use и transclude: сквозное включение. Оператор
use можно использовать так:
А можно – так:
Код
use my_subscreen:
text "А я – томат!"
Т.е. оператор
use может принимать в себя дочерний блок операторов и дисплейабл-объектов. Если вывести родительский экран, то этот дочерний блок виден не будет, если только на экране my_subscreen не будет стоять оператора-метки
transclude.
Код
screen my_subscreen:
vbox:
text "Этот текст всегда был здесь"
transclude
Если для вложенного экрана был указан дочерний блок, он займет место оператора
transclude.
Оператор python принимает в качестве дочернего блока питоновский код, и позволяет включать его в экран, не экранируя каждую его строчку.
Сравните:
Код
screen python_screen:
python:
name = "Eileen"
value = 42
test = "A string"
text "[name] [value] [test]"
и:
Код
screen python_screen:
$ name = "Eileen"
$ value = 42
$ test = "A string"
text "[name] [value] [text]"
Код, вложенный в оператор
python, выполняется в контексте экрана. Кроме того, важно помнить, что такой код выполняется каждый раз, когда отрабатывает код экрана – а код экрана может отрабатывать не только при его явном вызове через
show. Поэтому следите за возможными побочными эффектами этого срабатывания. Пока вы не смотрите, ваш вложенный код уже десятый раз присвоил переменной значение!
Кроме того, на практике любой питоновский код, включенный в экран, может отрабатывать в непредсказуемые моменты, даже если спрятан в условный оператор с ложным условием. Даже когда вы сами твердо уверены, что ну вот никак он не может отработать в этот момент. Может. Будьте осторожны.
Экранные объекты. Самый большой пласт операторов screen language – те, которые инициализируют дисплейабл-объекты. Разговор о них обязательно затронет еще и работу со стилями, и какие-то практические нюансы, а это тема отдельного гайда, а этот и так вышел достаточно долгим, и стоит прерваться. Спасибо за внимание и, надеюсь, до следующего раза!
Комментарии к записи: 15
Завтра внесу в свой поминальник)
А как вывести картинку?
frame:
background "my-image.jpg" # добавляет экранный объект frame (контейнер), фоном к нему кладет статичную картинку (картинка не будет растягиваться в зависимости от размеров frame)
add "my-image.jpg" # добавляет статичную картинку
add Frame("my-image.jpg") # добавляет дисплейабл Frame, внутри которого будет лежать картинка. Картинка растягивается, занимая весь Frame, а Frame, соответственно, занимает все допустимое пространство
Способов с извращениями навскидку не подскажу, надо по ситуации смотреть, в зависимости от целей. Но вообще это как раз тема экранных объектов и стилизации.
Мысль про "add" с утра начала формироваться, а у Вас уже готовый код ))
Который проверил и все что мне нужно нашел.
если выживу...Я давно уже пользуюсь собственным маленьким скриптиком создающим динамических игровых меню. Он как раз использует этот аспект.
Что даёт создание меню подобным образом?
Ну во-первых, на мой взгляд, облегчается создание кнопок. Они могут быть любого стиля. Содержать или не содержать фоновые картинки и т д.
И эти меню как раз задаются вызовом экранов, о которых говорится в этой статье.
Кроме того намного облегчаются многие игровые функции. Трудно будет запутаться, поскольку если функция нужна всегда вставляется один и тот же код.
Вот к примеру.
Во многих играх есть функция запоминания выбора. То есть если игрок проходит игру по второму разу, кнопки, которые уже были выбраны отображаются другим цветом. Мой скрипт это делает автоматически.
Так же очень легко производиться активации или блокировка кнопки "skip"
Возможно это уже давно существует но я нигде и ни у кого не видел подобной реализации. А та, что предлагается по умолчанию самим движком... впрочем всё познаётся в сравнении. Если кому-то нужно, я мог бы подготовить пример с подробным описанием...
И да, я считаю, вам надо запилить гайд со своим скриптом. Штука любопытная, лишним не будет, чем больше людей делится своим опытом, тем лучше для сообщества. (Меню с картинками внутри так вообще зачетнейшая вещь в нужном месте!)
Попросту пишем массив
Ну скажем так
$ ch = ["c1","c2","c3"]
Это надписи на кнопках
$ lb = ["l1","l2" ,"l3"]
Это метки куда надо перейти
потом задаём количество пунктов меню (хотя это можно вычислить)
И вызываем экран.
А там меню строится через цикл. Вот собственно и всё. Но это в самом простейшем случае, если не нужны всякие красивости и нет всевозможных ключей.
В другом варианте задаётся глобальный массив для всех пунктов сценария и меню строится по номеру сценария.
Вообще в полном варианте реализуется ещё уловка исключающая команду Call поскольку она не поддерживает трансформации. Ну а нужно чтобы меню плавно появлялось и так же растворялось. Но это не меню в полном смысле а попросту набор кнопок. И если использовать show то нужно принять меры чтобы реакция была только на нажатие кнопки. Вот и используется довольно замысловатый на первый взгляд трюк...
Если красивости в виде трансформаций не нужны, тогда Call
Есть ещё куча всяких мелочей. Но об этом долго рассказывать.
Что касается картинок. Я имел в виду фоны кнопок. Хотя дополнительные картинки на самих кнопках тоже возможны. Это элементарно и я вообще не понимаю почему это считают сложным.
Запилить гайд можно конечно, только вот куда? Лепить свой блог у меня нет ни времени не желания.
Ну, например, я хочу сделать, чтобы у персонажа менялась одежда в определенном меню и он потом в течение игры ходил в той одежде, которую ему выбрал игрок, пока тот ее не поменяет на другую.
И чтобы при каждом появлении и перемещении персонажа не писать огромные манускрипты по типу:
Кодif dress_1 = 1 and dress_2 = 0 and dress_3 = 0:
show char_dress_1:
xalign 2.0 yalign 1.0
linear 0.5 xalign 1.0
if dress_2 = 1 and dress_1 = 0 and dress_3 = 0:
show char_dress_2:
xalign 2.0 yalign 1.0
linear 0.5 xalign 1.0
if dress_3 = 1 and dress_2 = 0 and dress_1 = 0:
show char_dress_3:
xalign 2.0 yalign 1.0
linear 0.5 xalign 1.0
и т.д.а просто один раз написать экран screen со всеми переменными и вызывать его при появлении персонажа, но так же со всеми анимациями как и при вызове обычного изображения. Типа:
Кодshow screen char:
xalign 2.0 yalign 1.0
linear 0.5 xalign 1.0