¿Secuencias de comandos fáciles de usar cuando se usa un ECS?

8

Actualmente estoy creando un pequeño proyecto de pasatiempo para volver al desarrollo del juego, y he decidido estructurar mis entidades utilizando un ECS (Sistema de componentes de la entidad). Esta implementación de un ECS está estructurada así:

  • Entidad : en mi caso, es un intidentificador único que se utiliza como clave para una lista de componentes.
  • Componente : Contiene solo datos, por ejemplo, el Positioncomponente contiene una xy ycoordenada, y el Movementcomponente contiene una speedy directionvariable.
  • Sistema : componente maneja, por ejemplo, las que se necesita Positiony Movementcomponentes y añade la speedy directionen la posición de xy ycoordenadas.

Esto funciona bien, pero ahora deseo implementar secuencias de comandos en mis juegos, en forma de lenguaje de secuencias de comandos. En proyectos anteriores, he usado una implementación OOP de objetos de juego, lo que significaba que las secuencias de comandos eran bastante sencillas. Por ejemplo, un script simple podría verse así:

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

Sin embargo, cuando se usa un ECS, la entidad en sí no tiene funciones como moveToo getInventory, en cambio, el script anterior escrito en estilo ECS se vería así:

 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

Esto es mucho más detallado en comparación con la versión OOP, lo que no es deseable cuando la secuencia de comandos está dirigida principalmente a no programadores (jugadores del juego).

Una solución sería tener algún tipo de objeto contenedor que encapsule Entityy suministre funciones como moveTodirectamente, y maneje el resto internamente, aunque tal solución parece subóptima ya que toma mucho trabajo cubrir todos los componentes, y cada cada vez que se agrega un nuevo componente, deberá cambiar el objeto contenedor con nuevas funciones.

Para todos los desarrolladores de juegos que han implementado scripts en un ECS anteriormente, ¿cómo lo hicieron? El enfoque principal aquí es la usabilidad para el usuario final, con el menor costo de "mantenimiento" posible (preferiblemente no necesita cambiarlo cada vez que agrega un componente).

Charanor
fuente
Escribiré esto como un comentario porque soy un poco vago en su implementación exacta (supongo que C ++). ¿No podrías utilizar plantillas en algún lugar aquí? Para aplicar el componente X contra el componente Y? Entonces imagino que los componentes tendrían que anular el método base "aplicar" y especializarlo para los tipos de componentes que se pueden aplicar a él. Confiar en SFINAE aseguraría que funcione cuando debería hacerlo. O podría estar especializado en la System/ s clase / s para permitir que los componentes sigan siendo estructuras de datos.
NeomerArcana
¿Por qué no exponer el moveTométodo como parte del sistema subyacente en su caso de uso, por ejemplo, MoveSystem? De esta manera, no solo puede usarlo en los scripts que escribe, sino que también puede usarlo como parte del código de C ++ donde lo necesite. Entonces sí, tendrá que exponer nuevos métodos a medida que se agreguen nuevos sistemas, pero eso es de esperar ya que su comportamiento completamente nuevo estos sistemas introducen de todos modos.
Naros
No he tenido la oportunidad de hacerlo, pero ¿sería factible agregar "accesos directos" solo a las operaciones más comunes?
Vaillancourt

Respuestas:

1

Puede crear un sistema ScriptExecutionSystem que funcione en todas las entidades con un componente Script. Obtiene todos los componentes de la entidad que podrían ser útiles para exponer al sistema de secuencias de comandos y los pasa a la función de secuencias de comandos.

Otro enfoque sería lograr que sus usuarios también adopten ECS y les permitan definir sus propios componentes e implementar sus propios sistemas utilizando el lenguaje de secuencias de comandos.

Philipp
fuente
0

ECS tiene sus pros y sus contras. Los scripts fáciles de usar no son una de sus ventajas.

El problema que resuelve ECS es la capacidad de tener una gran cantidad de cosas similares en tu juego al mismo tiempo mientras se mantiene el rendimiento. Pero esta solución tiene un costo: el costo de una arquitectura fácil de usar. No es la mejor arquitectura para cada juego.

Por ejemplo, ECS habría sido una buena opción para Space Invaders , pero no tanto para PacMan .

Entonces, no es exactamente la respuesta que estaba buscando, pero es posible que ECS no sea la herramienta adecuada para su trabajo.

Si agrega una envoltura, observe el costo general. Si terminas eliminando el aumento de rendimiento de ECS en tu contenedor, entonces tienes lo peor de ambos mundos.


Pero para responder directamente a su pregunta: "Para todos los desarrolladores de juegos que han implementado secuencias de comandos en un ECS anteriormente, ¿cómo lo hicieron?"

Casi exactamente como lo estás haciendo, sin envoltorio. Las entidades no tienen más que un identificador. Los componentes no tienen más que datos. Los sistemas no tienen nada más que lógica. Los sistemas que aceptan entidades con los componentes necesarios se ejecutan. Agregue sistemas, entidades y componentes libremente.

Una vez utilicé un marco con un cuarto aspecto, llamado pizarra. Básicamente era una forma en que los sistemas se comunicaban entre sí. Creó más problemas de los que resolvió.


Relacionado: ¿Debería implementar Entity Component System en todos mis proyectos?

Evorlor
fuente
0

Con ECS puede dividirse en una única responsabilidad, por lo que cualquier entidad que se mueva querría dos componentes de datos: un MoveComponent y un MoveSpeedComponent.

using System;
using Unity.Entities;

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

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

así que ahora en su conversión agrega esos componentes a sus entidades

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);     
}

Ahora que tenemos la conversión y los datos que podemos trasladar al sistema, eliminé el sistema de entrada para facilitar la lectura, pero si desea obtener más información sobre el sistema de entrada, lo tendré todo en mi artículo la próxima semana sobre la unidad de conexión.

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;
        }
    }
}

Tenga en cuenta que la clase anterior está utilizando Unity.Mathmatics. Esto es excelente para poder utilizar diferentes funciones matemáticas con las que está acostumbrado a trabajar en los sistemas normales. Con todo esto en línea, ahora puede trabajar en el comportamiento de las entidades, nuevamente eliminé la entrada aquí, pero todo esto se explica mucho mejor en el artículo.

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);
}
}

Ahora puede introducir entidades que avanzarán a gran velocidad.

Pero también esto moverá a todas las entidades con este comportamiento para que pueda introducir etiquetas, por ejemplo, si agregó un PlayerTag, entonces solo la entidad con el playerTag IComponentData podrá realizar MoveForward si solo quiero mover el reproductor como el ejemplo abajo.

Voy a profundizar en eso también en el artículo, pero se ve así en un ComponentSystem típico

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

Gran parte de esto se explica bastante bien en la presentación de Angry Dots con Mike Geig, si aún no lo ha visto, le recomiendo echarle un vistazo. Señalaré mi artículo también después de que esté listo. Realmente debería ser útil obtener varias de esas cosas con las que está acostumbrado a trabajar de la manera que le gustaría en ECS.

Justin Markwell
fuente