🔁 FSM (Finite State Machine)🔄
✅ FSM — это конечный автомат состояний.
Объект всегда находится в одном состоянии, может переходить в другие, и у каждого состояния есть логика входа, обновления и выхода.
FSM реализован классом :
🧠 Зачем нужен FSM
FSM идеально подходит для:
🎮 Игры
➖ Состояния игрока: Idle, Walk, Run, Jump, Attack
➖ Анимации персонажей
➖ Управление оружием
➖ Камеры (follow / free / cinematic)
🧩 Логика
➖ Меню (MainMenu → Settings → Game)
➖ Режимы игры
🧩 Базовая концепция FSM
Каждое состояние — это набор методов с именами по шаблону:
🔻enterState() — Вызывается при переходе в состояние
🔻exitState() — Вызывается при выходе из текущего состояния
🔻filterState(request, args) — Разрешить / запретить переход
❗️State — имя состояния
🎮 Управление переходами
Переходы между состояниями запрашиваются с помощью метода:
fsm.request('State')
🏗 Минимальный пример использования FSM + Actor
Объект всегда находится в одном состоянии, может переходить в другие, и у каждого состояния есть логика входа, обновления и выхода.
FSM реализован классом :
from direct.fsm.FSM import FSM
🧠 Зачем нужен FSM
FSM идеально подходит для:
🎮 Игры
🧩 Логика
🧩 Базовая концепция FSM
Каждое состояние — это набор методов с именами по шаблону:
🔻enterState() — Вызывается при переходе в состояние
🔻exitState() — Вызывается при выходе из текущего состояния
🔻filterState(request, args) — Разрешить / запретить переход
❗️State — имя состояния
Переходы между состояниями запрашиваются с помощью метода:
fsm.request('State')
🏗 Минимальный пример использования FSM + Actor
# =========================
# ИМПОРТЫ
# =========================
from ursina import * # основной движок Ursina
from direct.actor.Actor import Actor # Actor из Panda3D (анимации)
from direct.fsm.FSM import FSM # FSM (машина состояний)
from ursina.shaders import lit_with_shadows_shader
# =========================
# ИНИЦИАЛИЗАЦИЯ ПРИЛОЖЕНИЯ
# =========================
app = Ursina()
# Делаем шейдер с освещением и тенями шейдером по умолчанию
Entity.default_shader = lit_with_shadows_shader
# Источник направленного света (как солнце)
DirectionalLight(color=color.dark_gray, shadows=True)
# =========================
# СЦЕНА / ЗЕМЛЯ
# =========================
plane = Entity(
model="plane",
scale=100,
texture="brick",
collider="box"
)
plane.texture_scale = (10, 10)
# =========================
# РОДИТЕЛЬ ДЛЯ ИГРОКА И КАМЕРЫ
# =========================
# Это позволяет вращать персонажа и камеру вместе
parent_entity = Entity()
camera.parent = parent_entity
camera.position = (0, 2.5, 4.5)
camera.rotation_y = 180
# =========================
# НАСТРОЙКИ МЫШИ
# =========================
mouse.locked = True
mouse_sensitivity = Vec2(6, 40)
# =========================
# ЗАГРУЗКА АКТОРА (ИГРОКА)
# =========================
player = Actor(
"player.glb", # базовая модель
{
"idle": "idle.glb",
"walk": "walking.glb",
"run": "running.glb",
},
)
player.reparent_to(parent_entity)
# =========================
# SUBPARTS (РАЗДЕЛЕНИЕ ТЕЛА)
# =========================
# Ноги — для ходьбы / бега
player.makeSubpart(
"legs",
[
"mixamorig:LeftUpLeg",
"mixamorig:LeftLeg",
"mixamorig:LeftFoot",
"mixamorig:LeftToeBase",
"mixamorig:LeftToe_End",
"mixamorig:RightUpLeg",
"mixamorig:RightLeg",
"mixamorig:RightFoot",
"mixamorig:RightToeBase",
"mixamorig:RightToe_End",
],
)
# Торс — всё, кроме ног
player.makeSubpart(
"torso",
["mixamorig:Hips"], # корневая кость торса
[
"mixamorig:LeftUpLeg",
"mixamorig:LeftLeg",
"mixamorig:LeftFoot",
"mixamorig:LeftToeBase",
"mixamorig:LeftToe_End",
"mixamorig:RightUpLeg",
"mixamorig:RightLeg",
"mixamorig:RightFoot",
"mixamorig:RightToeBase",
"mixamorig:RightToe_End",
],
)
# =========================
# ПРИВЯЗКА ОРУЖИЯ К КОСТИ
# =========================
# Получаем кость позвоночника
ancorSpine = player.exposeJoint(
None,
"modelRoot",
"mixamorig:Spine2"
)
# Создаём оружие
gun = Entity(
model="scifi_gun.obj",
texture="gun_texture.jpg",
color=color.dark_gray,
scale=0.067,
collider="box",
)
# Привязываем оружие к кости
gun.world_parent = ancorSpine
gun.position = (-25, -10, 12)
gun.rotation = (280, -90, 0)
Please open Telegram to view this post
VIEW IN TELEGRAM
# =========================
# ФЛАГИ СОСТОЯНИЯ
# =========================
is_moving = False # игрок движется
is_running = False # игрок бежит
is_aiming = False # прицеливание (пока не используется)
has_gun = False # оружие в руках
# =================================================
# FSM — МАШИНА СОСТОЯНИЙ ИГРОКА
# =================================================
class PlayerFSM(FSM):
def __init__(self, player):
super().__init__("PlayerFSM")
self.player = player
# -------------------------
# IDLE
# -------------------------
def enterIdle(self): # метод enter позволяет добавить логику при входе в какое либо состояния
self.player.loop("idle")
# -------------------------
# WALK
# -------------------------
def enterWalk(self):
self.player.loop("walk")
def exitWalk(self):
pass # метод exit позволяет добавить логику при выходе из какого либо состояния
# -------------------------
# RUN
# -------------------------
def enterRun(self):
self.player.loop("run")
# Создаём FSM
fsm = PlayerFSM(player)
# Стартовое состояние
fsm.request("Idle")
# =========================
# UPDATE — КАЖДЫЙ КАДР
# =========================
def update():
global is_running
# Вращение персонажа мышью
parent_entity.rotation_y += mouse.velocity[0] * mouse_sensitivity[1]
# Скорость зависит от бега
speed = 3 if is_running else 1
# Движение вперёд
if is_moving:
parent_entity.position += parent_entity.back * speed * time.dt
# Фиксированный наклон камеры
camera.rotation_x = 10
# =========================
# INPUT — СОБЫТИЯ КЛАВИАТУРЫ
# =========================
def input(key):
global is_moving, is_running, is_aiming, has_gun
if key == "escape":
application.quit()
# Начало движения
if key == "w":
is_moving = True
fsm.request("Walk")
# Остановка
if key == "w up" and not has_gun:
is_moving = False
fsm.request("Idle")
# Начало бега
if key == "left shift" and is_moving:
is_running = True
fsm.request("Run")
# Конец бега
if key == "left shift up" and is_moving:
is_running = False
fsm.request("Walk")
# =========================
# ЗАПУСК
# =========================
if __name__ == "__main__":
app.run()
❤1
Подробную инфу по работе с Actor и подготовке моделей вы найдёте тут: https://stepik.org/course/250738
Stepik: online education
Библиотека Ursina Engine. Игры на Python. Основы. Подробный курс
Единственный и уникальный курс по созданию игр и интерактивных приложений на Python с использованием библиотеки(движка) Ursina Engine. Он идеально подходит как для начинающих разработчиков, так и для тех, кто хочет быстро прототипировать свои идеи или создавать…
light = DirectionalLight(color=color.dark_gray, shadows=True, position = (0,10, -50))
light.look_at(Vec3(1,-1, 2))
shadow_bounds_box = Entity(model='wireframe_cube', scale=1000, visible=0)
light.update_bounds(shadow_bounds_box)
🔻Разберём этот кусок по слоям
1️⃣ Создание направленного света
light = DirectionalLight(
color=color.dark_gray,
shadows=True,
position=(0, 10, -50)
)
✅Что такое DirectionalLight
Это солнце 🌞
Идеален для:
Параметры:
color=color.dark_gray тёмно-серый Не слепит сцену, даёт мягкие тени
shadows=True включено Обязательно, иначе теней не будет
position=(0,10,-50) точка в пространстве Используется для расчёта теней
⚠️ Для DirectionalLight позиция не влияет на яркость,
но очень важна для карты теней.
2️⃣ Направление света
light.look_at(Vec3(1, -1, 2))
Что происходит
❗️Почему это важно
❗️Если look_at неправильный:
3️⃣ Ограничительная рамка для теней
shadow_bounds_box = Entity(
model='wireframe_cube',
scale=1000,
visible=0
)
🔥 Это ключевой момент, который многие не понимают.
DirectionalLight НЕ ЗНАЕТ,
где в мире нужно считать тени.
👉 Ему нужно сказать:
«Вот область мира, где считаем тени»
📦 Это воображаемая коробка, внутри которой:
4️⃣ Привязка границ теней
light.update_bounds(shadow_bounds_box)
и только внутри этого объёма считаются тени.)
Проще:
📸 У света есть своя камера
📦 Эта камера видит только определённую коробку мира
🌑 Всё, что внутри — отбрасывает и получает тени
🚫 Всё, что снаружи — игнорируется
⚠️ Если НЕ делать update_bounds
Тени:
❌ исчезают
❌ обрезаются
❌ мигают при движении камеры
❌ работают только рядом с (0,0,0)
🎯 Итог: что делает весь код
🧠 Полезные советы
Please open Telegram to view this post
VIEW IN TELEGRAM
🔻Чтобы применить его сразу ко всем объектам в начале кода импортируем шейдер и прописываем:
Entity.default_shader = lit_with_shadows_shader
🔻Теперь тени будут у всех объектов, если не переопределить шейдер для какого либо объекта(ов) явно.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Друзья, всех приветствую 👋
В связи с нестабильной работой телеграм, вести полноценно канал пока не удается. Но по мере возможности посты будут появляться, так же вы можете задавать вопросы в чате и предлагать темы для новых постов.
Работа над курсом также продолжается. Новые темы курса: actor, машина состояний, и совсем скоро появиться AIBehavior и урок по сборке в exe.
Спасибо ,что остаетесь здесь и спасибо за поддержку!!😉
В связи с нестабильной работой телеграм, вести полноценно канал пока не удается. Но по мере возможности посты будут появляться, так же вы можете задавать вопросы в чате и предлагать темы для новых постов.
Работа над курсом также продолжается. Новые темы курса: actor, машина состояний, и совсем скоро появиться AIBehavior и урок по сборке в exe.
Спасибо ,что остаетесь здесь и спасибо за поддержку!!😉
❤2
🎮 AIBehaviors в Ursina Engine
Разберёмся, что это такое и как это реально работает 👇
--——————————————————————————————-
🧠 Что такое AIBehaviors?
Это система “поведенческого ИИ” .
Не путай с полноценным AI:
🔻 Это НЕ:
— сложная логика
— не поиск пути
👉 Это:
— набор сил, которые “толкают” объект в нужную сторону
--————————————————————————————————-
⚙️ Основные понятия
🔹 AICharacter
Обёртка над объектом, которая даёт ему “физику поведения”
(масса, ускорение, сила)
🔹 AIWorld
Контейнер, где живут все AI-объекты и препятствия
👉 именно здесь обновляется весь AI
🔹 AIBehaviors
Набор действий, которые можно включать:
— движение
— избегание
— блуждание
--——————————————————————————————————-
🚶 Базовые алгоритмы поведения
🔸 seek (преследование)
Двигается прямо к цели
👉 самый простой вариант врага
——
🔸 pursue (умное преследование)
Предсказывает, куда движется цель
👉 лучше работает против игрока
——
🔸 wander (блуждание)
Случайное движение
👉 патрули, “живой” мир
——
🚧 obstacleAvoidance (обход препятствий)
Звучит мощно, но:
👉 AI НЕ строит маршрут
👉 AI просто “отталкивается” от объектов
📌 Как работает:
🔻есть направление к цели
🔻 если впереди препятствие → добавляется сила в сторону
--———————————————————————————————————————
AIBehaviors — это:
👉 не интеллект, а математика движения
Поэтому:
— может застревать
— может дёргаться
— не умеет обходить сложные уровни
---
🔥 Когда использовать
✅ Быстрое прототипирование
✅ Простые враги
---
🚀 Как делают нормальный AI в Ursina
Вместо AIBehaviors:
• FSM (машина состояний)
• raycast (зрение)
• векторная логика движения
👉 Это даёт полный контроль
Разберёмся, что это такое и как это реально работает 👇
--——————————————————————————————-
🧠 Что такое AIBehaviors?
Это система “поведенческого ИИ” .
Не путай с полноценным AI:
🔻 Это НЕ:
— сложная логика
— не поиск пути
👉 Это:
— набор сил, которые “толкают” объект в нужную сторону
--————————————————————————————————-
⚙️ Основные понятия
🔹 AICharacter
Обёртка над объектом, которая даёт ему “физику поведения”
(масса, ускорение, сила)
🔹 AIWorld
Контейнер, где живут все AI-объекты и препятствия
👉 именно здесь обновляется весь AI
🔹 AIBehaviors
Набор действий, которые можно включать:
— движение
— избегание
— блуждание
--——————————————————————————————————-
🚶 Базовые алгоритмы поведения
🔸 seek (преследование)
Двигается прямо к цели
👉 самый простой вариант врага
——
🔸 pursue (умное преследование)
Предсказывает, куда движется цель
👉 лучше работает против игрока
——
🔸 wander (блуждание)
Случайное движение
👉 патрули, “живой” мир
——
🚧 obstacleAvoidance (обход препятствий)
Звучит мощно, но:
👉 AI НЕ строит маршрут
👉 AI просто “отталкивается” от объектов
📌 Как работает:
🔻есть направление к цели
🔻 если впереди препятствие → добавляется сила в сторону
--———————————————————————————————————————
AIBehaviors — это:
👉 не интеллект, а математика движения
Поэтому:
— может застревать
— может дёргаться
— не умеет обходить сложные уровни
---
🔥 Когда использовать
✅ Быстрое прототипирование
✅ Простые враги
---
🚀 Как делают нормальный AI в Ursina
Вместо AIBehaviors:
• FSM (машина состояний)
• raycast (зрение)
• векторная логика движения
👉 Это даёт полный контроль
👍2
🤖Пример применения AIBehaviors
Чтобы использовать поведенческие алгоритмы, необходимо сделать два обязательных импорта, кроме основного:
Теперь добавим в сцену поверхность и два куба, которые будут в качестве персонажей:
# Игрок
Создаём AIWorld:
render — это корневой узел всей 3D-сцены
Создаём AICharacter
Остановимся подробнее:
AICharacter(name, entity, mass, movement_force, max_force)
name - задаём имя, можно любое
entity - передаём объект, который будет ИИ-персонажем
Изменяя следующие параметры, мы можем тонко отрегулировать динамику и характер движения:
mass — масса
movement_force — сила движения
max_force — ограничение ускорения
Добавляем нашего персонажа в AIWorld
Получаем алгоритмы поведения для персонажа
Задаем поведение:
В функции update обязательно вызываем обновление AIWorld
В этом примере враг будет постоянно преследовать игрока👾
Чтобы использовать поведенческие алгоритмы, необходимо сделать два обязательных импорта, кроме основного:
from ursina import *
from panda3d.ai import AIWorld
from panda3d.ai import AICharacter
app = Ursina()
Теперь добавим в сцену поверхность и два куба, которые будут в качестве персонажей:
plane = Entity(model="plane", scale=100, texture="brick", collider="box")
plane.texture_scale = (10, 10)
# Игрок
player = Entity(model="cube", color=color.azure, position=(0, 0, 0), collider='box')
# Враг
enemy = Entity(model="cube", color=color.red, position=(15, 0, 15), collider='box')
Создаём AIWorld:
ai_world = AIWorld(app.render)
render — это корневой узел всей 3D-сцены
Создаём AICharacter
enemy_char = AICharacter("red_cube", enemy, 50, 1, 3)Остановимся подробнее:
AICharacter(name, entity, mass, movement_force, max_force)
name - задаём имя, можно любое
entity - передаём объект, который будет ИИ-персонажем
Изменяя следующие параметры, мы можем тонко отрегулировать динамику и характер движения:
mass — масса
movement_force — сила движения
max_force — ограничение ускорения
Добавляем нашего персонажа в AIWorld
ai_world.addAiChar(enemy_char)
Получаем алгоритмы поведения для персонажа
enemy_ai = enemy_char.getAiBehaviors()
Задаем поведение:
enemy_ai.pursue(player)
В функции update обязательно вызываем обновление AIWorld
def update():
ai_world.update()
EditorCamera()
app.run()
В этом примере враг будет постоянно преследовать игрока👾
👍2
Делюсь скринами из примера новой игры😉
На данный момент есть карта маленького городка и персонаж игрока, добавленного с помощью Actor. Есть пока что только одна механика стрельбы. Управление реализовано через FSM(Finite State Machine)
На данный момент есть карта маленького городка и персонаж игрока, добавленного с помощью Actor. Есть пока что только одна механика стрельбы. Управление реализовано через FSM(Finite State Machine)
❤3🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Нашел способ сделать «зеркало», пока есть нюансы, но я добью до приемлемого результата и всё покажу😉
🔥3
Ещё у меня есть идея редактора уровней написать, уже есть кое какие наброски.
Я когда-то давно публиковал пост на эту тему с примером, но сейчас я уже придумал интерфейс и удобное управление.
Как только доработаю, обязательно поделюсь 😊
Я когда-то давно публиковал пост на эту тему с примером, но сейчас я уже придумал интерфейс и удобное управление.
Как только доработаю, обязательно поделюсь 😊
👍4