Keep your place in this quest

Log in or sign up for free to subscribe, follow lesson progress, and access more learning content.

Теперь, когда вы увидели, как работают персонажи и анимация в Cave, давайте сделаем шаг назад и разберёмся с системой скриптов, которая обычно связывает всё вместе: Python.

Python используется в Cave для написания поведения игрового процесса, инструментов редактора, обратных вызовов UI, обратных вызовов анимации, событий временной шкалы и небольших кусочков пользовательской логики, которые слишком специфичны, чтобы существовать как встроенный компонент. Вам не нужно становиться продвинутым программистом на Python перед использованием Cave, но очень полезно понимать базовую структуру скрипта Cave.

В этом уроке вы узнаете:

  • Для чего используется Python в Cave.
  • Как работают активы Python Script и Python Components.
  • Что делают start() и update().
  • Как получить доступ к текущей сущности из скрипта.
  • Как получить компоненты из сущности.
  • Когда использовать Python Component или Python Code Component.

Цель не в том, чтобы обучить всему языку Python. Цель — сделать так, чтобы скрипты Cave были понятны, когда вы их открываете.

Где учить Python?

Если вы не умеете программировать на Python, Uniday Studio на самом деле предлагает бесплатные учебные квесты по Python. Перейдите на uniday.studio/learn, чтобы увидеть все варианты, или начните здесь:

Для этого раздела мы предполагаем, что вы уже знакомы с темами из этих двух учебных квестов.


Для чего используется Python в Cave

В Cave Python — это слой скриптов, который вы используете, когда игровому объекту требуется поведение.

Например, Python может быть использован, чтобы:

  • Двигать игрока или врага.
  • Открывать дверь.
  • Проигрывать анимацию.
  • Воспроизводить звук.
  • Запускать временную шкалу.
  • Менять сцены.
  • Обновлять элемент пользовательского интерфейса.
  • Создавать пользовательские инструменты редактора.

Фактически, вы можете писать Python Scripts для создания всей логики вашей игры.

Именно поэтому стартовый проект уже содержит скрипты на Python. Например, стандартный игрок — это не просто модель с анимацией. У него также есть логика игрового процесса, которая считывает ввод, двигает персонажа, поворачивает модель и проигрывает нужную анимацию.

Таким образом, когда вы пишете Python в Cave, вы обычно не создаёте изолированный код. Вы пишете логику, которая управляет сущностью и взаимодействует с компонентами, присоединёнными к этой сущности.

Активы скриптов и Python Components

Код на Python обычно хранится внутри актива Python Script.

image.png

Затем, чтобы запустить этот скрипт в сцене, вы добавляете Python Component к сущности и выбираете класс из скрипта, который должен выполняться.

Представьте это так:

Часть Что она делает
Актив Python Script Хранит код.
Python Component Запускает класс из этого скрипта на сущности.
Сущность Объект, которым управляет скрипт.
Класс cave.Component Фактическое поведение, которое вы написали.

Например, у вас может быть актив скрипта под названием Door Controller. Внутри него может быть класс DoorController, который наследуется от cave.Component. Затем вы добавляете Python Component к сущности двери и выбираете этот класс.

Такое разделение важно, потому что один и тот же скрипт можно использовать повторно. Вы можете разместить много дверей в сцене, каждая из которых использует один и тот же скрипт управления дверью, но с разными свойствами или разными дочерними объектами.

Минимальный компонент Cave

Простой Python-компонент Cave выглядит так:

import cave

class MyComponent(cave.Component):
    def start(self, scene):
        print("Компонент запущен!")

    def update(self):
        pass

Вот несколько важных деталей:

  • import cave даёт вашему скрипту доступ к Python API Cave.
  • class MyComponent(cave.Component) создаёт класс компонента, который может запустить Cave.
  • start(self, scene) вызывается при старте компонента.
  • update(self) вызывается каждый кадр, пока компонент активен.

