Автопилот - TrueCat17/Ren-Engine GitHub Wiki

У персонажей можно управлять автопилотом.
Поддерживаются включение/выключение, определение набора действий, их вероятностей и запуск определённого действия из этого определённого набора.

Эта статья будет идти от простого и более важного до менее востребованного, но более сложного и детализированного.
Если вы не являетесь программистом, то можно предположить, что вам будут полезны только первые 4 абзаца.


Быстрый старт

Для начала можно просто "включить" автопилот без каких-либо дополнительных настроек, создав "стандартные действия" и установив их для нужного персонажа (или нескольких):

std_actions = get_std_rpg_actions()

character.set_auto(True)
character_actions = character.set_actions(std_actions)
character_actions.start('spawn')

Здесь character - RPG-персонаж (персонаж с вызванной make_rpg), которому устанавливается автопилот, а character_actions - копия действий, которая принадлежит именно этому персонажу.
Последняя строка запускает одно из стандартных действий - spawn (появление; в данном случае - в случайной локации).


Полезные функции

Остановить текущее действие для персонажа character:
character.get_actions().stop()
При включённом автопилоте следующее обновление персонажа запустит случайное действие (см. далее).

Запустить действие name (не забывать про кавычки):
character.get_actions().start('name')

Запустить случайное действие:
character.get_actions().random()

Обновить действие, перед этим установив ему состояние state:
character.get_actions().update(state = None)
Если state не задано (по умолчанию), то оно не меняется.

Также автопилот можно выключить:
character.set_auto(False)

Следует указать, что при запуске нового действия (случайного или нет) или при выключении автопилота
текущее действие автоматически завершается (и сидящий персонаж встанет, а идущий остановится).


Свойства

Используются при выполнении некоторых действий.

interesting_places - список названий интересных локаций, в которые персонаж будет заглядывать чаще, чем в другие.
Например, площадь и пляж: std_actions.interesting_places = ['square', 'beach']

home - имя локации домика для этого персонажа, либо (если такой локации нет) кортёж из имени локации и названия места в нём (рядом с какой-либо дверью), в котором этот персонаж будет пропадать во время "захода" в домик и появляться во время "выхода".
Примеры:
character_actions.home = 'house'
character_actions.home = ('street', 'door')

friends - список персонажей-друзей (не их имён, важно!) или ещё кого-либо, с кем данному персонажу будет логично время от времени о чём-то говорить. Если персонаж находится в списке "друзей" другого, то это не значит, что он сам обязан содержать его в своём списке, при необходимости этого можно не делать.
Пример: dv.get_actions().friends = [us, mi]

Как уже было сказано выше, функция set_actions создаёт копию переданных ей действий, присваивает её в действия персонажа и возвращает их в качестве своего результата. Свойства тоже копируются, поэтому в примере выше с interesting_places этот список будет доступен у всех персонажей, для которых установят эти std_actions.

Но так уж устроен Питон, что скопируется не сам список, а ссылка на него, поэтому при изменении этого списка у одного персонажа, он изменится и у всех остальных, и это следует учитывать.
Например, перед изменением можно делать копию:

character_actions.interesting_places = list(character_actions.interesting_places)
character_actions.interesting_places.remove('beach')
character_actions.interesting_places.append('park')

Почему set_actions не делает сразу полную копию списка?
Потому что иногда именно такое поведение может быть удобным.


Стандартные действия

Список стандартных действий:

  • spawn - появление в локации из списка интересных мест, если он задан (см. предыдущий абзац), либо просто в случайной локации,
  • sit - попытка посидеть некоторое время на ближайшем объекте-сиденье (если есть на текущей локации),
  • other_place - переход в случайное другое место текущей локации,
  • near_location - переход в случайное место соседней локации,
  • random_location - переход в случайное место случайной локации,
  • interesting_place - переход в случайное место локации из списка интересных (текущей локации, если список не задан),
  • look_around - оглядеться по сторонам,
  • home - пойти домой (если дом указан, см. предыдущий абзац),
  • to_friend - пойти к персонажу из списка друзей (снова предыдущий абзац), одновременно посылая его к себе навстречу,
  • follow - следовать за указанным персонажем (можно обновить с состояниями rewind_to_min/rewind_to_max для "перемотки" движения).

