El motor Koala es un motor de juego implementado completamente como un sistema de componentes de entidad (ECS).
El motor se basa en ENTT. La integración con otras piezas de software que usa EnTT debe ser sencilla. Esta documentación asume al menos un conocimiento básico de EnTT y su terminología ( entity , registry , handle ...).

El proyecto de ejemplo muestra algunas de las características principales. Debería darle una idea de lo que el soporte del motor para la reflexión y la extensibilidad del tiempo de ejecución tienen para ofrecer.
El motor utiliza submódulos GIT y, por lo tanto, debe clonarse de manera recursiva con
git clone https://github.com/phisko/kengine --recursive
El motor ha sido probado en Windows con MSVC y MingW.
La compilación de Linux funciona con GCC. Al momento de escribir, Clang no admite C ++ 20's constexpr std::string y std::vector .
El motor requiere un compilador C ++ 20 .
El motor comenzó como un proyecto de pasión/estudiante en 2016-17. Mis amigos/colegas y yo tuvimos una oportunidad para implementar un ECS desde cero. Queríamos seguridad y claridad de tipo absoluto, y aunque ya habíamos jugado con la metaprogramación de la plantilla, esta era una oportunidad para que aprendiéramos más al respecto.
Una vez que el Core ECS estaba funcionando, el motor se convirtió en un patio de recreo para aprender más sobre el desarrollo de juegos. Aprendí la representación de OpenGL, cómo usar NavMeshes con refundición/desvío, configurar la física con bala ... todo el tiempo desarrollando ayudantes útiles e instalaciones de reflexión de tiempo de ejecución compatibles con un ECS.
Después de ahora más de 5 años trabajando en el motor, me di cuenta de que el Core ECS ya no es el foco de este proyecto. Otras bibliotecas, y EnTT en particular, ofrecen una API muy similar, con características mucho más avanzadas. Así que me tomé el tiempo para destripar por completo el ECS interno y reemplazarlo con EnTT . Todas las características siguen siendo las mismas, y esto me dará más tiempo para trabajar en ayudantes útiles, lo que puede funcionar con otros proyectos EnTT .
Muchas partes del motor (como los sistemas de secuencias de comandos o el editor de entidades imgui) utilizan la API de reflexión de putils . La mayoría de los componentes en las siguientes muestras se definen así como reflejables.
El código del motor está organizado en tres categorías:
Tenga en cuenta que systems no son objetos de una clase específica. Los sistemas son simplemente entidades con un componente de ejecución (o cualquier otra cosa que necesiten para hacer su trabajo). La entidad luego vive en el registry con el resto del estado del juego. Esto permite a los usuarios sistemas introspectivos o agregarles comportamiento como cualquier otra entidad.
Estas tres categorías se dividen en varias bibliotecas, por ejemplo:
Tenga en cuenta que algunas bibliotecas contienen sub-bibliotecas, por ejemplo:
La sección CMake entra en más detalles sobre cómo trabajar con estas bibliotecas.
El motor viene con un número (bastante grande) de componentes preconstruidos que se pueden usar para arrancar un juego, o simplemente como ejemplos en los que puede basar sus propias implementaciones.
Estos componentes se ajustan a tres categorías:
Los componentes de datos contienen datos sobre su entidad.
Los componentes de datos son lo que primero viene a la mente al pensar en un componente, como una transformación o un nombre.
Los componentes de datos a veces pueden contener funciones:
Los componentes de la función contienen funciones para consultar, alterar o notificar a su entidad.
Los componentes de la función son simplemente titulares de functores que se pueden conectar como componentes a las entidades. Este mecánico puede usarse para:
Los componentes de la función son tipos que heredan de base_function, dándole la firma de la función como un parámetro de plantilla.
Para llamar a un componente de función, uno puede usar su operator() o su función call .
entt::registry r;
const auto e = r.create();
r.emplace<main_loop::execute>(e,
[]( float delta_time) { std::cout << " Yay! " << std::endl; }
);
const auto & execute = r.get<main_loop::execute>(e); // Get the function
execute ( 0 .f); // Call it with its parameters
execute.call( 42 .f); // AlternativelyLos meta componentes son componentes para componentes.
El motor utiliza "entidades de tipo" para contener información sobre los diversos componentes en uso. Cada entidad tipo representa un tipo de componente diferente y se puede usar para consultar las propiedades del componente en tiempo de ejecución.
Los meta componentes están conectados a estas "entidades tipo" y mantienen la implementación de una función genérica para ese tipo específico. Debido a que mantienen funciones, son muy similares a los componentes de la función.
Un ejemplo deja esto más claro: meta :: imgui :: editar es un meta componente que, cuando se llama, dibujará las propiedades de su "componente principal" usando IMGUI para la entidad dada. El siguiente código mostrará una ventana para editar el componente de nombre de e
// r is a registry with the "type entity" for `name` already setup
const auto e = r.create();
r.emplace<core::name>(e);
const auto type_entity = type_helper::get_type_entity<core::name>(r);
const auto & edit = r.get<meta::imgui::edit>(type_entity);
if (ImGui::Begin( " Edit name " ))
edit ({ r, e });
ImGui::End ();Si generaliza esto, puede editar todos los componentes para una entidad con el siguiente código:
// r is a registry with the "type entities" for all used components already setup
// e is an entity with an unknown set of components
if (ImGui::Begin( " Edit entity " ))
for ( const auto & [type_entity, edit] : r.view<meta::imgui::edit>()) {
edit ({ r, e });
}
ImGui::End ();Consulte Cmake para obtener instrucciones sobre cómo habilitar cada biblioteca.
Se proporciona un script Generate_Type_registration Python, que puede usarse para generar archivos C ++ que contienen funciones que registrarán un conjunto de tipos dados con el motor.
Esto no es absolutamente obligatorio .
El motor utiliza CMake como sistema de compilación. Se ha establecido un marco personalizado para simplificar la creación de bibliotecas. La raíz cmakelists itera sobre subdirectorios y los agrega automáticamente como bibliotecas si coinciden con algunas condiciones.
Se crea una biblioteca de interfaz kengine Base que enlaza con todas las bibliotecas habilitadas, por lo que los clientes simplemente pueden vincularse con eso.
Las siguientes opciones de CMake están expuestas.
KENGINE_TESTSCompila ejecutables de prueba para las bibliotecas que implementan pruebas.
KENGINE_NDEBUGDesactiva el código de depuración.
KENGINE_TYPE_REGISTRATIONGenerará el código de registro de tipo para los tipos de motor. Esto es fundamental para muchas de las capacidades de reflexión del motor, ya que proporciona la implementación de los meta componentes.
KENGINE_GENERATE_REFLECTIONActualizará los encabezados de reflexión para los tipos de motor. Estos se generan previamente, por lo que a menos que esté modificando el código fuente del motor, no debería necesitar para habilitar esto.
Todas las bibliotecas están deshabilitadas de forma predeterminada, para evitar construir dependencias no deseadas. Cada biblioteca se puede habilitar individualmente configurando su opción CMake en ON . Consulte Nombramiento de la biblioteca para ver el nombre de la opción.
Alternativamente, todas las bibliotecas se pueden habilitar con la opción KENGINE_ALL_SYSTEMS .
Tenga en cuenta que los sub-bibliotecarios necesitan que su biblioteca principal esté habilitada: kengine_imgui_entity_editor requiere kengine_imgui.
Las bibliotecas se nombran dependiendo de su ruta relativa a la raíz del motor. Las barras en el camino simplemente se reemplazan por subrayos, por ejemplo:
kengine_corekengine_imgui_toolEstos nombres son:
KENGINE_CORE para kengine_core )KENGINE_CORE_EXPORT para kengine_core )Es posible probar la existencia de una biblioteca durante la compilación gracias a que C ++ Define macros. Estos tienen el mismo nombre que las opciones de Cmake, por ejemplo:
# ifdef KENGINE_CORE
// The kengine_core library exists
# endifAlgunas bibliotecas hacen uso de VCPKG para la gestión de dependencias.
Dado que las bibliotecas son detectadas automáticamente por la raíz CMakeLists.txt , crear una nueva biblioteca es bastante fácil.
Las bibliotecas se vinculan automáticamente contra kengine_core , ya que proporciona ayudantes que deberían ser utilizados por todas las bibliotecas (como log_helper y el perfil_helper).
Los sub-bibliotecarios se vinculan automáticamente con sus padres. Por ejemplo, KEngine_imgui_entity_Editor se vincula automáticamente contra Kengine_imgui.
Los archivos de origen de los subdirectorios helpers y systems de una biblioteca se agregan automáticamente. Si no se encuentra ninguno, la biblioteca será una biblioteca de interfaz CMake.
El tipo de registro y código de reflexión se pueden generar automáticamente para componentes. Por defecto, todos los encabezados en los subdirectorios data y functions de una biblioteca se pasarán a los scripts de generación.
De manera similar a los archivos de origen, si alguno *.tests.cpp archivos se encuentran en helpers/tests o los subdirectorios systems/tests de una biblioteca, se agregará automáticamente un ejecutable Googletest.
CMakeLists.txt Custom.txt Las bibliotecas básicas no deberían necesitar sus propias CMakeLists.txt , ya que sus archivos fuente serán automáticamente. Sin embargo, si una biblioteca necesita un comportamiento personalizado (por ejemplo, agregar fuentes adicionales o vincular con una biblioteca de terceros), puede agregar su propia CMakeLists.txt . Que CMakeLists.txt se llamará después de la llamada a add_library .
Las siguientes variables y funciones se definen antes de llamar a CMakeLists.txt :
kengine_library_name : el nombre de la bibliotecakengine_library_tests_name : el nombre de la biblioteca googletest el objetivolink_type : el tipo de enlace de la biblioteca ( PUBLIC o INTERFACE , dependiendo de si las fuentes se encontraron o no)kengine_library_link_public_libraries(libraries) : enlaces contra otras bibliotecas (públicamente)kengine_library_link_private_libraries(libraries) : enlaces contra otras bibliotecas (en privado)register_types_from_headers(headers) : agrega encabezados para qué tipo se pueden generar encabezados de registro y reflexiónsubdirectory_is_not_kengine_library(path) : indica a la raíz CMakeLists.txt que no debe procesar path como una biblioteca de Kengine