DonerCcomponents est un cadre de modèle d'objet de jeu C ++ 14 basé sur les composants pour développer des jeux vidéo.
Si vous n'êtes pas familier avec ce concept, je vous recommande de jeter un coup d'œil au projet d'exemple ou au tutoriel et à l'essayer!
Vous pouvez acquérir des versions stables ici.
Alternativement, vous pouvez consulter la version de développement actuelle avec:
git clone https://github.com/Donerkebap13/DonerComponents.git
N'oubliez pas d'exécuter git submodule update --init --recursive par la suite.
Vous pouvez également me contacter directement par e-mail, si vous avez une suggestion ou si vous trouvez un bogue, n'hésitez pas à créer un nouveau problème.
DonerComponents_asteroids_Example est un exemple de projet que j'ai créé afin de montrer comment utiliser DonerCcomponents . C'est un clone des astéroïdes vraiment simples. Mon intention avec ce projet est de montrer toutes les fonctionnalités du Framework en ce moment, comme:
Ici, je vais essayer d'illustrer l'utilisation de base des principaux systèmes de DonerComponents. Après avoir lu ceci, vous aurez les connaissances de base sur la façon dont les choses sont organisées et comment elles peuvent être utilisées. Pour une compréhension plus approfondie, je vous recommande de jeter un œil au projet d'exemple.
CDonerComponentsSystems est un singleton qui initialise et donne accès à tous les systèmes différents DonerComponents . Il doit être initialisé:
# include < DonerComponents/CDonerComponentsSystems.h >
DonerComponents::CDonerComponentsSystems::CreateInstance ();
DonerComponents::CDonerComponentsSystems::Get ()->Init();Mis à jour:
float elapsed = ...;
DonerComponents::CDonerComponentsSystems::Get ()->Update(elapsed);Et détruit:
DonerComponents::CDonerComponentsSystems::DestroyInstance (); DonerComponents::CGameObject est l'acteur principal de DonerComponents. Cette classe peut contenir différents DonerComponents::CComponent qui définit son comportement. Il a également des informations sur ses parents et ses enfants. Il peut également recevoir des messages POD et les transmettre à ses composants et à ses enfants. Enfin et surtout, il peut également être étiqueté.
Créer un nouveau jeu GameObject est aussi simple que:
# include < DonerComponents/gameObject/CGameObject.h >
DonerComponents::CGameObjectManager* gameObjectManager = DonerComponents::CDonerComponentsSystems::Get()-> GetGameObjectManager ();
DonerComponents::CGameObject *gameObject = gameObjectManager-> GetNewElement (); GetNewElement(); Renvoie un DonerComponents::CGameObject tant qu'il n'a pas manqué de GameObjects à générer. Par défaut, DonerCcomponents peut avoir 4096 GameObjects vivant en même temps. Cette valeur est modifiable via l'indicateur du compilateur -DMAX_GAME_OBJECTS=4096 avec un maximum de 8,192 GameObjects.
DonerCcomponents prend en charge la définition des préfabriques, afin que l'utilisateur puisse définir une hiérarchie GameObject spécifique pour la réutiliser partout où elle est nécessaire:
# include < DonerComponents/gameObject/CPrefabManager.h >
DonerComponents::CGameObjectManager* prefabManager = DonerComponents::CDonerComponentsSystems::Get()-> GetPrefabManager ();
prefabManager-> RegisterPrefab ( " prefabName " , anyGameObjectCreatedPreviously);Les préfabriques pourraient également être chargés à partir d'un fichier JSON.
DonerComponents::CComponent est la classe de base de tout composant de DonerComponents. Les composants définissent le comportement du GameObject par agrégation. Ils peuvent écouter des messages spécifiques et effectuer des actions en conséquence. Tout nouveau composant doit hériter de cette classe et il peut implémenter certaines méthodes de base, si nécessaire. L'utilisateur peut s'inscrire jusqu'à 512 composants différents.
Voici un exemple d'une nouvelle création de composants. L'implémentation de toutes ses méthodes est facultative :
# include < DonerComponents/component/CComponent.h >
class CCompFoo : public DonerComponents ::CComponent
{
public:
void DoInit () override { m_foo = 0 . 0f ; }
void DoUpdate ( float dt) override { m_foo += dt; }
void DoDestroy () override { m_foo = 0 . 0f ; }
void DoActivate () override { }
void DoDeactivate () override { }
private:
float m_foo;
};Pour enregistrer ce composant dans le système, afin que tout jeu GameObject puisse l'utiliser, nous devons effectuer ce qui suit:
# include < DonerComponents/component/CComponentFactoryManager.h >
static constexpr int amountOfFooComponentsAvailable = 512 ;
ADD_COMPONENT_FACTORY ( " foo " , CCompFoo, amountOfFooComponentsAvailable); Après avoir initialisé les DonerComponents::CDonerComponentsSystems nous pouvons commencer à enregistrer nos composants dans le système à l'aide du macro ADD_COMPONENT_FACTORY . La chaîne qu'il reçoit est d'identifier le composant tout en analysant nos GameObjects à partir d'un fichier JSON . Les derniers paramètres sont le nombre de composants disponibles. Comme pour les GameObjects, il y a un maximum de 8,192 composants du même type en même temps.
Une fois qu'une composante est enregistrée dans le système, elle peut être ajoutée à un GameObject de deux manières différentes:
DonerComponents::CComponent* component = gameObject->AddComponent<CCompFoo>();
// same as
CCompFoo* component = gameObject->AddComponent<CCompFoo>();
// or
DonerComponents::CComponent* component = gameObject-> AddComponent ( " foo " );
// same as
CCompFoo* component = gameObject-> AddComponent ( " foo " );Dans DonerCcomponents , les composants sont mis à jour par type, un type à la fois, dans l'ordre dans lequel ils ont été enregistrés dans le système. Donc dans l'exemple:
ADD_COMPONENT_FACTORY ( " foo " , CCompFoo, 128 );
ADD_COMPONENT_FACTORY ( " bar " , CCompBar, 128 ); Tous CCompFoo existants seront mis à jour séquentiellement avant de mettre à jour tous les composants CCompBar existants.
Vous pouvez définir quelles données seront exposées pour être modifiées dans JSON à l'aide de Donerserializer . Vous pouvez vérifier ici comment l'utiliser. Ici, je vais juste montrer un exemple.
class CCompFoo : public DonerComponents ::CComponent
{
DONER_DECLARE_COMPONENT_AS_SERIALIZABLE (CCompFoo)
public:
CCompFoo ();
private:
float m_dummy1;
std::vector<std::string> m_dummy2;
};
DONER_DEFINE_REFLECTION_DATA (CCompFoo,
DONER_ADD_NAMED_VAR_INFO (m_dummy1, " dummy1 " ),
DONER_ADD_NAMED_VAR_INFO(m_dummy2, " dummy2 " )
) Après avoir exposé m_dummy1 et m_dummy2 , nous pouvons définir leurs valeurs en JSON comme ceci:
{
"root" : {
"name" : " test1 " ,
"components" : [
{
"name" : " foo " ,
"dummy1" : 1.0 ,
"dummy2" : [ " Test1 " , " Test2 " , " Test3 " ]
}
]
}
}Pour un aperçu plus approfondi de la lecture de JSON , vérifiez ceci .
DonerCcomponents prend en charge un système de messages pour interagir entre les différents GameObjects et les composants. Un message peut être n'importe quelle structure / classe définie par l'utilisateur. Habituellement, il ne contiendra que des données, pas de logique, mais il n'y a pas de limitation à cela. C'est ainsi qu'un DonerComponents::CComponent peut écouter un message spécifique:
// Somewhere in your code
struct SDummyMessage {
SDummyMessage ( int foo, int bar)
: m_foo(foo), m_bar(bar) {}
int m_foo = 0 ;
int m_bar = 0 ;
}
// Inside your component
CCompFoo::RegisterMessages () {
RegisterMessage (&CCompFoo::OnDummyMessage);
}
void CCompFoo::OnDummyMessage ( const SDummyMessage& message) {
// ...
}Après avoir enregistré les messages que vous souhaitez, vous pouvez commencer à envoyer des messages comme ceci:
SDummyMessage message ( 2 , 3 );
// This will propagate the message to all gameObject's components.
gameObject-> SendMessage (message);
// This will propagate the message to all gameObject's components and its children's components.
gameObject-> SendMessage (message, DonerComponents::ESendMessageType::Recursive);
// This won't send the message to the current gameObject but it's children.
gameObject-> SendMessageToChildren (message);
// Same as before but recursively through all gameObject's children and children's children.
gameObject-> SendMessageToChildren (message, DonerComponents::ESendMessageType::Recursive); SendMessage envoie le message immédiatement, dans le même cadre. Si vous souhaitez retarder l'envoi du message jusqu'à la fin de la trame, utilisez plutôt PostMessage .
Enfin et surtout, si vous souhaitez envoyer un message à tous les GameObjects vivants, vous pouvez utiliser BroadcastMessage :
SDummyMessage message ( 2 , 3 );
// This will propagate the message to all GameObjects alive.
gameObjectManager-> BroadcastMessage (message); DonerComponents::CHandle est une sorte de pointeurs intelligents à fil unique . Ils indiquent un DonerComponents::CGameObject ou DonerComponents::CComponent , sachant à tous les moments s'ils sont toujours valides ou non ou, en d'autres termes, s'ils ont été détruits ailleurs dans le code. La taille d'un DonerComponents::CHandle est de 32 bits. La façon de travailler dans des donscomponents est que nous ne stockons jamais des pointeurs bruts de DonerComponents::CGameObject ou DonerComponents::CComponent , nous stockons toujours DonerComponents::CHandle , afin que nous puissions vérifier si l'élément qu'ils pointent est toujours valide, donc nous n'accès pas aux pointeurs qui n'accédons pas. Tout DonerComponents::CHandle peut être jeté à un DonerComponents::CGameObject ou DonerComponents::CComponent . Si le casting est valide et que l'élément existe toujours, il renverra un pointeur valide à l'élément. Sinon, il renverra nullptr . Voici un exemple:
# include < DonerComponents/handle/CHandle.h >
# include < DonerComponents/gameObject/CGameObjectManager.h >
using namespace DonerComponents ;
CHandle gameObjectHandle = m_gameObjectManager-> GetNewElement ();
if (gameObjectHandle) {
// CGameObjectManager has return a valid CGameObject
} else {
// CGameObjectManager has run out of CGameObjects
}
CGameObject* gameObject = gameObjectHandle;
// gameObject will be valid as gameObjectHandle points to an alive gameObject
m_gameObjectManager-> DestroyGameObject (&gameObject);
// gameObject is nullptr at this point
// gameObjectHandle == false as it points to a destroyed gameObject.Vous pouvez également envoyer des messages via des poignées. Si la poignée est valide, le message se propagera correctement. Sinon, le message sera ignoré:
DonerComponents::CHandle handle = gameObject;
SDummyMessage message ( 2 , 3 );
handle.SendMessage(message);
Les balises sont un moyen d'ajouter plus d'informations à vos GameObjects, vous pouvez donc les filtrer, envoyer des messages uniquement à GameObjects avec des balises spécifiques, etc. Il existe deux façons d'ajouter des balises au système, vous pouvez donc les utiliser plus tard. Le premier, les déclarant directement dans le code:
# include < DonerComponents/tags/CTagsManager.h >
DonerComponents::CTagsManager* tagsManager = DonerComponents::CDonerComponentsSystems::Get()-> GetTagsManager ();
tagsManager-> RegisterTag ( " Tag1 " );
tagsManager-> RegisterTag ( " TagN " );Le second, en les analysant à partir d'un fichier JSON:
tagsManager-> ParseTagsFromFile ( " path/to/your/tags.json " );Le format du fichier tags.json est quelque chose de similaire à ceci:
{ "tags" : [ " Tag1 " , " tag2 " , " tagN " ] }DonerCcomponents prend en charge le chargement à partir du disque en utilisant JSON grâce à DonerSerializer , il existe donc un moyen de créer des préfabs ou des scènes qui peuvent être stockées en tant qu'actifs au lieu de les construire à partir de zéro dans le code chaque fois que nous exécutons notre application. L'utilisation de base est la suivante:
# include < DonerComponents/GameObjects/CGameObjectParser.h >
DonerComponents::CGameObjectParser parser;
CGameObject* gameObject = parser.ParseSceneFromFile( " path/to/your/scene.json " );Le format d'un fichier scene.json est quelque chose de similaire à ceci:
{
"root" : {
"name" : " test1 " ,
"tags" : [ " tag1 " , " tag2 " , " tag3 " ],
"components" : [
{
"name" : " comp_location " ,
"x" : 1 ,
"y" : -3 ,
"z" : 9
},
{
"name" : " comp_rotation " ,
"radians" : 0.2
}
],
"children" : [
{
"name" : " test11 " ,
"tags" : [ " tag1 " , " tag3 " ]
},
{
"name" : " test12 " ,
"initiallyActive" : false
}
]
}
} Si, au lieu d'analyser une scène, nous voulons analyser un préfabriqué pour l'enregistrer automatiquement dans DonerComponents::CPrefabManager , nous avons juste besoin d'appeler ParsePrefabFromFile :
# include < DonerComponents/GameObjects/CGameObjectParser.h >
DonerComponents::CGameObjectParser parser;
CGameObject* gameObject = parser.ParsePrefabFromFile( " path/to/your/prefab.json " );Après cela, le Prefab est disponible pour toute nouvelle scène analysée à utiliser.
DonerCcomponents prend en charge les préfabs qui incluent d'autres préfabriques, en mesure de remplacer les informations de son composant:
Prefab1.json
{
"root" : {
"name" : " Prefab1 " ,
"components" : [
{
"name" : " comp_location " ,
"x" : 1 ,
"y" : -3 ,
"z" : 9
},
{
"name" : " comp_rotation " ,
"radians" : 0.2
},
{
"name" : " sprite " ,
"texture" : " res/common/textures/asteroid_med.png "
}
]
}
}Prefab2.json
{
"root" : {
"name" : " Prefab2 " ,
"prefab" : " Prefab1 " ,
"components" : [
{
"name" : " sprite " ,
"texture" : " res/common/textures/flower_big.png "
}
]
}
}Scene.json
{
"root" : {
"name" : " root " ,
"prefab" : " Prefab2 " ,
"components" : [
{
"name" : " comp_location " ,
"x" : 0 ,
"y" : 0 ,
"z" : 0
}
]
}
}