ScreenLang - TrueCat17/Ren-Engine GitHub Wiki
ScreenLang - это язык описания графического интерфейса.
Интерфейс строится из набора компонентов, которые можно разделить на 2 группы: простые компоненты и контейнеры.
Указание любого свойства любого компонента можно сделать так:
comp [main_param] param1 value1 param2 value2
Так:
comp [main_param] param1 value1:
param2 value2
Или так:
comp [main_param]:
param1 value1
param2 value2
А если поменять строки с указанием параметров местами, то абсолютно ничего не поменяется, кроме порядка вычисления параметров.
Ну и кол-во указываемых параметров, разумеется, не ограничено.
$
, python
- используются обычно: также, как и в сценариях.
key KEY action action_func
Где
-
KEY
- строка, обозначающая клавишу на клавиатуре:- Например: проблел -
"SPACE"
, Enter -"RETURN"
, стрелка влево -"LEFT"
, F1 -"F1"
, W -"w"
, - Если присутствует префикс
"K_"
, то он удаляется автоматически, т. е."K_LEFT"
тоже будет работать (обратная совместимость).
- Например: проблел -
-
action_func
- функция, которая будет выполнена при нажатии на клавишу (см. последний раздел).
Также есть настройка параметров first_delay
(игнорирование нажатий после первого в течение некоторого времени) и
delay
(после второго и далее).
Это время указывается в секундах, по умолчанию 0.333 и 0.010 соответственно.
Пример:
Где-то в init-блоке:
python:
def my_func(a = 123):
print(a)
Теперь попробуем вызвать эту функцию при нажатии на Enter:
key "RETURN" action my_func
И действительно, при нажатии на Enter будет напечатано 123
.
Но часто бывает, что нам желательно бы передать этой функции параметры, это можно сделать так:
key "RETURN" action my_func(5)
Тут будет вызван результат выполнения функции my_func
, а т. к. мы ничего из неё не возвращаем или,
говоря иначе, возвращаем None
, этот None
будет проигнорирован.
Список клавиш можно посмотреть здесь (столбец "Key Name").
if
, elif
, else
, for
, while
(внутри циклов попрежнему могут использоваться break
и continue
, else
всё так же доступна после циклов).
Это может показаться странным, но всё только что перечисленное - контейнеры, хоть и несколько необычные.
Они добавляют потомков не в себя, а в своего родителя (а родитель может добавить его в своего родителя, а не в себя, если тоже принадлежит к этой группе фейк-контейнеров).
При создании такого контейнера создаются все нужные для данного кадра потомки.
Если в следующем кадре их количество увеличивается (например, в цикле for i in range(my_var)
значение my_var
больше, чем в предыдущем кадре, или условие стало истинным, а раньше всегда было ложным), то и недостающие потомки создаются.
Если уменьшается - ненужные в данном кадре потомки отключаются (не происходит обновления свойств и отрисовки) и включатся только когда это понадобится.
Т. е. всё работает так, как и ожидается интуитивно.
Но помнить о том, что это всё-таки не просто условия/циклы, а всё же контейнеры, может быть полезно. Например, если вы собираетесь делать 10 условий внутри цикла с тысячей итераций, то лучше всё же этого не делать и попытаться это дело оптимизировать. Допустим, у нас есть такой сильно упрощённый (и от этого даже несколько нелепый) пример:
for i in range(1000):
if i < 100:
image 'images/some_dir/000+.png'
elif i < 200:
image 'images/some_dir/100+.png'
# ...
else:
image 'images/some_dir/900+.png'
Если задуматься, то тут создаётся 1000 * 10 = 10000 условий и 1000 изображений. Ну, 1000 изображений, допустим, нам реально необходимы (например, у нас игра с кучей зомби, или мы хотим устроить красивый снегопад). А вот избавиться от кучи условий вполне возможно (тем более, что их значительно больше, а потому в первую очередь избавляться нужно от них).
Конкретно в этом случае можно написать всё более кратко, но вряд ли часто будет нужно именно это, поэтому я опишу то, что будет работать для более общего случая:
python:
images = []
for i in range(1000):
if i < 100:
path = 'images/some_dir/000+.png'
elif i < 200:
path = 'images/some_dir/100+.png'
# ...
else:
path = 'images/some_dir/900+.png'
images.append(path)
for path in images:
image path
Как видите, мы перенесли все условия в блок питон-кода и создали в нём список путей к изображениям, а в цикле без всяких условий создали все нужные изображения. Это работает гораздо быстрее, т. к. в данном случае условия и циклы - это реально условия и циклы, которые к тому же находятся в одном блоке.
Это уже - оптимизация, влияющая на читаемость кода.
Она может её как улучшить, так и ухудшить, поэтому пытайтесь сохранить в первую очередь её (читаемость), и если она страдает слишком сильно, и всё слишком запутывается, а вам нужно отрисовать лишь десяток объектов - не используйте оптимизацию.
Ещё раз повторю: если у вас производительность не страдает - ставьте лёгкость понимания кода на первое место.
Кстати, это относится ко всему коду, а не только к ScreenLang.
Объект текста тоже является контейнером и может содержать потомков, если это зачем-то понадобилось.
text TEXT
Где TEXT
- отображаемое значение (ожидается тип str
).
У текста можно настроить ещё чисто-текстовые свойства:
-
font
- имя файла шрифта, который располагается вresources/fonts/
(без расширения, примеры:"Calibri"
,"Arial"
), -
color
- цвет (про формат см. ниже), -
outlinecolor
- цвет обводки (None
- без обводки), -
text_size
(не путать сsize
) - размер шрифта, -
text_align
- выравнивание строк текста по горизонтали, допустимы нецелочисленные значения (float
, обычно от 0 до 1), а также строки"left"
,"center"
и"right"
(означают0.0
,0.5
и1.0
соответственно), -
text_valign
- выравнивание строк по вертикали (если точнее, расположение строк в текстовом поле), точно так же допустимыfloat
и строки"top"
,"center"
и"bottom"
для всё тех же значений0.0
,0.5
и1.0
соответственно.
Группа свойств для настройки начертания текста (True
- включить, False
- выключить, по умолчанию False
):
-
bold
- полужирность текста, -
italic
- курсив, -
underline
- подчёркивание, -
strikethrough
- зачёркивание.
Форматы цвета и их примеры (R
- красный канал, G
- зелёный, B
- синий):
-
0xRRGGBB
- число в 16-ричной системе счисления (с префиксом0x
):0x0080FF
,0xFF00FF
, -
'#RRGGBB'
- строка из6
символов с префиксом#
(или с0x
, или вообще без префикса):'#0080FF'
,'FF00FF'
, -
'#RGB'
- строка из3
символов, сокращённый вариант предыдущего формата:'#08F'
,'F0F'
, -
[R, G, B]
- список или кортёж из3
чисел от0
до255
включительно:[0, 128, 255]
,(255, 0, 255)
.
Кнопка является контейнером, т. к. иногда удобно добавлять в неё картинку и/или текст.
Может быть с текстом или без:
textbutton TEXT action action_func # с текстом
button action action_func # без текста
Где
-
TEXT
- отображаемый текст, -
action_func
- аналогичноkey
.
Свойства для отображения:
-
ground
- фон кнопки в обычном состоянии, -
hover
- фон кнопки, когда на неё наведена мышь.
Если мышь наведена на полностью прозрачный пиксель изображения, то она не считается наведённой. Для избегания такого поведения используйте около-нулевую непрозрачность вместо нулевой. Для пользователя такое не должно быть заметно.
Также для текстовой кнопки доступны все текстовые настройки. И ещё: вы вряд ли сможете это хоть как-то использовать, но обычная кнопка - это текстовая кнопка с пустым текстом.
Дополнительные свойства кнопок:
-
action
- действие, выполняемое при нажатии Левой Кнопки Мыши, -
alternate
- действие, выполняемое при нажатии Правой Кнопки Мыши, -
hovered
- действие, выполняемое при наведении на кнопку, -
unhovered
- действие, выполняемое при убирании курсора с кнопки, -
activate_sound
- звук, проигрываемый на канале"button_click"
при клике Левой Кнопки Мыши, -
hover_sound
- звук, проигрываемый на канале"button_hover"
при наведении на кнопку, -
selected
-True
/False
, установить ли кнопке "наведённое", "выделенное" состояние даже без наведения мыши, -
mouse
-True
/False
, определяет,- нужно ли использовать на кнопке "наведённый" курсор мыши
- и считать нажатие
<Space>
и<Enter>
как клик ЛКМ - (при
False
не использует и не считает).
Также кнопки имеют дополнительные свойства-аналоги
для всех текстовых свойств (начиная с font
и заканчивая strikethrough
),
но с префиксом hover_
- для наведённого состояния (например, hover_color
для color
).
По умолчанию они имеют значение None
, т. е. вместо них используются обычные свойства, без этого префикса.
image IMAGE
Где IMAGE
- путь к изображению (применимо и то, что описывалось в главе с im-функциями).
Тут следует упомянуть вот о чём.
Допустим, у вас есть что-то типа такого (упрощённо):
image im.scale('images/some_dir/some_name.png', 200, 100)
Так вот, не стоит так делать, лучше будет (не всегда, но в большинстве случаев) переписать это следующим образом:
image 'images/some_dir/some_name.png':
size (200, 100)
О свойстве size
, как и о других общих свойствах, будет рассказано
далее.
Причина, почему это лучше - не будет создана ещё одна текстура, которая будет отличаться от оригинальной лишь размером.
Это - более оптимальное использование
кэша.
Во время отрисовки изображение будет растянуто так, как указано (если не указано, то размер будет взят из стиля).
Растяжение с помощью size
(и подобными) почти совсем-совсем не сказывается на производительности.
Можно лишь условно назвать imagemap
контейнером, потому что единственный объект, который он может содержать - hotspot
.
Он же, в свою очередь, может быть добавлен только в imagemap
(а содержать вообще ничего не может).
Итак, imagemap
виден как изображение, которое указано в его свойстве ground
.
При наведении курсора мыши на область (на непрозрачный пиксель), указанную в одном из hotspot
'ов,
поверх этой области рисуется изображение, указанное в свойстве hover
.
Свойства ground
и hover
очень похожи на соответствующие у кнопок.
Элемент hotspot
имеет свою область в imagemap
(x
, y
, width
, height
) и свойство action
(а так же остальные кнопочные свойства).
Пример использования:
imagemap:
ground 'images/menu/ground.png'
hover 'images/menu/hover.png'
hotspot (50, 50, 200, 25) action Function(start_mod, 'main_game')
hotspot (50, 75, 200, 25) action exit_from_game
Используются, в основном, для создания меню, в котором варианты действий нельзя (или сложно) представить в виде обычных кнопок.
Это контейнеры, которые автоматически располагают элементы внутри себя (vbox
- по вертикали, в столбец; hbox
- по горизонтали, в линию).
Имеют свойство spacing
, которое определяет расстояние между объектами (по умолчанию - 0
).
Пример:
vbox:
spacing 15
textbutton "Играть" action Function(start_mod, 'main_game')
textbutton "Настройки" action ShowMenu('settings')
textbutton "Выход" action exit_from_game
Ничто, пустота.
Имеет размеры: в отличие от Ren'Py, не width
и height
, а xsize
и ysize
(или сразу size
, об этом позже).
Имена этих свойств изменены для большей логичности и стандартизации.
Может использоваться и в качестве контейнера.
Например, так можно сделать область в четверть экрана и расставить по её углам картинки:
null:
size (0.5, 0.5)
image img align (0.0, 0.0)
image img align (0.0, 1.0)
image img align (1.0, 0.0)
image img align (1.0, 1.0)
Позволяет использовать указанный скрин в качестве элемента интерфейса.
Например, use my_screen
- включить скрин my_screen в текущий скрин (без кавычек!).
Или с передачей параметров: use my_screen(1, some_var + 2, 'some string')
.
Поддерживаются даже именованные параметры: use my_screen(1, 2 + 3, prop = 10, other_prop = None)
.
Важно!
Рекурсивное включение скринов друг в друга недопустимо.
Т. е. нельзя включить скрин A
в скрин B
, если скрин B
уже включает в себя скрин A
(принципиально, без исключений).
Собственно, самый верхний узел во всей этой иерархии. Имеет свойства:
-
has
- позволяет превратить скрин в соответствующий авто-контейнер (has vbox
илиhas hbox
), -
zorder
- отвечает за сортировку: меньшеzorder
- раньше отрисовывается, больше других скринов перекрывают текущий, -
modal
- если есть хоть 1 скрин сmodal True
, то все скрины сFalse
не реагируют на мышь и клавиатуру, -
save
- нужно ли сохранять скрин, чтобы потом восстановить его после загрузки, по умолчаниюTrue
- сохранять,False
- отключить сохранение (отключено, например, для скринов паузы и сохранения).
Кроме этих имеются и все остальные общие свойства (alpha
, rotate
, pos
, anchor
и прочие, благодаря чему можно,
например, повернуть или сместить сразу весь скрин всего одной строчкой).
В отличие от Ren'Py, здесь весь python-код в скринах выполняется в глобальном пространстве имён (т. е. так, как это и ожидается по умолчанию).
С одной стороны, это вызывает некоторые неудобства, например, такой код может работать не так, как от него ожидается:
label some_label:
$ i = 100 + 23
# В этом месте может начаться отрисовка скринов,
# если какой-нибудь из них использует переменную i,
# то её значение, разумеется, уже не будет равно 123.
# Поэтому используйте более осмысленные имена переменных.
np "100 + 23 = " + str(i)
С другой стороны это всё же лучше, чем чуть ли не в каждом блоке python-кода писать кучу global
'ов.
Ну а раз скрины не имеют локальных пространств имён, то и приём параметров осуществляется не так, как в Ren'Py.
Например, если параметры передаются так: use some_screen('text', color = 0xFFFFFF)
, то чтение может быть таким:
# определение имён параметров (и их значений по умолчанию) как в python функции
screen some_screen(text_str, text_color, x = 100):
text screen.text_str: # чтение - из объекта <screen> - либо через точку
color screen['text_color'] # либо через строку в квадратных скобках
$ prop_name = 'x'
xpos screen[prop_name] # вместо прямого указания строки можно использовать любую строковую переменную
Ну и простой пример использования скрина:
screen my_screen:
image "images/my_image.png":
size (400, 300)
xalign time.time() % 1 # дробная часть (остаток от деления на 1) от кол-ва секунд от начала 1970 года
yalign 0.5
Показать его можно так:
show screen my_screen
- да, всё та же команда show
, но со словом screen
.
Или так:
$ show_screen('my_screen')
Или вот так (снова обратная совместимость, renpy.show_screen
- это просто ссылка на show_screen
):
$ renpy.show_screen('my_screen')
Передачу параметров можно сделать только в python
-коде:
$ show_screen('some_screen', 'some text', 0x008000)
$ show_screen('some_screen', 'some text', 0x0080FF, 20)
Скрыть же скрин можно точно так же любым из 3-х этих способов, заменив при этом show
на hide
(передача каких-либо параметров при этом не имеет смысла и потому запрещена).
Заменить скрин from
на to
:
replace_screen("from", "to")
Теперь при вызове show_screen("from")
будет показываться скрин to
.
Вообще-то, для обычного переопределения скрина можно просто создать новый скрин с уже существующим именем.
Однако в таком случае старый скрин становится полностью недоступен, что не всегда подходит.
В случае использования replace_screen
команда use
по-прежнему имеет доступ к нужному скрину.
Это может быть использовано, например, для масштабирования, сдвига или каких-либо украшательств стандартного скрина
без изменения его кода (что важно, ведь он для этого не предназначен и может изменяться в разных версиях движка).
При использовании replace_screen
меняют своё поведение все функции [show/hide/has]_screen
,
а также, соответственно, и отправка сигналов из них
(сигналы отправляются как для "старых", так и для "новых" имён).
clipping True
- обрезать вложенные объекты за границами текущего (True
) или нет (False
, по умолчанию).
skip_mouse False
- "пропускать мышь через себя" (True
, полезно при создании инвентаря) или нет (False
, по умолчанию).
alpha 0.75
- непрозрачность (0
- полностью прозрачно, 1.0
- полностью видимо).
rotate 45
- поворот по часовой стрелке, в градусах.
xpos 10
- координата по оси X в пикселях (0
- самая левая точка).
ypos 15
- Y-координата (0
сверху).
pos (10, 15)
- более компактная форма записи 2-х предыдущих строк.
xanchor 50
- X-координата в объекте, которая будет считаться началом, точкой отсчёта.
Например, если xpos == 750
, а xanchor == 650
, то фактически объект будет рисоваться, начиная с X-координаты 100
.
Аналогично для yanchor
.
anchor (50, 75)
- установка xanchor
и yanchor
одной строкой.
xsize 100
- ширина объекта.
ysize 50
- высота.
size (100, 50)
- установка xsize
и ysize
.
Не стоит путать size
с text_size
, т. к. последнее отвечает за размер шрифта в текстовом поле.
xzoom 0.5
- масштаб объекта (и всех вложенных в него) по оси X (по умолчанию 1.0
).
Аналогично для yzoom
.
zoom (2.0, 0.5)
- установка xzoom
и yzoom
сразу.
Если размер не задан явно, то он вычисляется.
Размер vbox
и hbox
вычисляется так (на примере vbox
):
- Ширина - максимум среди ширины всех вложенных объектов,
- Высота - сумма высот всех объектов + учёт расстояния (
spacing
) между ними.
Аналогично, но наоборот, вычисляется размер hbox
.
Размер текстового поля вычисляется похожим образом, но вместо вложенных объектов там строки.
xalign 0.5
- размещение по центру оси X в родительском контейнере.
yalign 1.0
- размещение снизу экрана.
align (0.5, 1.0)
- сокращённая запись.
Как это работает:
xalign 1.0
# это сокращённая запись для:
xpos 1.0 # помещаем в правый край экрана,
xanchor 1.0 # ставим в качестве "нулевой точки" объекта его правый край
# Т. е. мы ставим самый правый пиксель объекта в самый правый пиксель экрана
Если тип xpos
- float
(нецелое число), то это значение умножается на ширину родительского контейнера.
В случае ypos
- на высоту.
Для anchor
умножение идёт на размер текущего объекта.
А для size
- на ширину и высоту окна.
Иначе такие штуки было бы непонятно как вычислять и их пришлось бы запретить:
hbox:
textbutton "Click Me!" xsize 0.9
Т. к. 0.9
было бы относительно ширины родительского контейнера, но она зависит от ширины этой кнопки.
Хотя можно было бы считать, что все эти размеры просто равны нулю, в этом не было бы абсолютно никакого смысла.
А если брать в расчёт не такие примитивные случаи, то мы бы получили ещё и кучу "магии",
когда объект исчезает непонятно почему, непонятно куда и непонятно во что.
spacing
умножается на высоту окна для vbox
и ширину для hbox
.
text_size
, очевидно, всегда умножается на высоту.
Свойства spacing
, text_size
, xsize
, ysize
и size
имеют свойства-ограничения с суффиксами _min
и _max
.
Например, при ширине окна 1920
, свойства xsize 0.5
и xsize_max 700
установят ширину объекта в 700
, а не 960
.
Эти свойства-ограничения работают, только если они больше 0
, т. е. по умолчанию они отключены.
Свойства alpha
, rotate
, pos
и zoom
у объектов наследуются от "родителей", в которых они вложены.
image parent_image:
rotate 45
alpha 0.5
xpos 1000
image child_image:
rotate 135 # 135 + 45 = 180
alpha 0.5 # 0.5 * 0.5 = 0.25
xpos -200 # так просто не вычислить, нужно учитывать поворот
Также компактная форма записи нескольких свойств (pos
, anchor
, size
, zoom
и align
) может принимать 1 параметр
вместо списка из 2, если x
и y
параметры должны быть одинаковыми, например:
zoom 2 # вместо zoom (2, 2)
align 0.5 # вместо align (0.5, 0.5)
С появлением масштабирования появляется и необходимость использовать нецелые числа в качестве абсолютных значений (а не
как часть, например, ширины окна), для этого существует тип absolute
:
null:
align 0.5
size 0.1
zoom 10
image snow:
align 0.5
size absolute(0.5) # размер будет 0.5 пикселя, но масштабирование увеличит его до 5
# size 0.5 - а это сделало бы размер снежинки в 0.5 экрана, после чего опять увеличило бы в 10 раз
В отличие от Ren'Py, где тип absolute
просто наследуется от float
, и любые операции с которым вернут обычный float
(даже absolute(1) + absolute(2))
вернёт float(3.0)
), в Ren-Engine всё работает корректно
(всегда возвращается absolute
, если хотя бы 1 из операндов является absolute
).
Ещё у объектов image
, imagemap
, button
и textbutton
есть свойство crop
- список из 4-х значений,
которые указывают, какую часть текстуры отрисовывать.
По умолчанию это (0.0, 0.0, 1.0, 1.0)
- (x, y, ширина, высота).
Числа в этом списке домножаются на ширину (x и ширина) и высоту (y и высота) текстуры, если они являются float
'ами.
Надо заметить, что это свойство не влияет на размер отрисовываемого объекта, а лишь указывает, какую часть текстуры
нужно отрисовывать на весь объект.
Также у этих 4 типов объектов есть свойство corner_sizes
,
отвечающее за сохранение размеров границ при масштабировании.
У image
и imagemap
оно по умолчанию установлено в 0
(отключено),
а у button
и textbutton
- -1
(автоматически - треть от меньшей стороны текстуры).
Если даётся просто 1
число, то оно устанавливается для каждой из сторон
(left
, top
, right
, bottom
- лево, верх, право, низ).
Можно указать 4
числа в кортеже или списке - отдельно для каждой из сторон.
Или 2
- тогда right
будет браться из left
, а bottom
- из top
.
Если в качестве размера даётся float
, то идёт умножение на нужную сторону текстуры.
Если какое-то свойство объекта не задано в явной форме, то оно берётся из его стиля.
Они хранятся в объекте style
(см. файл Ren-Engine/rpy/style.rpy
).
Компонент типа text
по умолчанию имеет стиль text
, image
- стиль image
и т. д.
Но можно создать свой стиль (как это сделать - см. в файле, что указан выше) и указать его, тогда недостающие свойства
будут браться из него, а прежний стиль будет игнорироваться.
Указать стиль можно свойством style
, например:
image 'some_image.png':
style 'my_super_style' # в кавычках, т. к. это указание строки
anchor (0.5, 0.5)
(На самом деле переданная строка - назовём её style_value
- заменяется на объект стиля style[style_value]
,
т. е. для случая выше это будет style['my_super_style']
или style.my_super_style
)
Но для этого стиль my_super_style
должен быть создан как-то так через python
:
style.my_super_style = Style(style.image)
style.my_super_style.pos = (10, 10)
style.my_super_style.size = (50, 50)
Или так через команду style
в init
-блоке:
style my_super_style is image:
pos (10, 10)
size (50, 50)
На самом деле этот код автоматически конвертируется в предыдущий код, практически дословно.
Кроме обычных свойств (pos
, size
...) стиль может содержать свойство properties
,
которое принимает в качестве значения словарь (dict
):
init:
python:
def my_func():
return {
'pos': 10, # конечно, допустима и сокращённая запись, если x- и y- свойства одинаковы
'size': 50,
}
style my_super_style is image:
anchor 0.5
properties my_func() # допускается любой python-код, а не только указание заранее созданного словаря
Свойства стиля кэшируются при его первом использовании, дальнейшие изменения не будут учитываться. Раньше кэширование было в пределах отрисовываемого кадра, а не всего запущенного мода, но из-за этого производительность была ниже на 10%, поэтому от этого было решено отказаться.
Тем не менее, не смотря на неизменяемость конкретных стилей, ScreenLang-объекты могут менять один стиль на другой:
init python:
# стили вообще не обязаны быть в объекте <styles>
my_style_1 = Style(style.textbutton)
# тут настройка my_style_1
my_style_2 = Style(style.textbutton)
# тут настройка my_style_2
my_current_style = my_style_1
def change_my_style():
global my_current_style
if my_current_style is my_style_1:
my_current_style = my_style_2
else:
my_current_style = my_style_1
screen my_screen:
key 'F7' action change_my_style
textbutton 'Exit':
style my_current_style
action exit_from_game
Также у стилей есть методы (каждый использует кэширование в пределах кадра):
-
get_current('xsize')
- получить текущее значениеxsize
с учётом размера окна и свойств-ограничений вродеxsize_min
иxsize_max
; -
get_ground(width = None, height = None)
- получить изображениеground
, отмасштабированное до нужных размеров (при переданныхNone
используютсяget_current('xsize')
иget_current('ysize')
) с сохранением размеров границ (которые берутся из когда-то "нестандартного" свойстваcorner_sizes
) функциейim.scale_without_borders
; -
get_hover(width = None, height = None)
- аналогично для свойстваhover
.
Последние 2 стали обычно не нужны после появления свойства corner_sizes
в ScreenLang.
Поддерживаются "действия" из renpy. Общая суть:
Создаётся вызываемый объект (callable
), который запоминает переданные ему параметры:
tmp = SetVariable('a', 123)
А потом происходит вызов этого объекта:
tmp()
Function
вызывает функцию (1-й параметр) с остальными параметрами:
key "RETURN" action Function(my_func, 5)
Также есть SetVariable('var_name', value)
, AddVariable('var_name', value)
и
ToggleVariable('var_name', true_value = True, false_value = False)
,
которые устанавливают переменной var_name
значение value
(Add - добавляет, Toggle - меняет значение на противоположное).
Есть и аналогичные действия вроде SetDict
и прочих
(где Variable заменено на Dict, и дополнительным первым параметром идёт словарь),
но они есть лишь для обратной совместимости,
SetVariable
и прочие тоже могут работать со словарями (и даже объектами):
SetVariable('persistent.my_config.my_prop', 123)
Тут стоит отметить, что точку между именами объектов и свойств следует использовать даже в том случае,
если идёт работа со словарём (т. е. не допускается использование квадратных скобок).
Пример:
$ my_var = 123
key "SPACE" action AddVariable('my_var', 321)
$ print(my_var)
GetSetAttr('path.to.some.prop', value = _undefined, obj = None)
отвечает за установку и чтение свойств.
Запись - при указании параметра value
(возврат None
), иначе - чтение (возврат свойства).
obj
- корневой объект, от которого ищется свойство (None
- глобальное пространство имён).
Кстати, под капотом там используется функция getset_attr
с такими же параметрами.
Возможно, она и сама по себе иногда будет кому-то полезна.
Действие Jump('your_label')
(прыжок на метку сценария), реализованно следующим образом:
def Jump(label):
return Function(renpy.jump, label)
Подобным образом реализованы также:
-
Call('label_name')
- вызов метки сценария с последующим возвратом на прежнее место, -
Play('file_name', 'channel')
иStop('channel')
- см. Музыка и звуки, -
Notify('Your Message')
- вывод уведомления (notification.out
), -
Language('english')
- смена языка (renpy.change_language
), -
Show
,ShowScreen
иShowMenu
- показать указанный скрин (renpy.show_screen
), -
Hide
,HideScreen
иHideMenu
- скрыть указанный скрин (renpy.hide_screen
).
В статье [Сохранение и загрузка][./Сохранение-и-загрузка] (из категории "Конфигурирование игры") также описаны некоторые связанные с этим действия.
If(cond, true, false)
возвращает аргумент true
, если условие cond
истинно, иначе возвращает False
(это обычная функция, возвращающая результат немедленно, добавлена для совместимости с renpy).
Eval(code, file_name = None, num_line = None)
Exec(code, file_name = None, num_line = None)
Выполняют код code
, при компиляции указывается место,
которое будет выведено в случае вылета исключения (None
- определить автоматически).
Разница между ними:
-
Eval
запускает код и возвращает результат (Eval("2 + 3")()
->5
), -
Exec
может делать присваивания, ничего не возвращает (Exec("v = f(2, 3)")()
->None
).
SetDictFuncRes(obj, 'var_name', func)
при вызове делает obj[var_name] = func()
Return(value)
используется для возвращения значения из скрина.
Вызвать из сценария скрин, который может вернуть значение таким образом, можно так:
$ renpy.call_screen('your_screen', 'your_variable')
После этого сценарий будет ждать до тех пор, пока ваш скрин не вернёт результат с помощью Return
.
Он будет записан в указанную переменную, которую сценарий сможет использовать.
Важно: за исключением некоторых особых ситуаций (см. Сигналы и таймауты и Статьи из раздела конфигурирования игры), в скринах удобнее использовать не указанные ранее функции, а строки с кодом:
-
key "F2" action SetVariable('my_prop', 2)
- вариант в стиле renpy, -
key "F2" action 'my_prop = 2'
- вариант в стиле Ren-Engine, проще и понятнее.
Также следует сказать, что вместо 1 функции/строки в action
можно передать список функций или строк.
И это ещё 1 причина использовать строки с кодом, а не функции.
Ведь указанные выше "функции" в большинстве своём являются лишь обёртками, которые сначала
запоминают все параметры, а уже потом вызываются.
В результате такой код:
key "F2" action [SetVariable('my_prop', 23), Function(my_func, my_prop)]
сначала запомнит старое значение my_prop
, потом установит ему значение 23
,
а затем вызовет my_func
со старым значением my_prop
,
хотя на первый взгляд может показаться, что никакого запоминания старого значения здесь нет.
В то же время код
key "F2" action ['my_prop = 23', 'my_func(my_prop)']
будет работать ровно так, как и задумывалось.
Хотя тут следует обратить внимание на то,
что в случае забывания кавычек вокруг вызова my_func
всё тоже будет плохо
(несмотря на отсутствие Function
и, следовательно, запоминаний в ней,
без указания кавычек код будет выполнен сразу во время составления списка,
тогда как запись в переменную будет выполнена, очевидно, уже после этого составления,
ведь нельзя же отправить в функцию выполнения недоделанный список).
Поэтому будьте предельно внимательными при составлении списка действий.