Scripting intuitivo quando si utilizza un ECS?


8

Attualmente sto creando un piccolo progetto di hobby per tornare allo sviluppo del gioco e ho deciso di strutturare le mie entità utilizzando un ECS (Entity Component System). Questa implementazione di un ECS è strutturata in questo modo:

  • Entità : nel mio caso è un intidentificatore univoco utilizzato come chiave per un elenco di componenti.
  • Componente : contiene solo dati, ad es. Il Positioncomponente contiene una xe ycoordinate e il Movementcomponente contiene una speede una directionvariabile.
  • Sistema : componenti Maniglie, ad esempio riprende le Positione Movementcomponenti e aggiunge la speede directionalla posizione del xe ycoordinate.

Funziona bene, ma ora desidero implementare lo scripting nei miei giochi, sotto forma di un linguaggio di scripting. In progetti precedenti ho usato un'implementazione OOP di oggetti di gioco, il che significava che lo scripting era piuttosto diretto. Ad esempio, un semplice script potrebbe assomigliare a questo:

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

Tuttavia, quando si utilizza un ECS, l'entità stessa non ha alcuna funzione simile moveToo getInventory, invece lo script sopra scritto in stile ECS sarebbe simile a questo:

 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

Questo è molto più dettagliato rispetto alla versione OOP, il che non è auspicabile quando lo scripting è rivolto principalmente a non programmatori (giocatori del gioco).

Una soluzione sarebbe quella di avere una sorta di oggetto wrapper che incapsula Entitye fornisce funzioni come moveTodirettamente e gestisca il resto internamente, sebbene tale soluzione sembri non ottimale poiché ci vuole molto lavoro per coprire tutti i componenti, e ogni ogni volta che viene aggiunto un nuovo componente, è necessario modificare l'oggetto wrapper con nuove funzioni.

A tutti gli sviluppatori di giochi che hanno già implementato lo scripting in un ECS: come hai fatto? L'obiettivo principale qui è l'usabilità per l'utente finale, con il minor costo di "manutenzione" possibile (preferibilmente non è necessario cambiarlo ogni volta che si aggiunge un componente).


Scriverò questo come un commento perché sono un po 'vago sulla tua esatta implementazione (presumo C ++). Non potresti utilizzare modelli da qualche parte qui? Per applicare il componente X al componente Y? Immagino quindi che i componenti dovrebbero sovrascrivere il metodo "applica" di base e specializzarlo per i tipi di componenti che possono essere applicati ad esso. Affidarsi a SFINAE assicurerebbe che funzioni quando è previsto. Oppure potrebbe essere specializzato nelle Systemclassi per consentire ai componenti di rimanere strutture di dati.
NeomerArcana,

Perché non esporre il moveTometodo come parte del sistema sottostante nel tuo caso d'uso, ad esempio MovementSystem? In questo modo non solo puoi usarlo negli script che scrivi ma puoi anche usarlo come parte del codice C ++ dove ne hai bisogno. Quindi sì, dovrete esporre nuovi metodi man mano che vengono aggiunti nuovi sistemi, ma ci si deve aspettare dal suo comportamento completamente nuovo introdotto da questi sistemi.
Naros,

Non ho avuto la possibilità di farlo, ma sarebbe possibile aggiungere "scorciatoie" solo a quelle operazioni più comunemente eseguite?
Vaillancourt

Risposte:


1

È possibile creare un sistema ScriptExecutionSystem che opera su tutte le entità con un componente Script. Ottiene tutti i componenti dell'entità che potrebbero essere utili per essere esposti al sistema di scripting e li passa alla funzione di script.

Un altro approccio potrebbe essere quello di convincere i tuoi utenti ad abbracciare anche ECS e consentire loro di definire i propri componenti e implementare i propri sistemi utilizzando il linguaggio di scripting.


0

ECS ha i suoi pro e contro. Lo scripting user-friendly non è uno dei suoi pro.

Il problema che ECS risolve è la capacità di avere un gran numero di cose simili nel tuo gioco allo stesso tempo mantenendo le prestazioni. Ma questa soluzione ha un costo: il costo di un'architettura facile da usare. Non è la migliore architettura per ogni gioco.

Ad esempio, ECS sarebbe stata una buona scelta per Space Invaders , ma non tanto per PacMan .

Quindi non esattamente la risposta che stavi cercando, ma è possibile che ECS non sia lo strumento giusto per il tuo lavoro.

Se aggiungi un wrapper, osserva i costi generali. Se finisci per rimuovere il miglioramento delle prestazioni di ECS nel tuo wrapper, allora hai il peggio di entrambi i mondi.


Ma per rispondere direttamente alla tua domanda: "A tutti gli sviluppatori di giochi che hanno già implementato script in un ECS - come hai fatto?"

Praticamente esattamente come lo stai facendo, senza un involucro. Le entità non hanno nient'altro che un identificatore. I componenti non hanno altro che dati. I sistemi non hanno altro che logica. Vengono eseguiti i sistemi che accettano entità con i componenti richiesti. Aggiungi sistemi, entità e componenti liberamente.

Una volta ho usato un framework con un quarto aspetto, chiamato lavagna. Fondamentalmente era un modo per i sistemi di comunicare tra loro. Ha creato più problemi di quanti ne abbia risolti.


Correlati: devo implementare Entity Component System in tutti i miei progetti?


0

Con ECS puoi suddividere in un'unica responsabilità, quindi qualsiasi entità che si sposta vorrebbe due componenti di dati: un MoveComponent e 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;
}
///////////////////////////////////////////

così ora nella tua conversione aggiungi quei componenti alle tue entità

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

Ora che abbiamo la conversione e i dati che possiamo spostare nel sistema, ho rimosso il sistema di input per la leggibilità, ma se desideri saperne di più sul sistema di input, avrò tutto elencato nel mio articolo la prossima settimana su Unity 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;
        }
    }
}

nota che la classe sopra sta usando Unity.Mathmatics. Questo è ottimo per essere in grado di utilizzare diverse funzioni matematiche con cui sei abituato a lavorare nei sistemi normali. Con tutto ciò in linea ora puoi lavorare sul comportamento delle entità: di nuovo ho rimosso l'input qui ma questo è molto meglio spiegato nell'articolo.

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

Ora puoi introdurre entità che si sposteranno avanti a una velocità.

Ma anche questo sposterà ogni entità con questo comportamento in modo da poter introdurre i tag, ad esempio se hai aggiunto un PlayerTag, solo l'entità con il playerTag IComponentData sarà in grado di eseguire il MoveForward se voglio solo spostare il giocatore come nell'esempio sotto.

Lo approfondirò anche nell'articolo, ma sembra così in un tipico ComponentSystem

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

Gran parte di questo è spiegato abbastanza bene nella presentazione di Angry Dots con Mike Geig, se non l'hai ancora visto ti consiglio di provarlo. Indicherò anche il mio articolo dopo che è scaduto. Dovrebbe davvero essere utile per ottenere molte di quelle cose a cui sei abituato a lavorare come desideri in ECS.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.