Screens and screen language: вводная
Всегда приятно понимать, что именно делает та или иная строка кода, а не бездумно повторять их за учебником – поэтому попытаемся разобраться, как устроены экраны в 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

А можно – так:
Код
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 – те, которые инициализируют дисплейабл-объекты. Разговор о них обязательно затронет еще и работу со стилями, и какие-то практические нюансы, а это тема отдельного гайда, а этот и так вышел достаточно долгим, и стоит прерваться. Спасибо за внимание и, надеюсь, до следующего раза!

Автор материала: Ikuku
Материал от пользователя сайта.



Ren'Py 06 Октября 2017 1027 Ikuku ren'py, screen language, Screens, программирование, ТГ «Красавица Икуку», экраны 4.3/6

Комментарии (11):
1
1 stop_control   (07 Октября 2017 01:29)
53339
Спасибо!
Завтра внесу в свой поминальник)

0
6 Ikuku   (08 Октября 2017 21:47)
56408
Рады помочь! Продолжение про экранные объекты я все-таки собираюсь добить, но не знаю, как скоро - там реально много перевода, тестирования примеров и подготовки схем. Хочется сделать качественно.

0
2 mrWebster   (07 Октября 2017 20:28)
19499
Едва начав читать эту статью, я понял, от кого она. Мне не очень нравились Ваши статьи на тему сюжета, но я уважаю Ваши труды на тему различных возможностей Ren'Py. Спасибо за то, что делитесь знаниями.

0
3 Aspote   (07 Октября 2017 22:13)
45071
А чо такое? Поумничать не дают?

-1
4 mrWebster   (08 Октября 2017 13:11)
19499
Ваш комментарий не коррелирует ни с моим, ни со статьей. Подредактируйте хоть, чтобы другим людям его содержание было понятно.

0
5 Aspote   (08 Октября 2017 17:06)
45071
Коррелирует, коррелирует.

1
7 Ikuku   (08 Октября 2017 21:50)
56408
Меня сильно вдохновляет ТГ. Да и за пару лет работы с движком столько раз пришлось сутками долбаться с самыми простыми и дурацкими задачами... хочется, в общем, максимально облегчить работу всем тем, "кто придет после".

0
8 stop_control   (10 Октября 2017 23:41)
53339
В примерах прописан вывод текста или кнопок в Screens.
А как вывести картинку?

0
9 Ikuku   (11 Октября 2017 12:21)
56408
Способов несколько, на самом деле. Если не требуется никаких извращений, можно сделать это через оператор add, используя путь к картинке или дополнительно дисплейабл Frame() в качестве параметра. Можно картинку положить фоном в любой простой контейнер. Вот так:
Код
screen test:
   frame:
     background "my-image.jpg" # добавляет экранный объект frame (контейнер), фоном к нему кладет статичную картинку (картинка не будет растягиваться в зависимости от размеров frame)

   add "my-image.jpg"  # добавляет статичную картинку
   add Frame("my-image.jpg") # добавляет дисплейабл Frame, внутри которого будет лежать картинка. Картинка растягивается, занимая весь Frame, а Frame, соответственно, занимает все допустимое пространство

Способов с извращениями навскидку не подскажу, надо по ситуации смотреть, в зависимости от целей. Но вообще это как раз тема экранных объектов и стилизации.

0
10 stop_control   (11 Октября 2017 13:40)
53339
IKUKU - скоро распечатаю Ваше фото и поставлю в красный угол, как ангела хранителя))

Мысль про "add" с утра начала формироваться, а у Вас уже готовый код ))
Который проверил и все что мне нужно нашел.

0
11 Ikuku   (11 Октября 2017 17:51)
56408
Ну нет, красный угол мне еще надо заслужить) Может, после гайда по экранным объектам если выживу...

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]