La néssecité d'avoir un système ECS par rapport à l'héritage.
Le problème de l'héritage c'est que ça peut devenir très lourd dans le cas de jeux par exemple,
ou vous avez différents objets ou encore différents sorts, et chaque objets ou sorts affectent une statisitque
différente. Avec l'héritage il faudrait un nombre de classes filles qui soit égale aux nombres d'effets différents
que peuvent fournir les sorts ou les objets. (Stats différentes, etc...) ou encore faire un test avec le nom de l'objet
ou du sort et appliquer un effet différent en fonction du nom ce qui serait trop lourd à mettre en place. C'est là que le système ECS (Entity,Component,System) de ODFAEG intervient.
Le système ECS de ODFAEG permet de pouvoir attacher à chaque entité, des composants. De ce fait vous pouvez bénéficier à la fois des bénéfices de l'héritage (redéfinition de comportements) et
du système ECS car ODFAEG n'est pas purement ECS car certains jeux ne nécessitent pas obligatoiremen d'avoir un système ECS.(Jeux d'échec, ...)
D'ailleurs ODFAEG est basé sur la SFML à la base pour créer des jeux simples donc tout ce qui est transformable et les sommets
sont définits à l'aide de l'héritage mais pour certains jeux plus complexe il est nécessaire de rajouter la couche ECS.
C'est la classe Entity qui permet de faire le pont entre l'héritage et le monde ECS mais attention de ne pas dupliquer les données
c'est à dire ajouter un composant HP par exemple à une entité hors que vous avez déclaré une variable membre de type hp pour cette entité.
Comment utilisé le système ECS de ODFAEG.
Grâce aux handle sur l'EntityId dans la classe Entity et à l'objet componentMapping vous pouvez facilement manipuler des composants.Ajouter des composants aux entités :
Imaginons que vous ayez une entité item, vous souhaîtez lui ajouter une stat (HP par exemple) et que vous ayez définit un composantHPStat comme ceci :
struct HPStat {
int hp;
};
Pour ajouter le composant HPStat à l'objet item il suffit d'appeler la méthode addComponent :
Entity* potion = new Item("Hp potion");
HPStat hpToResotre;
hpToRestore.hp = 100;
potion->addComponent(hpToResotre);
Récupérer des composants :
Pour récupérer des componsants vous devez utiliser la méthode getComponent attention que celle-ci peut ne pas renvoyé de composantsi cette entité ne possède pas de composant de ce type.
Voici un exemple qui varifie si l'entité potion possède un composant HPStat et affiche le nombre d'hp à restaurer:
if (potion->getComponent().has_value()) {
std::cout<<"hp to restore : "<getComponent().value().get().hp<
J'utilise value pour extraire l'objet qui contient la valeur du composant et get pour extraire la référence vers le composant.Supprimer un composant
Pour ce faire il faut appeler la méthode removeComponent comme ceci :
potion->removeComponent<HPStat>();
Ajouter une entité enfant.
Tout comme les entités ODFAEG, les entités ECS peuvent avoir des entités enfants, par exemple :
Entity* folder = new Folder();
Entity* file = new File();
folder->addComponent(datasFolder);
file->addComponent(datasFile);
folder->addEnttChild(file->getEnttID());
Il suffit de récupérer le handle de l'entité enfant et d'appeler la méthode addEnttChild et de lui passer le handle de l'entité enfant.Ceci permet par exemple, si vous voulez appliquer des systèmes sur les entités parents et enfants, que le système soit appliqué sur les deux entités à la fois sans
devoir le faire exmplicitement!
Sérializer des composants.
Tout comme les entités, les composants peuvent être sérialisés, pour cela il faut, comme pour les entités, définir une méthodeserialize avec comme paramètre template l'Archive dans la classe de votre composant. (voir le chapitre sur la sérialisation)
Pour sérializer des composants d'une entité, il faut appeler la méthode writeEntities en lui passant comme paramètre template,
la liste des composants à sérialiser (cela sérialize aussi les composants enfants) :
folder->writeEntities(outputArchive);
Et pour les lires, il suffit d'appeler la méthode readEntities
folder->readEntities(inputArchive);
Attention que la lecture et l'écriture sont symétriques vous devez donc, lire les entités dans le même order que vous les avez écrites.
Clôner et fusionner des composants.
Comme pour les entités vous pouvez clôner des composants par exemple :
Entity* clonedFolder = folder->clone();
clonedFolder->setEnttID(folder->cloneEntt());
Il suffit d'appeler la méthode cloneEntt et de lui passez la liste des composants à clôner et de passer le handle retourné par la méthode cloneEntt à l'entité clônée.Et pour la fusion :
Entity* mergedFolder = new Folder();
mergedFolder->setEnttID(gitFolder.merge(localFolder));
Comme vous pouvez le constatez, ceci est vraiment pratique si vous souhaitez, fusionner deux répertoires comme lorsque vouspusher votre travail depuis votre PC sur un dépôt git par exemple.
Appliquer un système aux entités.
Pour modifier des composants on utilise des systèmes que l'on applique sur les composants, vous devez définir les systèmes comme ceci :
template
concept CONTAINS = requires {
typename std::enable_if_t::value>;
};
template
concept NOTCONTAINS = requires {
typename std::enable_if_t::value>;
};
struct Attaquer {
template requires CONTAINS
void operator()(EntityId entityId, ComponentMapping& cmapping, T& params) {
Entity* ennemi = std::get<0>(params);
if (cmapping.getComponent(entityId).has_value()) {
WeaponComponent& wc = cmapping.getComponent(entityId).value().get();
//On attaque l'ennemi.
}
}
template requires NOTCONTAINS
void operator()(EntityId entityId, ComponentMapping& cmapping, T& params) {
//On n'attaque pas l'ennemi.
}
};
Pour créer des systèmes, il faut redéfinir l'opérateur() et celui-ci prend 3 paramètres : l'entité courante pour lequel le systèmeest appelé, component mapping qui est l'objet utilisé pour récupérer les composants de l'entité courante et params qui
est un tuple pouvant contenir des paramètres supplémentaires à passer au système lors de l'appel à celui-ci.
Voilà maintenant il ne reste plus qu'à appeler notre système sur l'entité comme ceci :
std::tuple ennemies = std::make_tuple(ennemi);
Attaquer attaquer;
hero.apply(attaquer, ennemies);
Voilà c'est tout pour ce chapitre!