Пример автопилота - 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 с нужными параметрами,
словно именно оно и было так запущено с самого начала.