В первой части мы разобрали основы Ursina и создали простую 3D-игру. Теперь перейдем к более сложной механике — искусственному интеллекту для NPC с помощью поведенческих деревьев (Behavior Trees).
1. Что такое поведенческие деревья?
Поведенческое дерево — это структура, которая определяет логику действий NPC. В отличие от простых скриптов, оно позволяет:
-
Гибко комбинировать условия и действия
-
Легко масштабировать ИИ-логику
-
Избегать спагетти-кода в сложных сценариях
Пример простого дерева для NPC-стража:
Поведение охранника: ├─ Если видит игрока → Атаковать ├─ Если слышит шум → Исследовать └─ Иначе → Патрулировать
2. Подключаем AI в Ursina
Ursina включает модуль Behavior для работы с поведенческими деревьями.
Шаг 1: Создаем базового NPC
from ursina import * from ursina.prefabs.ai import Behavior app = Ursina() # NPC - это просто куб с "мозгами" npc = Entity(model='cube', color=color.red, position=(3, 0, 0))
Шаг 2: Добавляем поведение
# Определяем действия NPC def patrol(): npc.x += time.dt * 2 # Движется вправо if npc.x > 5: npc.x = -5 # Возвращается на старт def chase_player(): npc.position += (player.position - npc.position).normalized() * time.dt * 3 # Создаем дерево поведения npc.add_script(Behavior({ 'sequence': [ {'condition': lambda: distance(npc, player) < 3, 'action': chase_player}, {'action': patrol} ] }))
Как это работает:
-
NPC сначала проверяет, близко ли игрок (distance < 3).
-
Если да — преследует (chase_player).
-
Если нет — патрулирует (patrol).
3. Сложные сценарии
Пример: NPC с тремя состояниями
states = { 'calm': { 'action': lambda: setattr(npc, 'color', color.green), 'transition': [ {'condition': lambda: distance(npc, player) < 4, 'next_state': 'alert'}, ] }, 'alert': { 'action': lambda: setattr(npc, 'color', color.yellow), 'transition': [ {'condition': lambda: distance(npc, player) < 2, 'next_state': 'angry'}, {'condition': lambda: distance(npc, player) > 5, 'next_state': 'calm'}, ] }, 'angry': { 'action': chase_player, 'transition': [ {'condition': lambda: distance(npc, player) > 4, 'next_state': 'alert'}, ] } } npc.add_script(Behavior({'state_machine': states}))
Логика:
-
Спокойный (зеленый): игрок далеко → NPC стоит.
-
Настороженный (желтый): игрок приближается → NPC поворачивается к нему.
-
Агрессивный (красный): игрок слишком близко → погоня!
4. Продвинутые техники
4.1. Работа с памятью NPC
Чтобы NPC «запоминал» игрока даже когда тот скрылся:
npc.memory = {'last_seen_player_pos': None} def remember_player(): if distance(npc, player) < 3: npc.memory['last_seen_player_pos'] = player.position Behavior({ 'action': remember_player, 'sequence': [ {'condition': lambda: npc.memory['last_seen_player_pos'] is not None, 'action': lambda: npc.look_at(npc.memory['last_seen_player_pos'])}, {'action': patrol} ] })
4.2. Групповое поведение
Создаем стаю NPC, которая атакует вместе:
enemies = [Entity(model='cube', color=color.red) for _ in range(5)] for enemy in enemies: enemy.add_script(Behavior({ 'parallel': [ {'condition': lambda e=enemy: distance(e, player) < 4, 'action': chase_player}, {'action': lambda e=enemy: e.look_at_2d(player)} ] }))
5. Оптимизация производительности
-
Используйте distance_squared вместо distance для проверок (избегаем квадратного корня).
-
Ограничивайте частоту проверок через time.dt:
def update(): if time.time() % 1.0 < time.dt: # Проверяем раз в секунду npc.bt.update()
6. Что дальше?
-
Добавьте путьfinding через ursina.pathfinding для обхода препятствий.
-
Создайте диалоговую систему — NPC могут реагировать на слова игрока.
-
Экспериментируйте с нейросетями (подключите TensorFlow для обучения NPC).
Совет: Для сложных проектов используйте визуальный редактор поведенческих деревьев Behavior Tree Editor.
Итог: С Ursina даже сложный ИИ становится доступным. Начните с простых патрулей, а затем создавайте NPC с характером!
P.S. Попробуйте сделать NPC, который:
-
Прячется за укрытиями
-
Подбирает предметы
-
Общается с другими NPC
Какой вариант реализуете первым?
пример игры
from ursina import * import random app = Ursina() # Настройки игры player_speed = 5 npc_speed = 2.5 collectibles_count = 10 score = 0 # Создаем игрока player = Entity( model='cube', color=color.orange, scale=(1, 1, 1), collider='box' ) # Создаем землю ground = Entity( model='plane', texture='grass', scale=(20, 1, 20), collider='box' ) # Создаем NPC npc = Entity( model='cube', color=color.red, position=(5, 0, 5), collider='box' ) # Создаем собираемые предметы collectibles = [] for i in range(collectibles_count): collectible = Entity( model='sphere', color=color.yellow, scale=0.5, position=(random.uniform(-8, 8), 0.5, random.uniform(-8, 8)), collider='sphere' ) collectibles.append(collectible) # UI для счета score_text = Text(text=f'Score: {score}', position=(-0.8, 0.4), scale=2) # Логика ИИ NPC def update_npc(): if distance(npc, player) < 6: npc.look_at(player) npc.position += npc.forward * time.dt * npc_speed else: npc.rotation_y += 20 * time.dt npc.position += npc.forward * time.dt * (npc_speed / 2) # Камера с фиксированным углом camera.position = (0, 15, -20) # Высота и отдаление camera.rotation_x = 30 # Наклон камеры вниз def update(): global score # Движение игрока player.x += held_keys['d'] * time.dt * player_speed player.x -= held_keys['a'] * time.dt * player_speed player.z += held_keys['w'] * time.dt * player_speed player.z -= held_keys['s'] * time.dt * player_speed # Вращение игрока (убрали Q/E, чтобы камера не "срывалась") # Обновление ИИ NPC update_npc() # Камера следует за игроком с плавным смещением camera.position = ( player.x, 15, # Фиксированная высота player.z - 20 # Отдаление по Z ) camera.look_at(player) # Камера всегда направлена на игрока # Проверка сбора предметов for collectible in collectibles[:]: if player.intersects(collectible).hit: collectibles.remove(collectible) destroy(collectible) score += 1 score_text.text = f'Score: {score}' if random.random() > 0.3: new_collectible = Entity( model='sphere', color=color.yellow, scale=0.5, position=(random.uniform(-8, 8), 0.5, random.uniform(-8, 8)), collider='sphere' ) collectibles.append(new_collectible) if player.intersects(npc).hit: print("Game Over!") application.pause() app.run()
ссылка на оригинал статьи https://habr.com/ru/articles/898360/
Добавить комментарий