(Intermédiaire) Bien organiser ses Entités.



  • Sommaire

    Introduction

    Cette astuce a pour but de vous faire découvrir une méthode pour bien organiser vos entités et leurs fonctionnalités.
    Cela permet de créer un système d'entité dynamique, par exemple, pour un système de PNJ.

    Pré-requis

    • Savoir utiliser les Entités
    • Être au point sur les Interfaces et Classes Abstraites (Tutoriel)

    Un peu de théorie

    Afin d'organiser nos entités, nous allons utiliser une version très simplifiée d'un Design Pattern propre aux jeux vidéos.
    Celui ci se nomme **ECS **de Entity Component System, il est d'ailleurs utilisé par Forge avec les ExtendedProperties.
    Pour bien comprendre, imaginez que vous souhaitez créer un système d'entité. La méthode la plus commune est de créer les classes de vos entités et d'y insérer vos variables, mais ce système devient très vite embarrassant lorsque le nombre de variable devient très grand. Par exemple, pour ajouter une monnaie, vous allez créer plusieurs méthodes juste pour agir avec l'argent du joueur. À force, on se retrouve avec un très grand nombre de fonctions.
    Nous pouvons donc dès maintenant mettre une croix dessus.
    <image plus disponible :/>

    Du coup, pour insérer nos variables, nous allons utiliser ce qu'on appelle des components. L'avantage d'utiliser ceux ci est que l'on peut très facilement et très rapidement modifier ou même supprimer nos fonctionnalités !
    On va donc simplement enregistrer une collection de **components **dans notre classe principale.
    <image plus disponible :/>
    J'utilise ici une Liste mais on peut utiliser plusieurs types de collections en fonction de nos besoins.

    Grâce à ce système, on va pouvoir bien organiser nos entités, ce qui nous permettra de les modifier très facilement plus tard !
    De plus, les components peuvent être accroché à n'importe quelle entité, et on retrouve carrément des ComponentEntityPosition, ComponentEntityVelocity, etc... dans certains jeux !

    Après cette petite explication, nous allons passer à la pratique.

    Code

    Maintenant que nous avons une idée claire de ce que nous voulons, nous allons nous occuper d'y implémenter.
    Commençons par le début : le Component.

    Créez une nouvelle classe abstraite ComponentEntity.
    Dans le constructeur, faites passer l'entité en question.

        protected final Entity entity;
    
        public ComponentEntity(Entity entity)
        {
            this.entity = entity;
        }
    

    Comme le component sert à stocker des variables, il doit pouvoir avoir accès aux NBT de l'entité. On rajoute donc ces deux méthodes :

        public abstract void writeToNBT(NBTTagCompound compound); 
    
        public abstract void readFromNBT(NBTTagCompound compound); 
    

    Mais pour stocker dans les NBT, il nous faut une chaîne de caractère qui sert de clef. Pour ça, on a qu'à demander !

        public abstract String getComponentName();
    

    Ensuite, vous pouvez ajouter les méthodes abstraites que vous souhaitez, cela dépend par exemple si vous utilisez le DataWatcher ou si vous passez par des Packets.
    Vous pouvez (pour cet exemple) créer une méthode abstraite addWatchableObjects, ou bien sync.

    Bon, notre objet est créé, mais rien n'est encore fonctionnel, il faut accrocher nos components sur notre entité.
    On créer notre collection (pour ma part, une liste).

        private List <componententity>entityComponents;
    

    Puis ont s'occupe des NBT :
    Pour chaque component de la liste, on créer un nouveau Tag qui aura pour nom le nom de notre component, ça permet d'éviter les erreurs de duplicata. (On est jamais trop prudent !)

        @Override
        public void writeToNBT(NBTTagCompound compound)
        {
            super.writeToNBT(compound);
            for(ComponentEntity component : entityComponents) 
            {
                NBTTagCompound tag = new NBTTagCompound();
                component.writeToNBT(tag);
                compound.setTag(component.getComponentName(), tag);
            }
        }
    
        @Override
        public void readFromNBT(NBTTagCompound compound) 
        {
            super.readFromNBT(compound);
            for(ComponentEntity component : entityComponents)
            {
                NBTTagCompound tag = compound.getCompoundTag(component.getComponentName());
                component.readFromNBT(tag);
            }
        }
    

    Et voilà, ça prend pas beaucoup de place !
    N'oubliez pas vos méthodes abstraites !
    Un exemple pour la synchronisation :

        public void syncAllComponent() 
        {
            for(ComponentEntity component : entityComponents) 
            {
                component.sync();
            }
        }
    
        public void syncComponent(int componentId) 
        {
            entityComponents.get(componentId).sync();
        }
    

    Pour terminer, on enregistre les components dans le constructeur de notre entité.

        public EntityExample(World world)
        {
            super(world);
            entityComponents = new ArrayList<componententity>();
            entityComponents.add(MON_COMPONENT, new ComponentEntityMonComponent(this));
        }
    

    Et on ajoute des getters.

        public ComponentEntityMonComponent getComponentEntityMonComponent() 
        {
            return (ComponentEntityMonComponent) entityComponents.get(MON_COMPONENT);
        }
    

    Vous pouvez rendre ce système dynamique en utilisant une TagList pour sauvegarder vos components, et d'ajouter les components en fonction de cette liste en lisant les NBT. Cela vous permettra d'attacher ou détacher des components directement en modifiant les NBT de l'entité !

    Le mot de la fin

    Si certains points ne sont pas clairs, n'hésitez pas à me le dire .



  • La technique est intéressante mais personnellement je préfère utiliser la hiérarchie de classe afin de ne pas surcharger mes classes de variables et de méthodes.



  • Oui, c'est une méthode comme les autres !
    L'avantage c'est que tu peux attacher/détacher les components très facilement, c'est surtout dans une optique dynamique.
    Par exemple si tu veux créer un système de NPC, pas besoin de créer 50 classes pour chaque type de NPC, suffit de lui attacher des components.



  • Ou tu crées une classe entité, une classe entité vivante, une classe NPC et un constructeur ou une classe pour chaque type de npc, comme fait minecraft aujourd'hui



  • Hello,

    sur le sujet, je propose un peu de lecture aux anglophiles.

    Il s'agit d'un article de 2007 (déjà!) qui a fait date car il regroupait un certain nombre de considérations de cette période, lorsque les moteurs de jeux sont passés d'architectures tout en hiérarchies à des architectures principalement basées sur des composants.

    Bonne lecture.


Log in to reply