Удобный сценарий при использовании ECS?

8

В настоящее время я создаю небольшой хобби-проект, чтобы вернуться к разработке игр, и я решил структурировать свои сущности, используя ECS (Entity Component System). Эта реализация ECS имеет следующую структуру:

  • Entity : В моем случае это уникальный intидентификатор, который используется в качестве ключа к списку компонентов.
  • Компонент : содержит только данные, например, Positionкомпонент содержит координаты and xи y, а Movementкомпонент содержит переменные a speedи direction.
  • Система : Рукоятки компоненты, например , он принимает Positionи Movementкомпоненты и добавляет speedи directionк позиции на xи yкоординаты.

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

function start()
    local future = entity:moveTo(pos1)
    wait(future)

    local response = entity:showDialog(dialog1)
    if wait(response) == 1 then
        local itemStack = entity:getInventory():removeItemByName("apple", 1)
        world:getPlayer():getInventory():addItemStack(itemStack)
    else
        entity:setBehavior(world:getPlayer(), BEHAVIOR_HOSTILE)
    end
end

Однако при использовании ECS сам объект не имеет никаких функций, таких как moveToили getInventory, вместо этого приведенный выше скрипт, написанный в стиле ECS, будет выглядеть примерно так:

 function start()
    local movement = world:getComponent(MOVEMENT, entity)
    movement:moveTo(pos1)

    local position = world:getComponent(POSITION, entity)
    local future = Future:untilEquals(position.pos, pos1)
    wait(future)

    local dialogComp = world:getComponent(DIALOG, entity)
    local response = dialogComp:showDialog(dialog1)

    if wait(response) == 1 then
        local entityInventory = world:getComponent(INVENTORY, entity)
        local playerInventory = world:getComponent(INVENTORY, world:getPlayer())
        local itemStack = entityInventory:removeItemByName("apple", 1)
        playerInventory:addItemStack(itemStack)
    else
        local entityBehavior = world:getComponent(BEHAVIOR, entity)
        local playerBehavior = world:getComponent(BEHAVIOR, world:getPlayer())
        entityBehavior:set(playerBehavior, BEHAVIOR_HOSTILE)
    end
end

Это намного более многословно по сравнению с версией ООП, что нежелательно, когда сценарии ориентированы в основном на непрограммистов (игроков в игре).

Одним из решений было бы иметь некоторый объект-обертку, который инкапсулирует Entityи предоставляет функции, такие как moveToнепосредственно, и обрабатывает все остальное внутри, хотя такое решение кажется неоптимальным, поскольку для охвата всех компонентов и каждого требуется много работы. При добавлении нового компонента вам потребуется заменить объект-оболочку новыми функциями.

Всем разработчикам игр, которые ранее использовали скрипты в ECS - как вы это сделали? Основное внимание здесь уделяется удобству использования для конечного пользователя с минимальными затратами на «обслуживание» (желательно, чтобы вам не приходилось менять его каждый раз, когда вы добавляете компонент).

Charanor
источник
Я напишу это как комментарий, потому что я немного расплывчат в вашей точной реализации (я предполагаю C ++). Не могли бы вы использовать шаблоны где-то здесь? Чтобы применить компонент X против компонента Y? Тогда я представляю, что компоненты должны были бы переопределить базовый метод apply и специализировать его для типов компонентов, которые могут быть применены к нему. Полагаясь на SFINAE, вы гарантируете, что он будет работать тогда, когда это нужно. Или он может быть специализирован для Systemклассов / классов, чтобы компоненты оставались структурами данных.
НеомерАркана
Почему бы не раскрыть moveToметод как часть базовой системы в вашем случае использования, например MovementSystem? Таким образом, вы можете не только использовать его в сценариях, которые вы пишете, но вы также можете использовать его как часть кода C ++ там, где вам это нужно. Так что да, вам придется раскрывать новые методы по мере добавления новых систем, но этого следует ожидать, так как эти системы в любом случае вводят совершенно новое поведение.
Нарос
У меня не было возможности сделать это, но возможно ли добавить «ярлыки» только к наиболее часто выполняемым операциям?
Vaillancourt

Ответы:

1

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

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

Philipp
источник
0

У ECS есть свои плюсы и минусы. Удобный сценарий не является его плюсом.

