Skip to content
Dimitry edited this page Nov 3, 2018 · 18 revisions

Акторы

Акторы служат мостом между фреймворком и физическим объектом в Unity. Задача актора визуально настроить базовые компоненты сущности, инициализировать сущность. При уничтожении игрового объекта с актором уничтожается и сущность.

Как создавать.

Акторы наследуются от класса Actor или MonoActor. MonoActor отличается возможностью добавления апдейтов и сигналов.

 // basic melee unit
    public class ActorEnemy : Actor
    {

        // Атрибут FoldoutGroup("ИМЯ") - нарисует компоненты внутри сворачиваемой группы с заданным именем.
        [FoldoutGroup("Setup")] public ComponentDamageble componentDamageble;
        [FoldoutGroup("Setup")] public ComponentAiBasic componentAIBasic;  
        [FoldoutGroup("Setup")] public ComponentRigidBody componentRigidBody;
        [FoldoutGroup("Setup")] public ComponentBounds componentBounds;
        [FoldoutGroup("Setup")] public ComponentDepth componentDepth;
        [FoldoutGroup("Setup")] public ComponentShadow componentShadow;
        [FoldoutGroup("Setup")] public ComponentView componentView;
        [FoldoutGroup("Setup")] public ComponentAnimationMap componentAnimationMap;
        [FoldoutGroup("Setup")] public ComponentHealth componentHealth;


        // метод setup служит инициализацией, он отыгрывается после Awake. Именно через сетап мы передаем компоненты сущности.
        protected override void Setup()
        {
     
            // Используем Add чтобы передать компонент сущности, не поощряется, но компонент может быть унаследован от
            //другого компонента и в качестве типа может быть указан базовый тип ( родитель ). 
            Add(componentAIBasic as ComponentAI);
            Add(componentShadow);
            Add(componentView);
            Add(componentBounds);
            Add(componentDepth);
            Add(componentRigidBody);
            Add(componentDamageble);
            Add(componentHealth);
            // Сгененирует новый компнент при добавлении по указанному типу.
            Add<ComponentTime>();
            Add<ComponentObjectBounds>();
            // Добавить тэги можно через запятую.
            Add(Tag.GroupEnemy, Tag.GroupDestructable);
        }
}

Как это обычно выглядит:

Настройка актора

  • Вместе с актором генерируется entity - сущность. Ее индекс можно найти во вкладке Actor.
  • Актор неявно добавляет компонент ComponentObject отвечающий за физический объект ( GameObject, Transform ). Это происходит перед методом Setup. Не все сущности должны иметь ComponentObject, но все сущности создаваемые от актора будут обладать ComponentObject.
  • Акторы не предназначены для написания игровой логики, хотя и не содержат строгого запрета на это. Задача актора визуально представить сущность в инспекторе и дать возможность настроить компоненты при инициализации. В 95 % случаев компоненты и метод Setup - единственное, что будет в вашем акторе.
  • В Акторах может содержаться дополнительная логика по настройкам компонентов, игре, обработки событий связанных с анимациями если необходимо и нет явной причины отделить это в отдельный моноскрипт.

Как уничтожать Акторов

            // нашли актора. Внимание, это просто пример. Вам вряд ли такое понадобится.
            Actor a = FindObjectOfType<ActorPlayer>();
            a.Release();

Но так как по большей части мы имеем дело с сущностями и не обращаемся к акторам напрямую то удалить объект можно обратившись к методу через сущность.

    // обращаемся к первой сущности на сцене
            var entity = 0;
            entity.Release();

Внимание - код Release ниже актуален до версии 2018.10.27, после этой версии достаточно использовать метод выше.

   var entity = 1;
   // если мы знаем, что сущность это актор, то обязательно передаем true. 
   entity.Release(true); 

На заметку

Любое логические действие уничтожения и удаления в фреймворке идет через методы Release(), хотя сейчас еще можно встретить Kill(), Destroy(), с каждым апдейтом все приводится к методам Release(); - если видите этот метод то он 100% связан с уничтожением.

Что происходит когда Актор уничтожен?

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

Например монстр является десятой сущностью. Мы убили монстра, а следующим действием выстрелили в другого противника. С большой вероятностью летящая пуля станет новой десятой сущностью.

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

После версии 2018.10.27 уничтожение является отложенным на конец кадра. Активировав метод Release сущность помечается на уничтожение. После работы всех обработчиков и групп срабатывает уничтожение. Это исключает какое либо нарушение работы циклов.

В чем разница между деактивацией и уничтожением актора?

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

Пулы

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

Пулы

В фреймворке по умолчанию предусмотрено несколько пулов и их можно настроить в разделе MONO у актора. По умолчанию используется Pool.None.

При вызове метода Release будет совершена проверка нужно ли удалять объект полностью или его следует деактивировать и разместить в пул. При Pool.None объект будет полностью уничтожен.

При активации объекта из пула можно вызвать дополнительный метод для совершения настроек: OnSpawned()

ActorTest : Actor{
 protected override void OnSpawned(){}
}

Кеширование

Иногда хочется быстро и удобно обращаться к внутренним transform объектам без сложного поиска. Для этого можно сделать некий класс где вы будете хранить константы путей. Я обычно делаю так:

public static class Path
{
    public const int ToHitNormal = 0;
    public const int ToHitCritical = 1;
    public const int ToSolid = 2;
    public const int ToShadow = 3;
    public const int ToCollider = 4;
    public const int ToView = 5;
    public const int ToMuzzle = 6;
    public const int ToHitSpot = 7;
}

Этот концепт очень похож на систему с тэгами.

Дальше во время сетапа вы просто указываете тэг пути и сам путь.

    public class ActorEnemy : Actor
    {
  protected override void Setup()
        {

     // первым аргументом указываем тэг. Вторым внутренний путь до трансформа.
                  Add(Path.ToHitSpot, "View/Point Hit");
                  Add(Path.ToHitNormal, "View/Collider Hit");
                  Add(Path.ToHitCritical, "View/Collider Hit Critical");
                  Add(Path.ToSolid, "View/Collider Solid");
                  Add(Path.ToView, "View");

                  Add<ComponentTime>();
             
                  // получаем компонент по пути.
                  entity.Get<BoxCollider2D>(Path.ToSolid);
                 
        }
    }

* Кеши хранятся в ComponentObject, так как актор автоматически добавляет ComponentObject, то на момент срабатывания ADD актор уже знает куда передать кеш. 
* Старайтесь сохранять кеши первыми. Если вы где-то дальше будете обращаться к ним во время настроек это поможет избежать ошибок инициализации.
* Кеши необязательная концепция. У юнити хорошо оптимизирован поиск компонентов и хотя получение кеша неимоверно быстрее не стоит использовать их если вам некомфортно или вы делаете это редко.