Пример автопилота - TrueCat17/Ren-Engine GitHub Wiki
Эта глава показывает пример создания своего автопилота.
Вероятно, она будет бесполезна для новичков в программировании из-за сложностей некоторых вещей.
В этом случае рекомендуется просто использовать стандартный автопилот из предыдущей главы.
Если же вы хотите сделать сделать своих персонажей более живыми, оригинальными и необычными, то добро пожаловать!
Также после прочтения рекомендуется ознакомиться с реализацией стандартных действий в Ren-Engine/rpy/rpg/actions.rpy
.
Итак, предположим, у нас есть патрульный ch
, он обходит локацию "склад" (store).
Начиная с места point-1
идёт до point-2
, затем до point-3
и т. д.
В конце же возвращается к point-1
.
И пусть доходя до каждой точки он с некоторой вероятностью оглядывается по сторонам.
Для начала рассмотрим функцию появления.
def patrol_spawn(character, state):
if state == 'start':
point_nums = []
for place_name in rpg_locations['store'].places.keys():
if not place_name.startswith('point-'): continue
num = place_name[len('point-'):]
if num.isdigit():
point_nums.append(int(num))
point_nums.sort()
index = random.randint(0, len(point_nums) - 1)
show_character(character, 'point-' + str(point_nums[index]), 'store')
actions = character.get_actions()
actions.point_nums = point_nums
actions.next_index = index + 1
return 'end'
Строка actions.start('spawn')
запускает это действие сразу,
функция patrol_spawn
выполняется с параметром state = 'start'
.
В начале мы берём все подходящие места, берём их число-окончание, добавляем их в массив и сортируем его.
Этот подход хорош тем, что он не требует отсутствия "дыр",
т. е. если у нас есть 10 мест вида point-N
и мы удаляем point-4
, то после point-3
список будет продолжаться.
Кроме того, из-за сортировки по числам-окончаниям point-10
будет идти после point-2
,
тогда как сортировка по строкам покажет другой, нежелательный результат.
Далее мы берём случайное место, ставим в него персонажа и запоминаем список чисел-окончаний и следующую позицию.
Случайность здесь позволяет сделать вид, что персонаж ходит уже давно, и находится в данной позиции только из-за того,
что мы заглянули к нему именно в данное время.
Теперь разберём функцию ходьбы между отмеченными точками по кругу.
def patrol_walking(character, state):
actions = character.get_actions()
if state == 'start':
num = actions.point_nums[actions.next_index % len(actions.point_nums)]
character.move_to_place('point-' + str(num))
actions.next_index += 1
return 'moving'
if state == 'moving':
if character.ended_move_waiting():
return 'end'
return 'moving'
if state == 'end':
if not character.ended_move_waiting():
actions.next_index -= 1
character.move_to_place(None)
return 'end'
В начале мы направляем персонажа к следующей точке, не забывая её после этого обновлять (+= 1
).
Остаток от деления (%
) на длину списка позволяет не думать о сбрасывании счётчика в 0
при достижении последней точки в этом списке.
В состоянии moving
мы просто ждём, пока персонаж дойдёт.
В конце не забываем проверить, что мы именно дошли, а не были остановлены запуском какого-то другого действия. В случае последнего возвращаем индекс "следующего места" назад и останавливаем персонажа.
И наконец, соединим это всё вместе и отправим в работу.
patrol_actions = RpgActions()
patrol_actions.set_action('spawn', patrol_spawn, 0)
patrol_actions.set_action('walking', patrol_walking, 4)
patrol_actions.set_action('look_around', rpg_action_look_around, 1)
ch = Character(...)
ch.make_rpg(...)
ch.set_auto(True)
ch_actions = ch.set_actions(patrol_actions)
ch_actions.start('spawn')
Первая строка создаёт экземпляр "действий".
Далее мы добавляем туда желаемые действия: появление в нужной точке, ходьба и оглядывание по сторонам.
Последнее действие является стандартным, о нём говорилось в предыдущей главе.
Обратите внимание на вероятности запуска этих действий:
- их сумма равна
5
, она не обязана равняться100
, -
spawn
не может быть запущен случайно (вероятность этого равна0
), это нужно сделать вручную, - вероятность запуска
walking
-80%
(4/5
), аlook_around
-20%
(1/5
).
Строки с ...
подразумевают, что вы уже достаточно хорошо знаете (или можете найти),
как делается создание персонажа и его инициализация для RPG-игры.
Наконец, мы позволяем автопилоту управлять данным персонажем,
устанавливаем ему RPG-действия и
запускаем у него действие spawn
.
Вот и всё.
Этот автопилот не так уж и сложен, но при этом довольно гибок и расширяем.
По крайней мере, он таким задумывался.
Иногда бывает удобно сделать одно общее действие, которое будет использоваться другими действиями.
Например, other_place
принимает дополнительным параметром список локаций,
в которых и будет осуществляться поиск случайного места. По умолчанию это None
- текущая локация,
но также туда можно передать, к примеру, список "интересных" локаций.
Вот реализация функции перехода к соседней локации:
def rpg_action_near_location(character, state):
location_names = [place.to_location_name for place in character.location.places.values() if place.to_location_name]
character.get_actions().set('other_place', location_names, state = state)
return IGNORE_STATE
Сначала берём все соседние локации (локации, в которые ведут выходы текущей локации персонажа).
Затем запускаем действие автопилота other_place
с нужными параметрами.
Метод set
отличается от start
только тем, что он не останавливает предыдущее действие
(не вызывает текущую функцию с параметром state = 'end'
), а сразу запускает указанное.
И наконец, возвращаем "состояние" IGNORE_STATE
(на самом деле это простая переменная-строка),
которое будет проигнорировано, а не записано (т. к. запись уже была сделана в методе set
).
При следующем обновлении уже будет запускаться действие other_place
с нужными параметрами,
словно именно оно и было так запущено с самого начала.