Имя MyComponent может быть любым, но в реальных проектах лучше использовать понятные имена, например DoorController, EnemyAI, Checkpoint или PlayerHealth.

Методы жизненного цикла

Cave автоматически вызывает некоторые методы, когда ваш компонент работает.

Самые распространённые:

Метод Когда вызывается Типичное использование
start(self, scene) При запуске компонента. Получение ссылок, считывание свойств, подготовка переменных.
firstUpdate(self) Всегда после всех start() компонентов сущности, в первый обновляющий кадр. Создание переменных, зависящих от инициализации других компонентов.
update(self) Каждый кадр, если сцена не на паузе. Движение, ввод, таймеры, проверки состояний.
pausedUpdate(self) Каждый кадр, если сцена на паузе. Логика паузы.
end(self, scene) При завершении компонента. Очистка при необходимости.

Большинство скриптов для начинающих используют start() и update(). Например, при создании движущейся платформы start() — хорошее место для хранения исходной позиции, а update() — для движения платформы в каждом кадре.

Также есть методы editorUpdate и lateUpdate, но мы их здесь не рассматриваем, так как они более продвинуты.

Доступ к текущей сущности

Внутри компонента Cave self.entity — это сущность, которой принадлежит данный Python Component.

Это одна из самых важных идей в скриптинге Cave. Скрипт не существует сам по себе в сцене. Он принадлежит сущности.

Вот простой пример:

import cave

class DoorController(cave.Component):
    def start(self, scene):
        self.transform = self.entity.getTransform()
        self.isOpen = False

    def update(self):
        pass

В этом скрипте:

  • self.entity — это сущность двери.
  • self.entity.getTransform() получает компонент Transform двери.
  • self.isOpen — переменная, используемая скриптом для запоминания, открыта дверь или нет.

Вы будете использовать такую схему постоянно. Сначала получаете сущность, затем необходимые компоненты или дочерние сущности, а затем используете их в логике.

Получение других компонентов

Чтобы управлять сущностью, обычно нужно получить один или несколько её компонентов.

Например, скрипт игрока может получить:

  • Компонент Transform для перемещения или поворота сущности.
  • Компонент Character для управления движением персонажа.
  • Компонент Animation дочерней сущности с мешем для проигрывания анимаций.
  • Компонент Audio для воспроизведения цикличного звука.

Небольшой пример:

import cave

class SimpleMover(cave.Component):
    def start(self, scene):
        self.transform = self.entity.getTransform()
        self.speed = 2.0

    def update(self):
        self.transform.move(0, 0, self.speed * cave.getDeltaTime(), local=True)

Этот скрипт сдвигает сущность вперёд в каждом кадре.

Важная часть — cave.getDeltaTime(). Поскольку update() вызывается каждый кадр, умножение движения на время между кадрами обеспечивает одинаковую скорость движения независимо от частоты кадров.

Чтение пользовательских свойств

Жёстко закодированные значения подходят для первых тестов, но для реальных игровых объектов лучше использовать редактируемые свойства.

Например, вместо записи:

self.speed = 2.0

Можно прочитать значение из свойств сущности:

self.speed = self.entity.properties.get("speed", 2.0)

Это значит:

  • Если у сущности есть свойство speed, использовать его.
  • Если нет — взять значение по умолчанию 2.0.

Это очень полезно для повторно используемых скриптов. Вы можете создать один скрипт SimpleMover и использовать его для нескольких сущностей с разной скоростью.

Пример:

Сущность Свойство speed
Медленная платформа 1.0
Быстрая платформа 4.0
Двигающаяся опасность 7.0

Скрипт остаётся тот же, но поведение меняется в зависимости от сущности.

Альтернативно, вы можете создавать локально изменяемые переменные для самого компонента, а не полагаться на свойства сущности. Когда вы создаёте переменную так:

import cave

class PlatformMover(cave.Component):
    # Это будет локальная переменная:
    speed = 2.0

    def start(self, scene: cave.Scene):
        pass

    def update(self):
        events = cave.getEvents()

