Простейшие примеры - 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 после завершения метки возвращает в меню.
Для возврата назад после выполнения метки нужно использовать 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)


return

Прекращает выполнение текущей метки, будто текущая команда в ней была последней.


pass

Ничего не делает, как и в питоне служит для заполнения места в "пустых" блоках.


window

Операции с диалоговым окном. Тут всё как в Ren'Py.
Так можно скрыть его:
window hide
А так - показать:
window show

Но тут не поддерживаются эффекты скрытия/показа (with effect_name).


ADV и NVL режимы

$ set_mode_adv()
# Обычный режим. Следующая реплика удалит ту, что видна сейчас. Текст снизу экрана.

$ set_mode_nvl()
# Следующая реплика будет идти ниже текущей. Текст начинается сверху экрана.

Очищать экран от реплик в nvl-режиме нужно так:
nvl clear
Если этого не делать, то довольно быстро текст будет отображаться так низко, что его нельзя будет увидеть (ниже границы окна игры).


Меню выбора

Choice menu

$ 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: Потому что:

  1. В этой куче будет сложно что-то найти, если понадобится.
  2. Возможно замещение ресурсов одного мода ресурсами другого.
  3. При использовании одних и тех же имён переменных/меток/скринов моды могут конфликтовать друг с другом. Поэтому каждый мод лежит в своей папке, и в один момент времени может быть запущен только один мод. Также это хорошо сказывается на времени запуска (можно установить хоть 1000 модов, время запуска любого из них будет таким, будто он один).

Отсутствие меню

Q: Что если мне не нужно главное меню?
A: Можно запустить нужный мод сразу в init-блоке главного меню.

init -200000000:
	# Низкий приоритет для того, чтобы не ждать выполнения других init-блоков.
	$ start_mod("my_super_game")

Запуск мода

Q: Что происходит при запуске мода?
A:

  1. Происходит парсинг (разбор) всех rpy-файлов:
    1. в ../Ren-Engine/rpy/ (файлы движка),
    2. в mods/common/ (общие файлы для всех модов текущей игры) и
    3. в директории мода,
  2. Выполняются все init-блоки в нужном порядке,
  3. Добавляются все скрины, имена которых перечислены в списке start_screens (по умолчанию: ["hotkeys", "dialogue_box", "fps_meter"]),
  4. Происходит переход на метку (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.


resources

Кстати, при запуске Ren-Engine из консоли (или скрипта) ему можно передать путь к resources в первом параметре. Если он не указан, то он считается равным ../resources/ относительно исполняемого файла. После запуска Ren-Engine перейдёт в директорию с ресурсами и уже относительно неё будут проводиться все дальнейшие действия.


Далее ->
<- Назад

⚠️ **GitHub.com Fallback** ⚠️