За каждое действие отвечает отдельная python-функция.
У стандартных действий эта функция имеет имя rpg_action_%action_name%.
Например, rpg_action_spawn или rpg_action_to_friend.


Списки разрешённых и заблокированных действий

Хранятся в свойствах allow и block соответственно, содержат названия действий (или сами функции).
Заблокированное действие не может быть выбрано автоматически и не может быть запущено вручную.
Если список разрешённых действий не пуст, то запрещены все действия, которых в нём нет.
Копируются, как и другие свойства.


Вероятности действий

Каждое действие имеет свою вероятность, с которой оно будет выбрано после выполнения предыдущего действия.
Также кроме обычной вероятности может быть задан диапазон из минимальной и максимальной, тогда вероятность будет новым случайным числом в этом диапазоне при каждом новом выборе.

Вероятность может быть нулевой, тогда такое действие никогда не будет выбрано автоматически (например, spawn).
Сумма вероятностей не обязана равняться 100, это не проценты.

Стандартные действия имеют вероятности от 10 до 50 (у каждого по разному), но здесь не приводится точных цифр, т. к. они могут быть изменены в будущем, если будет решено, что это улучшит автопилот.
Посмотреть точные цифры на текущий момент можно в файле Ren-Engine/rpy/rpg/actions.rpy в функции get_std_rpg_actions.

Там же можно увидеть, как инициализируется объект RpgActions, и как можно обойтись без использования вышеупомянутой функции получения стандартных действий.


Состояния действия

При запуске устанавливается действие, его состояние start, а также именованные и неименованные параметры.
По умолчанию действие будет запущено не сразу, а лишь при следующем обновлении персонажа (в ближайшем кадре).
Иногда это не желательно, тогда в метод start можно передать в явном виде параметр state = 'start'.

При вызове функция действия возвращает новое состояние, которое будет передано ей при следующем обновлении.
Если возвращается состояние end, то действие тут же вызывается снова с состоянием end, после чего оно считается завершённым, и происходит запуск случайного действия.

Почему при возврате состояния end функция вызывается с этим состоянием тут же, а не на следующем кадре?
Это сделано для удобства управления передвижением.
Метод Character.move_to_place пытается проложить дорогу до нужной точки, но если это невозможно, то двигает персонажа по прямой сквозь все препятствия (если начальная и конечная точка на 1 локации) или телепортирует в конечную точку (если она в другой локации).
Если это нежелательно, то в случае отсутствия пути для отмены передвижения следует вызвать этот же метод со значением None, чтобы остановить персонажа, после чего обычно следует завершение действия.
И ровно то же самое (остановка на месте) происходит и в состоянии принудительного завершения.
Поэтому, чтобы не дублировать код, состояние end обновляется сразу же.

Текущее состояние хранится в свойстве state.
Текущая функция - в свойстве cur_action.
Хранится именно функция, а не имя действия, т. к. это позволяет однократно запускать функцию без её регистрации в наборе действий.


Пользовательские действия

Вы можете добавить свои функции для управления персонажами:
actions.set_action("name", function, min_chance, max_chance = None)
Здесь:

  • actions - RPG-действия, их можно получить, создав экземпляр класса RpgActions или из функции get_std_rpg_actions,
  • name - имя устанавливаемого действия (строка),
  • function - функция, управляющая поведением,
  • min_chance, max_chance - диапазон вероятности запуска (об этом говорилось в абзаце "Вероятности действий").

Функция должна принимать 2 параметра:

  • character - персонаж, которым нужно управлять,
  • state - состояние действия на текущий момент (об этом говорилось в абзаце "Состояния действий").