Переменная speed будет локально изменяемой для каждого экземпляра компонента:

image.png


Получение дочерних сущностей

Многие объекты в Cave построены в виде небольших иерархий.

Шаблон игрока — хороший пример. Корневая сущность Player содержит физику и логику персонажа, в то время как дочерняя сущность Mesh содержит визуальную модель персонажа и компонент Animation.

Если скрипт на корневой сущности игрока хочет проигрывать анимацию, он сначала получает дочернюю сущность меша:

import cave

class PlayerAnimationExample(cave.Component):
    def start(self, scene):
        self.mesh = self.entity.getChild("Mesh")
        self.animator = self.mesh.get("Animation")

    def update(self):
        self.animator.playByName("p-idle", blend=0.2, loop=True)

Это тот же паттерн, что используется в контроллере игрока по умолчанию:

  1. Получить дочернюю сущность.
  2. Получить компонент у этой дочерней сущности.
  3. Использовать компонент при необходимости.

Поняв этот паттерн, многие скрипты Cave становятся намного легче для понимания.

Метод getChild имеет необязательный параметр "recursive" с значением по умолчанию True. Если True, он будет искать во всех дочерних сущностях, включая потомков потомков, пока не найдёт сущность с нужным именем.

Получение сущностей сцены (запросы сцены)

При создании игры вам часто понадобится получить другие сущности в сцене. Давайте рассмотрим это. Первый шаг — получить саму сцену, и в Cave это можно сделать двумя способами:

# Возвращает активную сцену:
scene = cave.getScene()

# Возвращает сцену, к которой принадлежит эта сущность:
scene = self.entity.getScene()

Для удобства, методы start и end класса cave.Component также получают сцену как параметр, так как очень вероятно, что вы будете использовать её в этих методах. Для локального кода, например, Python Code Component, также по умолчанию определена переменная scene, которую можно просто "взять и использовать".

Получив сцену, вы можете получить конкретную Entity по её имени с помощью следующего кода:

watchtower = scene.get("Watch Tower 01")

Cave предоставляет множество других методов в классе сцены для выполнения различных запросов к сцене. Вы можете выполнять ray casts, sphere casts, проверять контактные области (коробки или сферы) для обнаружения столкновений, а также получать все сущности, все корневые сущности, все сущности с определённым тегом, свойством или именем и многое другое. Поэтому стоит изучить Python API для подробностей.

Всегда проверяйте, вернул ли ваш запрос что-то валидное. Например:

# Запрос несуществующей сущности:
ent = scene.get("This Entity Doesnt Exist")

if ent is None:
    print("Invalid Entity!")

Получение компонентов Entity

После того как у вас есть конкретная сущность и вы убедились, что она существует, полезно понять, как получить её компоненты.

У сущности есть специальный метод get, в который можно передать имя компонента в виде строки, и он автоматически найдёт и вернёт компонент, если он присутствует:

animator = self.entity.get("Animation Component")

Чтобы упростить жизнь, если имя компонента оканчивается на слово "Component", что очень часто встречается, можно полностью опустить это окончание при вводе названия компонента в этом методе. Также можно выбирать, включать ли пробелы в составных именах или писать всё слитно.

Например, чтобы получить Rigid Body Component у сущности, хотя его Python-имя RigidBodyComponent, подойдут все варианты ниже:

rb = self.entity.get("Rigid Body")
rb = self.entity.get("RigidBody")
rb = self.entity.get("Rigid Body Component")
rb = self.entity.get("RigidBodyComponent")
rb = self.entity.get("RigidBody Component")

Поскольку Python не сильно типизирован, в Cave является хорошей практикой указывать type hint для типа компонента, что можно сделать так. Обратите внимание, что в этом случае нужно указать полное имя компонента, так как это связано с семантикой Python:

rb : cave.RigidBodyComponent = self.entity.get("Rigid Body")

Это позволяет IntelliSense работать в внешних редакторах, таких как Visual Studio Code, или в встроенной системе автозаполнения, которую Cave Engine предоставляет в редакторе скриптов.

