DonerComponents es un marco de modelo de objeto de juego de componentes C ++ 14 para desarrollar videojuegos.
Si no está familiarizado con este concepto, le recomiendo que eche un vistazo al proyecto de ejemplo o al tutorial y lo pruebe.
Puede adquirir lanzamientos estables aquí.
Alternativamente, puede consultar la versión de desarrollo actual con:
git clone https://github.com/Donerkebap13/DonerComponents.git
Recuerde ejecutar git submodule update --init --recursive después.
Puede contactarme directamente por correo electrónico también, si tiene alguna sugerencia o encuentra algún error, no dude en crear un nuevo problema.
DonerComponents_asTeroids_Example es un proyecto de ejemplo que he creado para mostrar cómo usar donerComponents . Es un clon de aseroides realmente simple. Mi intención con ese proyecto es mostrar todas las características que el marco admite en este momento, como:
Aquí intentaré ilustrar el uso básico de los principales sistemas de donerComponentes. Después de leer esto, tendrá el conocimiento básico sobre cómo se organizan las cosas y cómo se pueden usar. Para una comprensión más profunda, le recomiendo que eche un vistazo al proyecto de ejemplo.
CDonerComponentsSystems es un singleton que inicializa y da acceso a todos los sistemas diferentes de donerComponents . Debe inicializarse:
# include < DonerComponents/CDonerComponentsSystems.h >
DonerComponents::CDonerComponentsSystems::CreateInstance ();
DonerComponents::CDonerComponentsSystems::Get ()->Init();Actualizado:
float elapsed = ...;
DonerComponents::CDonerComponentsSystems::Get ()->Update(elapsed);Y destruido:
DonerComponents::CDonerComponentsSystems::DestroyInstance (); DonerComponents::CGameObject es el actor principal de DonerComponents. Esta clase puede contener diferentes DonerComponents::CComponent que define su comportamiento. También tiene información sobre sus padres y sus hijos. También puede recibir mensajes POD y reenviarlos a sus componentes y sus hijos. Por último, pero no menos importante, también se puede etiquetar.
Crear un nuevo GameObject es tan simple como:
# include < DonerComponents/gameObject/CGameObject.h >
DonerComponents::CGameObjectManager* gameObjectManager = DonerComponents::CDonerComponentsSystems::Get()-> GetGameObjectManager ();
DonerComponents::CGameObject *gameObject = gameObjectManager-> GetNewElement (); GetNewElement(); Devolverá un DonerComponents::CGameObject siempre que no se haya quedado sin GameObjects para generar. Por defecto, DonerComponents puede tener 4096 GameObjects vivos al mismo tiempo. Este valor es modificable a través de la bandera del compilador -DMAX_GAME_OBJECTS=4096 con un máximo de 8.192 GameObjects.
DonerComponents admite la definición de prefabricados, por lo que el usuario puede definir una jerarquía específica de GameObject para reutilizarla donde sea necesario:
# include < DonerComponents/gameObject/CPrefabManager.h >
DonerComponents::CGameObjectManager* prefabManager = DonerComponents::CDonerComponentsSystems::Get()-> GetPrefabManager ();
prefabManager-> RegisterPrefab ( " prefabName " , anyGameObjectCreatedPreviously);Los prefabricados también se pueden cargar desde un archivo JSON.
DonerComponents::CComponent es la clase base para cualquier componente en donerComponents. Los componentes definen el comportamiento del gameObject por agregación. Pueden escuchar mensajes específicos y realizar acciones en consecuencia. Cualquier componente nuevo debe heredar de esta clase y puede implementar algunos métodos básicos, si es necesario. El usuario puede registrar hasta 512 componentes diferentes.
Aquí hay un ejemplo de una nueva creación de componentes. La implementación de todos sus métodos es opcional :
# 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;
};Para registrar este componente en el sistema, por lo que cualquier juego de gameObject puede usarlo, necesitamos hacer lo siguiente:
# include < DonerComponents/component/CComponentFactoryManager.h >
static constexpr int amountOfFooComponentsAvailable = 512 ;
ADD_COMPONENT_FACTORY ( " foo " , CCompFoo, amountOfFooComponentsAvailable); Después de inicializar el DonerComponents::CDonerComponentsSystems podemos comenzar a registrar nuestros componentes en el sistema utilizando el macro ADD_COMPONENT_FACTORY . La cadena que recibe es identificar el componente mientras analiza nuestros GameObjects desde un archivo JSON . El último parámetros es cuántos componentes estarán disponibles. Al igual que con los GameObjects, hay un máximo de 8.192 componentes del mismo tipo al mismo tiempo.
Una vez que se registra un Componet en el sistema, se puede agregar a un GameObject de dos maneras diferentes:
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 " );En DonerComponents , los componentes se actualizan por tipo, un tipo a la vez, en el orden en que se registraron en el sistema. Entonces en el ejemplo:
ADD_COMPONENT_FACTORY ( " foo " , CCompFoo, 128 );
ADD_COMPONENT_FACTORY ( " bar " , CCompBar, 128 ); Todos CCompFoo existentes se actualizarán secuencialmente antes de actualizar todos los componentes CCompBar existentes.
Puede definir qué datos estarán expuestos para modificarse en JSON usando DonerSerializer . Puede consultar aquí cómo usarlo. Aquí solo voy a mostrar un ejemplo.
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 " )
) Después de exponer m_dummy1 y m_dummy2 , podemos definir sus valores en JSON así:
{
"root" : {
"name" : " test1 " ,
"components" : [
{
"name" : " foo " ,
"dummy1" : 1.0 ,
"dummy2" : [ " Test1 " , " Test2 " , " Test3 " ]
}
]
}
}Para una mirada más profunda sobre cómo leer de JSON , consulte esto .
DonerComponents admite un sistema de mensajes para interactuar entre diferentes colegios de juego y componentes. Un mensaje podría ser cualquier estructura/clase definida por el usuario. Por lo general, solo contendrá datos, no hay lógica, pero no hay limitación en esto. Así es como un DonerComponents::CComponent puede escuchar un mensaje específico:
// 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) {
// ...
}Después de registrar los mensajes que desea, puede comenzar a enviar mensajes como este:
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 envía el mensaje de inmediato, en el mismo cuadro. Si desea retrasar el envío del mensaje hasta el final del marco, use PostMessage en su lugar.
Por último, pero no menos importante, si desea enviar un mensaje a todos los juegos de juego vivos, puede usar BroadcastMessage :
SDummyMessage message ( 2 , 3 );
// This will propagate the message to all GameObjects alive.
gameObjectManager-> BroadcastMessage (message); DonerComponents::CHandle son una especie de consejos inteligentes de un solo hilo . Señalan un DonerComponents::CGameObject o DonerComponents::CComponent , sabiendo en todos los momentos si todavía son válidos o no o, en otras palabras, si han sido destruidos en otro lugar del código. El tamaño de un DonerComponents::CHandle es de 32 bits. La forma de trabajar en DonerComponents es que nunca almacenamos consejos crudos de DonerComponents::CGameObject o DonerComponents::CComponent , siempre almacenamos DonerComponents::CHandle , por lo que podemos verificar si el elemento al que apuntan todavía es válido, por lo que no accedemos a los Pointers Dangling. Cualquier DonerComponents::CHandle puede ser elegido a un DonerComponents::CGameObject o DonerComponents::CComponent . Si el elenco es válido y el elemento todavía existe, devolverá un puntero válido al elemento. De lo contrario, devolverá nullptr . Aquí hay un ejemplo:
# 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.Además, puede enviar mensajes a través de mangos. Si el mango es válido, el mensaje se propagará correctamente. De lo contrario, el mensaje será ignorado:
DonerComponents::CHandle handle = gameObject;
SDummyMessage message ( 2 , 3 );
handle.SendMessage(message);
Las etiquetas son una forma de agregar más información a sus gameObjects, por lo que puede filtrarlas, enviar mensajes solo a GameObjects con etiquetas específicas, etc. Hay dos formas de agregar etiquetas al sistema, por lo que puede usarlos más tarde. Primero, declarándolos directamente en el código:
# include < DonerComponents/tags/CTagsManager.h >
DonerComponents::CTagsManager* tagsManager = DonerComponents::CDonerComponentsSystems::Get()-> GetTagsManager ();
tagsManager-> RegisterTag ( " Tag1 " );
tagsManager-> RegisterTag ( " TagN " );El segundo, analizándolos de un archivo JSON:
tagsManager-> ParseTagsFromFile ( " path/to/your/tags.json " );El formato del archivo tags.json es algo similar a esto:
{ "tags" : [ " Tag1 " , " tag2 " , " tagN " ] }DonerComponents admite la carga desde el disco usando JSON gracias a DonerSerializer , por lo que hay una manera de crear prefabricados o escenas que se pueden almacenar como activos en lugar de construirlos desde cero en código cada vez que ejecutamos nuestra aplicación. El uso básico es el siguiente:
# include < DonerComponents/GameObjects/CGameObjectParser.h >
DonerComponents::CGameObjectParser parser;
CGameObject* gameObject = parser.ParseSceneFromFile( " path/to/your/scene.json " );El formato de un archivo escena.json es algo similar a este:
{
"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, en lugar de analizar una escena, queremos analizar una prefabricación para registrarlo automáticamente en DonerComponents::CPrefabManager , solo necesitamos llamar ParsePrefabFromFile :
# include < DonerComponents/GameObjects/CGameObjectParser.h >
DonerComponents::CGameObjectParser parser;
CGameObject* gameObject = parser.ParsePrefabFromFile( " path/to/your/prefab.json " );Después de hacer esto, el prefabricado está disponible para cualquier nueva escena analizada.
DonerComponents admite prefabricados que incluyen otros prefabricados, poder anular la información de su componente:
Prefabulante1.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 "
}
]
}
}Escena.json
{
"root" : {
"name" : " root " ,
"prefab" : " Prefab2 " ,
"components" : [
{
"name" : " comp_location " ,
"x" : 0 ,
"y" : 0 ,
"z" : 0
}
]
}
}