Кроме того, функция может принимать и другие параметры, которые ей можно передать во время запуска:
actions.start(my_super_func, my_param = 'my_value')

Напоминание: получить RPG-действия персонажа character можно через character.get_actions().
Через них можно запускать другие действия или хранить какие-то данные.
Кстати, при запуске другого действия из текущего, необходимо вернуть значение IGNORE_STATE, чтобы значение state не было перезаписано возвращаемым значением (см. rpg_action_near_location).

Также необходимо помнить, что:

  • при 60 кадрах в секунду на обновление 1 кадра даётся 16 мс,
  • кроме вашего кода выполняется куча другого кода,
  • ваше действие может выполняться на 100 различных персонажах каждый кадр,
  • наиболее сложно-вычислимые стадии действий иногда будут происходить сразу у нескольких персонажей в 1 кадре,
  • у питона есть сборщик мусора, который тоже иногда будет вносить свой вклад в задержку обновления кадра. Поэтому вам нужно максимально экономить ресурсы компьютера.

Например, прокладывание пути (тяжёлая операция) в большинстве случаев достаточно сделать 1 раз (на старте), в то время как бОльшую часть времени будет происходить дешёвая по времени проверка вида "Ещё идём? Да - продолжаем идти. Нет - завершение".
Если же путь нужно время от времени обновлять (например, если цель пути - двигающийся персонаж), то делать это стоит не каждый кадр, а раз в секунду, а если локации персонажа и цели разные (т. е. расстояние велико) - раз в 3 секунды (цифры даны в качестве простого и понятного примера).


Время отсутствия повтора

Иногда нужно запретить выбор того же действия рандомом после того, как это действие закончилось.
Например, если персонаж только что сидел и встал, то снова садиться после этого - нелепо.

Для этого есть метод actions.set_cooldown(action, time), который запрещает действию action повторяться time секунд после завершения.

Это относится только к случайному выбору действия, при необходимости его всё ещё можно запустить вручную.


Случайный параметр

Некоторые действия (как, например, sit - сидеть) имеют длительность своей основной фазы (которая может быть случайной).

Для более удобной настройки таких параметров имеется метод actions.set_random_param('your_param', min_value, max_value).
Для отключения случайности параметрам min_value и max_value можно указать одно значение.

Действие при необходимости вызовет метод actions.get_random_param('your_param') и получит случайное число от min_value до max_value, указанных ранее.


Очередь действий

Добавить действие в очередь (такие действия будут выполняться друг за другом по порядку):
character_actions.add('need_action')

Можно указывать и доп. параметры:
character_actions.add('need_action', need_location)
character_actions.add('need_action', max_dist = 150, state = 'moving')

Метод add добавляет указанные параметры в свойство queue и запускает первое действие из этого массива с нужными параметрами после завершения текущего действия (после чего удаляет его из очереди).

Каждый элемент "очереди" является массивом со значениями [action_name, args, kwargs], при необходимости его можно менять/смотреть вручную.


Работа со временем

Система сохранения и загрузки игры накладывает свой отпечаток на работу со временем.

Нельзя полагаться на то, что следующий кадр будет через примерно 0.016 секунды, или что текущее время + 3 секунды не будет давно прошедшим для уже следующего кадра.
Вполне возможно, что в текущем кадре будет сделано сохранение, которое будет загружено через минуту или через месяц (в любом случае вы рискуете получить неадекватный результат).

Поэтому рекомендуется вместо time.time() использовать специальную функцию get_game_time(), которая учитывает лишь нужное время.


Может оказаться полезной вспомогательная функция для поиска случайного места, свободного для передвижения:
rpg_random_free_point(location_names)
Где location_names - список локаций (None недопустим).
Возвращает кортёж (location_name, x, y) или None (если некоторое число случайных проверок не дали результата).


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

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