Проблема, которую решает ECS, - это возможность иметь в игре одновременно много похожих вещей, сохраняя при этом производительность. Но это решение имеет свою цену - стоимость простой в использовании архитектуры. Это не лучшая архитектура для каждой игры.

Например, ECS был бы хорошим выбором для Space Invaders , но не так много для PacMan .

Так что не совсем тот ответ, который вы искали, но вполне возможно, что ECS просто не подходит для вашей работы.

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


Но чтобы прямо ответить на ваш вопрос - «Всем разработчикам игр, которые ранее использовали скрипты в ECS - как вы это сделали?»

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

Однажды я использовал фреймворк с четвертым аспектом, который называется доской. Это был в основном способ для систем общаться друг с другом. Это создало больше проблем, чем решило.


Связанный: Должен ли я внедрить Entity Component System во все мои проекты?

Evorlor
источник
0

С ECS вы можете разделить одну ответственность, поэтому любой движущийся объект будет нуждаться в двух компонентах данных: MoveComponent и MoveSpeedComponent.

using System;
using Unity.Entities;

[Serializable]
public struct MoveForward : IComponentData{}
////////////////////////////////////////////
using System;
using Unity.Entities;

[Serializable]
public struct MoveSpeed : IComponentData
{
public float Value;
}
///////////////////////////////////////////

так что теперь в вашем преобразовании вы добавляете эти компоненты в свои объекты

public class MoveForwardConversion : MonoBehaviour, IConvertGameObjectToEntity
{
public float speed = 50f;

public void Convert(Entity entity, EntityManager manager,       GameObjectConversionSystem conversionSystem)
{
    manager.AddComponent(entity, typeof(MoveForward));

    MoveSpeed moveSpeed = new MoveSpeed { Value = speed };
    manager.AddComponentData(entity, moveSpeed);     
}

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

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;

namespace Unity.Transforms
{
public class MoveForwardSystem : JobComponentSystem
{
    [BurstCompile]
    [RequireComponentTag(typeof(MoveForward))]
    struct MoveForwardRotation : IJobForEach<Translation, MoveSpeed>
    {
        public float dt;

        public void Execute(ref Translation pos, [ReadOnly] ref MoveSpeed speed)
        {
            pos.Value = pos.Value + (dt * speed.Value);            
           // pos.Value.z += playerInput.Horizontal;
        }
    }
}

обратите внимание, что вышеприведенный класс использует Unity.Mathmatics. Это отлично подходит для возможности использовать различные математические функции, с которыми вы привыкли работать в обычных системах. Имея все это в соответствие, теперь вы можете работать с поведением сущностей - опять же, я удалил входные данные, но это гораздо лучше объясняется в статье.

using Unity.Entities;
using UnityEngine;

public class EntityBehaviour : MonoBehaviour, IConvertGameObjectToEntity
{
public float speed = 22f;

void Update()
{
    Vector3 movement = transform.forward * speed * Time.deltaTime;
}
public void Convert(Entity entity, EntityManager manager, GameObjectConversionSystem conversionSystem)
{   
    //set speed of the entity
    MoveSpeed moveSpeed = new MoveSpeed { Value = speed };
    //take horizontal inputs for entites
    //PlayerInput horizontalinput = new PlayerInput { Horizontal = Input.GetAxis("Horizontal") };

    //add move component to entity
    manager.AddComponent(entity, typeof(MoveForward));
    //add component data  
    manager.AddComponentData(entity, moveSpeed);
   // manager.AddComponentData(entity, horizontalinput);
}
}

Теперь вы можете вводить объекты, которые будут двигаться вперед со скоростью.

Но это также переместит каждую сущность с таким поведением, так что вы можете ввести теги, например, если вы добавили PlayerTag, тогда только сущность с playerTag IComponentData сможет выполнить MoveForward, если я только хочу переместить игрока, как в примере ниже.

Я углублюсь в это и в этой статье, но в типичной ComponentSystem это выглядит так

    Entities.WithAll<PlayerTag>().ForEach((ref Translation pos) =>
    {
        pos = new Translation { Value =  /*PlayerPosition*/ };
    });

Многое из этого объясняется довольно хорошо в презентации Angry Dots с Майком Гейгом, если вы еще не видели, я рекомендую проверить это. Я также укажу на свою статью после того, как она выйдет. Должно быть полезно получить некоторые из тех вещей, с которыми вы привыкли работать, работая так, как вам хотелось бы в ECS.

Джастин Марквелл
источник