Простейшие примеры - TrueCat17/Ren-Engine GitHub Wiki
python:
np = Character("Новая Персона", unknown_name = "Неизвестная Персона", color = "#FF8000")
-
"Новая Персона"
- первоначальное имя создаваемого персонажаnp
, -
unknown_name
используется после вызова функцииmake_names_unknown()
, -
color
- цвет в формате#RRGGBB
.
Также задать имя можно функцией set_name
(она же meet
):
$ set_name('np', "Прохожий")
А получить текущее имя можно так:
$ get_name('np')
Обратите внимание на необходимость кавычек.
Выполнив $ make_names_known()
, можно вернуть оригинальные имена для всех персонажей.
- narrator (используется, по умолчанию, когда ничего не указано) - рассказчик:
"Светит яркое солнце."
- th - мысли главного героя вашего произведения:
th "Мне было жарко."
- me - слова от лица главного героя (нужно регистрировать самостоятельно):
me "Здравствуйте."
- extend - в отличие от всех остальных, не стирает текст, а продолжает его дальше от лица предыдущего говорящего:
extend " Дайте 2 мороженных."
Выводимый текст может быть python-выражением.
Следующий пример выведет текст "5 + 5 = 10" (без кавычек, конечно) от лица Новой Персоны (регистрация была сверху):
np "5 + 5 = " + str(5 + 5)
В репликах допустимы теги курсива (i
), "полужирности" (b
), подчёркивания (u
), зачёркивания (s
),
указания цвета (color
), указания цвета обводки (outlinecolor
), размера (size
), прозрачности (alpha
),
шрифта (font
) и вставки изображения (image
), а в диалоговом окне также доступен тег паузы (w
).
Также есть тег plain
, который обнуляет теги i
, b
, u
, s
внутри себя.
Теги могут быть как угодно вложены друг в друга, но они должны закрываться в порядке,
обратном порядку открытия (но image
и w
закрытия не требуют).
Т. е. вот правильное закрытие тегов:
np "{b}полужирный {i}полужирный курсив{/i} полужирный.{/b}"
А вот неправильное:
np "{b}полужирный {i}полужирный курсив{/b} курсив.{i}"
Далее следуют более подробные примеры.
Пауза может иметь время (в секундах), после которого она прекратится:
np "Текст1{w=1}-Текст2{w=3.5}-Конец."
Если время не установлено, то оно считается равным бесконечности.
Пауза любой продолжительности может быть пропущена нажатием <Enter> или <Space>.
Цвет задаётся 16-ричным кодом в формате RRGGBB (допускается префикс #
), без кавычек:
np "Сколько {s}{color=FFFFFF}зим{/color}{/s} {color=#00AA00}лет{/color} мы с тобой не виделись!"
np "Здравствуйте, я {s}Кирилл{/s} " + str(np) + ". {w}{i}Суть такова{/i}... {w}Я {b}джва{/b} года хочу такую игру!"
Как видно из 2-х последних примеров, слишком большое количество тегов скорее навредит тексту, чем приукрасит его.
Используйте их в меру.
Можно указать напрямую:
np "Обычный {size=50}Размер 50{/size} обычный."
Можно увеличить или уменьшить относительно предыдущего:
np "{size=30}30 {size=+5}35{/size} 30 {size=*0.5}15{/size} 30 {size=-5}25 25 25..."
Указывается так же, как и размер, но 0.0
означает полную прозрачность, а 1.0
- полную непрозрачность.
Указывается имя файла (без расширения вроде .ttf
или .woff2
) шрифта из папки resources/fonts/
после тега font
:
np "{font=Alcdnova}Изменённый шрифт."
В теге изображения можно либо указать имя изображения, зарегистрированного командой image
, либо дать код,
выполнение которого вернёт путь к файлу изображения (в самом простом случае нужно просто заключить этот путь в кавычки):
image key = "images/items/key.png" # где-то в init-секции
np "Получен предмет: {image=key}."
Или же:
np "Получен предмет: {image=images/items/key.png}." # тег image принимает путь без кавычек
Изображения в тексте изменяют свой размер так, чтобы сохранялось соотношение сторон и чтобы высота была равна размеру текущего размера шрифта.
Кстати, строка с репликой:
np some_str
Делает то же самое, что и строка питона:
$ np(some_str)
Т. е. персонаж тут используется в качестве вызываемого объекта (у него вызывается функция __call__
с параметром some_str
).
Код для питона можно выполнить 2 способами.
Как одну строку (такие начинаются со знака $):
$ print(min(123 * 432, 234 * 321))
Или как блок python-кода:
python:
def pm(a, b):
print(a * b)
pm(33, 45)
Кстати, содержимое print
также выводится и в лог-файл (var/log.txt
).
Тут всё как в питоне.
$ a = 3 + 4
if a < 5:
# Эта ветка выполнится, если переменная a будет меньше 5
# 7 < 5 - это ложь
np "Да это же наглая ложь!"
elif a == 5:
# Кол-во веток elif может быть любым:
# и 0, и 1, и 5, и 1000, но обычно 10 - уже перебор
# Ветка elif выполняется, если условия всех предыдущих
# веток elif и первой if были ложными
np "3 + 4 == 5? {w=1.5}Серьёзно?"
elif a > 10:
np "Теперь в другую сторону перегнули..."
else:
# Только эта ветка будет выполнена, т. к. все предыдущие
# условия оказались не верны
# else может быть использована (а мб и не использована)
# только в конце, после if или elif
np "(НЕ) ОТГАДАНО."
Кстати, тут показаны комментарии: всё, что находится после # - игнорируется (как и в питоне).
!!!
Ни в коем случае нельзя путать приравнивание:
a = 3 # Теперь a будет равна 3
Со сравнением:
a == 3
# Это выражение вернёт True (истина), ЕСЛИ a равна 3, ИНАЧЕ вернёт False (ложь)
# В любом случае, значение a остаётся прежним, а не меняется на 3
Опять же, всё как в питоне, но поддерживается только while
(т. к. for
использует итераторы, которые нельзя сохранить).
np "Я считаю до 5."
np "Не могу до 10."
np ""
$ num = 0
while num < 5:
extend str(num + 1) + " "
$ num += 1
extend "- с рифмой с детства я дружу!"
# Последняя строка будет такой:
# 1 2 3 4 5 - с рифмой с детства я дружу!
Есть даже
-
continue
(переходит к след. шагу цикла), -
break
(прерывает цикл), -
else
(выполняется, если не было выхода из цикла через break).
$ a = [3, 1, 4, 8, 5, 2, 6]
$ my_index = 0
while my_index < len(a):
$ my_value = a[my_index]
$ my_index += 1
if my_value <= 2:
continue
if my_value > 7:
break
np "Ещё одно подходящее число найдено. Это " + str(my_value)
else:
# Сюда управление не перейдёт в данном примере, т. к. в списке a есть 8
np "Кажется, тут не было чисел больше 7, потому что break не был использован..."
Вообще, циклы вне питона используются редко. Поэтому, если вы знаете питон, то дальнейших объяснений вам не нужно. А если не знаете, то вам они вряд ли пригодятся (но если что - поищите уроки по циклам питона).
Позволяют прыгнуть на нужную метку.
jump
после завершения метки возвращает в меню.
Для возврата назад после выполнения метки нужно использовать call
вместо jump
, это их единственное различие.
# Допустим, мы сейчас на этой метке
label cur_label:
# ...
jump need_label
me "А этих слов я уже никогда не скажу."
me "Но если я их уже говорю, значит... "
extend "Парой строк выше jump был коварно заменён на call!"
label need_label:
me "И вот мы здесь."
# ...
me "А здесь конец метки."
Для этого можно использовать expression
после jump
/call
:
jump expression "end_root_" + random.choice(cur_root_names)
Прекращает выполнение текущей метки, будто текущая команда в ней была последней.
Ничего не делает, как и в питоне служит для заполнения места в "пустых" блоках.
Операции с диалоговым окном. Тут всё как в Ren'Py.
Так можно скрыть его:
window hide
А так - показать:
window show
Но тут не поддерживаются эффекты скрытия/показа (with effect_name
).
$ set_mode_adv()
# Обычный режим. Следующая реплика удалит ту, что видна сейчас. Текст снизу экрана.
$ set_mode_nvl()
# Следующая реплика будет идти ниже текущей. Текст начинается сверху экрана.
Очищать экран от реплик в nvl-режиме нужно так:
nvl clear
Если этого не делать, то довольно быстро текст будет отображаться так низко, что его нельзя будет увидеть (ниже границы окна игры).
$ energy = 10
$ skills = 4
me "Куда бы мне пойти?":
menu:
"Никуда, останусь сидеть дома.":
pass
"На работу пора." if skills > 0 else None:
$ energy -= 5
$ skills += 1
"В парк!" if energy > 2 else None:
$ energy -= 2
jump day2_park_meet
Как можно заметить, некоторые пункты меню можно не отображать (в отличие от Ren'Py тут требуется else None
для этого).
Если результат равен None
, то пункт меню не отображается, но если это пустая строка, то получается зазор между пунктами.
Если что, res1 if cond else res2
- обычная питоновская конструкция:
print("3 меньше 4." if 3 < 4 else "Ловите наркомана!")
Из питона:
$ renpy.pause(1.5)
Командой:
pause 1.5
Внимание, пауза идёт после исполнения текущей КОМАНДЫ, а не после СТРОКИ вызова паузы:
python:
renpy.pause(5)
print("Эта строка будет выведена сразу же.")
me "А вот эта - через 5 секунд."
Сделано это для того, чтобы как можно скорее освободить python от исполнения этой команды.
Нужно помнить, что python - штука однопоточная, поэтому если он встанет на паузу, то графический интерфейс тоже перестанет обновляться,
ведь ему требуется питон для вычисления свойств визуальных объектов (позиция, размеры, условия, действия и т. д.).
Если же вам и вправду нужно, чтобы ваше приложение в прямом смысле зависло на некоторое время, используйте функцию time.sleep(1.5)
.
Также любая пауза может быть промотана нажатием Enter
или Space
.
Параметра hard
(непропускаемость паузы) здесь не существует.
Всегда ненавидел смотреть слайдшоу по 1.5 минуты без возможности пропуска.
Если вы хотите к себе тонны ненависти - можете прикрутить этот hard
, у меня бы ушло на это не больше 15 минут, но я берегу вас, как могу.
Перед тем как говорить непосредственно о запуске игры, следует дать определение понятию "мод".
Мод - это папка с rpy
-файлами и нужными ресурсами.
Понятия "мод" и "непосредственно сама игра" объединены в одно, чтобы не создавать новые понятия там, где в этом нет необходимости.
Ведь основная игра может делать ровно то же самое, что и моды.
Все моды лежат в resources/mods/
.
В начале игры запускается мод основного меню main_menu
.
Из него происходит переход к другим модам (например, к основной игре или к какому-то дополнению, и проч.)
Q: Зачем нужно деление модов на директории, почему нельзя свалить их все в одну кучу?
A: Потому что:
- В этой куче будет сложно что-то найти, если понадобится.
- Возможно замещение ресурсов одного мода ресурсами другого.
- При использовании одних и тех же имён переменных/меток/скринов моды могут конфликтовать друг с другом. Поэтому каждый мод лежит в своей папке, и в один момент времени может быть запущен только один мод. Также это хорошо сказывается на времени запуска (можно установить хоть 1000 модов, время запуска любого из них будет таким, будто он один).
Q: Что если мне не нужно главное меню?
A: Можно запустить нужный мод сразу в init
-блоке главного меню.
init -200000000:
# Низкий приоритет для того, чтобы не ждать выполнения других init-блоков.
$ start_mod("my_super_game")
Q: Что происходит при запуске мода?
A:
- Происходит парсинг (разбор) всех rpy-файлов:
- в
../Ren-Engine/rpy/
(файлы движка), - в
mods/common/
(общие файлы для всех модов текущей игры) и - в директории мода,
- в
- Выполняются все
init
-блоки в нужном порядке, - Добавляются все скрины, имена которых перечислены в списке
start_screens
(по умолчанию:["hotkeys", "dialogue_box", "fps_meter"]
), - Происходит переход на метку (
label
)start
:
Если она отсутствует, то исполнен будет только код скринов, сценарий же запущен не будет,
Мод будет завершён:- При выходе из игры или при запуске другого мода через
start_mod
(см. тут), - При завершении всех команд в метке
start
(если она есть, конечно), - При завершении всех команд в метке, запущенной с помощью
jump
.
- При выходе из игры или при запуске другого мода через
Порядок инициализации определяется приоритетом, аналогично Ren'Py. Чем меньше приоритет, тем раньше запустится init
-блок.
Например, зададим переменным значения в одном блоке, а используем в другом.
init -1:
$ a = 4
$ b = 56
init 0:
$ print(a, b)
Если поменять приоритеты (0
и -1
) местами, то получим ошибку о несуществовании переменных, которые хотим использовать.
Кстати, init
-блоки могут быть полностью питон-кодом, т. е. первый блок из прошлого примера можно переписать так:
init -1 python:
a = 4
b = 56
Каждый мод начинает своё выполнение с метки start.
Имя каждого мода указывается в файле name
(именно так, без расширения вроде .rpy
или .txt
) в папке мода.
Его содержимое, разумеется, должно быть в UTF-8.
Кстати, при запуске Ren-Engine из консоли (или скрипта) ему можно передать путь к resources в первом параметре.
Если он не указан, то он считается равным ../resources/
относительно исполняемого файла.
После запуска Ren-Engine перейдёт в директорию с ресурсами и уже относительно неё будут проводиться все дальнейшие действия.