Для Transform Component, поскольку он является одним из самых распространённых типов компонентов и запрашивается часто, у Entity есть нативный метод для получения основного Transform:

transf = self.entity.getTransform()

Вызов self.entity.getTransform() даёт тот же результат, что и self.entity.get("Transform"), но первый вариант быстрее и оптимизированнее.

Если у сущности несколько компонентов одинакового типа, метод get вернёт первый найденный, но иногда требуется получить все совпадения. Например, у много-материальной меш-сущности, представленной в Cave сущностью с несколькими mesh-компонентами, вы можете захотеть получить все mesh-компоненты. Для этого используется метод getAll:

meshCmps = self.entity.getAll("Mesh")

# Изменяем все материалы на светящийся:
for meshCmp in meshCmps:
    meshCmp.material.setAsset("Glowing Material")

Получение Python-компонентов Entity

Теперь мы знаем, как получать нативные компоненты Cave, но что если нужно получить компонент, написанный вами на Python?

Для этого существует специальный метод getPy, который работает так же, как обычный get, но дополнительно возвращает Python-компоненты:

myCmp = self.entity.getPy("MyCustomComponent")

Он требует отдельного метода из-за внутренних оптимизаций, чтобы Cave работал максимально быстро.

Получив ваш кастомный Python-компонент, вы можете свободно обращаться к его переменным и методам на Python:

myCmp = self.entity.getPy("MyCustomComponent")

# Изменяем python-переменные:
myCmp.customValue = 10

# Вызываем кастомные методы:
myCmp.doSomething()
myCmp.applyDamage(10)

Python Code Component

Кроме обычных Python Component, в Cave есть Python Code Component. Этот компонент полезен для быстрых скриптов, написанных прямо внутри сущности, без создания отдельного Python Script asset.

У него есть все методы, и отличие в том, что в Python Code Component вы пишете скрипт непосредственно внутри компонента, он не модульный и не переиспользуемый. Если вы скопируете сущность, вместе с ней копируется и скрипт. Иногда это более быстрый способ создать простую логику, например вращение монетки.

Он хорошо подходит для:

  • Быстрых тестов.
  • Маленьких коллбеков.
  • Одноразовых поведений.
  • Прототипов.

Для больших игровых систем это не лучший вариант, так как код плохо структурируется и переиспользуется, но для маленьких фрагментов он оптимален.

Python Component vs Python Code Component

Используйте это как простое правило:

Использовать Когда
Python Component Поведение должно переиспользоваться, редактироваться как asset или развиваться со временем.
Python Code Component Поведение небольшое, локальное.

Например, переиспользуемый EnemyAI — это Python Script asset для Python Component. Небольшой код, который что-то печатает или вызывает функцию при нажатии кнопки — Python Code Component.

Хорошая первая цель для скрипта

Хороший первый скрипт Cave — это что-то небольшое и заметное.

Попробуйте сделать одно из этих:

  • Платформу, которая движется вперёд.
  • Дверь, которая открывается при старте сцены.
  • Свет, который включается и выключается через несколько секунд.
  • Подбор предмета, который проигрывает звук и отключается.
  • Кнопку, меняющую сцену.

Эти примеры минимальны, но они обучают самому важному рабочему процессу: получить сущность, получить компонент, изменить что-то, протестировать в режиме Play и подстроить под себя.

Что нужно запомнить

Python-скрипты в Cave обычно прикрепляются к сущностям через Python Components.

Самая важная базовая схема:

  1. Используйте start() для получения ссылок и подготовки значений.
  2. Используйте update() для выполнения поведения каждый кадр.
  3. Используйте self.entity для доступа к сущности, которой принадлежит скрипт.
  4. Используйте компоненты для движения, анимации, воспроизведения звука или управления объектом.

Когда эта схема станет понятной, программирование в Cave перестанет быть загадкой. Вы не просто пишете код — вы учите сущности правильно вести себя.