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


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

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