O motor Koala é um mecanismo de jogo totalmente implementado como um sistema de componentes de entidade (ECS).
O motor é baseado no ENTT. A integração com outros softwares usando EnTT deve ser direta. Esta documentação assume pelo menos conhecimento básico da EnTT e sua terminologia ( entity , registry , handle ...).

O projeto de exemplo mostra alguns dos principais recursos. Deve ter uma idéia do que o suporte do mecanismo para a extensibilidade de reflexão e tempo de execução tem a oferecer.
O motor usa submódulos Git e, portanto, deve ser clonado recursivamente com
git clone https://github.com/phisko/kengine --recursive
O motor foi testado no Windows com MSVC e Mingw.
A compilação Linux funciona com o GCC. No momento da redação deste artigo, Clang não suporta o C ++ 20 do constexpr std::string e std::vector .
O motor requer um compilador C ++ 20 .
O motor começou como um projeto de paixão/aluno em 2016-17. Meus amigos/colegas e eu tentamos implementar um ECS desde o início. Queríamos segurança e clareza do tipo absoluto e, embora já tivéssemos brincado com metaprogramação de modelo, essa era uma chance de aprender mais sobre isso.
Depois que o Core ECS estava funcionando, o motor se transformou em um playground para aprender mais sobre o desenvolvimento de jogos. Aprendi a renderização do OpenGL, como usar Navmeshes com reformulação/desvio, configurar a física com bala ... o tempo todo desenvolvendo ajudantes úteis e instalações de reflexão de tempo de execução compatíveis com um ECS.
Depois de mais de cinco anos trabalhando no motor, percebi que o próprio Core ECS não é mais o foco deste projeto. Outras bibliotecas, e EnTT em particular, oferece uma API muito semelhante, com recursos muito mais avançados. Então, dediquei um tempo para estripar completamente os ECs internos e substituí -lo pelo EnTT . Todos os recursos permanecem os mesmos, e isso me dará mais tempo para trabalhar em ajudantes úteis, o que pode trabalhar com outros projetos EnTT .
Muitas partes do mecanismo (como os sistemas de script ou o editor de entidades Imgui) fazem uso da API de reflexão de putils . A maioria dos componentes nas amostras a seguir é, portanto, definida como refletível.
O código do motor está organizado em três categorias:
Observe que systems não são objetos de uma classe específica. Os sistemas são simplesmente entidades com um componente de execução (ou qualquer outra coisa que eles precisam para fazer seu trabalho). A entidade vive então no registry com o resto do estado do jogo. Isso permite que os usuários introsquem sistemas ou adicionem comportamento a eles como qualquer outra entidade.
Essas três categorias são divididas em várias bibliotecas, por exemplo:
Observe que algumas bibliotecas contêm sub-bibliotecas, por exemplo:
A seção CMake entra em mais detalhes sobre como trabalhar com essas bibliotecas.
O motor vem com um número (bastante grande) de componentes pré-criados que podem ser usados para inicializar um jogo, ou simplesmente como exemplos nos quais você pode basear suas próprias implementações.
Esses componentes se encaixam em três categorias:
Os componentes de dados mantêm dados sobre sua entidade.
Os componentes de dados são o que primeiro vem à mente ao pensar em um componente, como uma transformação ou um nome.
Às vezes, os componentes de dados podem manter funções:
Os componentes da função mantêm funções para consultar, alterar ou notificar sua entidade.
Os componentes da função são simplesmente detentores de funções que podem ser anexados como componentes das entidades. Este mecânico pode ser usado para:
Os componentes da função são tipos que herdam da Base_function, fornecendo a assinatura da função como um parâmetro de modelo.
Para chamar um componente de função, pode -se usar seu operator() ou sua função 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); // AlternativelyOs componentes de meta são componentes para componentes.
O motor usa "entidades de tipo" para manter informações sobre os vários componentes em uso. Cada entidade de tipo representa um tipo de componente diferente e pode ser usado para consultar as propriedades do componente em tempo de execução.
Os componentes dos meta -componentes são anexados a essas "entidades do tipo" e mantêm a implementação de uma função genérica para esse tipo específico. Como eles têm funções, são muito semelhantes aos componentes da função.
Um exemplo deixa isso mais claro: meta :: imgui :: editar é um componente de meta que, quando chamado, desenhará suas propriedades de "componente pai" usando o ImGui para a entidade dada. O código a seguir exibirá uma janela para editar o componente de nome do 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 ();Se você generalizar isso, pode editar todos os componentes para uma entidade com o seguinte 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 o Cmake para obter instruções sobre como ativar cada biblioteca.
A Generate_type_registration Script Python é fornecido, que pode ser usado para gerar arquivos C ++ contendo funções que registrarão um conjunto de tipos especificados com o mecanismo.
Isso não é absolutamente obrigatório .
O motor usa CMake como um sistema de construção. Uma estrutura personalizada foi implementada para simplificar a criação de bibliotecas. Os cmakelists raiz iteram sobre os subdiretos e os adicionam automaticamente como bibliotecas se corresponderem a algumas condições.
É criada uma biblioteca de interface kengine básica que vincula todas as bibliotecas habilitadas, para que os clientes possam simplesmente vincular a isso.
As seguintes opções de cmake são expostas.
KENGINE_TESTSCompila os executáveis de teste para as bibliotecas que implementam testes.
KENGINE_NDEBUGDesativa o código de depuração.
KENGINE_TYPE_REGISTRATIONGerará o código de registro de tipo para tipos de motor. Isso é central para muitos dos recursos de reflexão do motor, pois fornece a implementação para os componentes de meta.
KENGINE_GENERATE_REFLECTIONAtualizará os cabeçalhos de reflexão para os tipos de motor. Eles são pré-gerados; portanto, a menos que você esteja modificando o código-fonte do mecanismo, você não precisa ativar isso.
Todas as bibliotecas são desativadas por padrão, para evitar a criação de dependências indesejadas. Cada biblioteca pode ser ativada individualmente, definindo sua opção CMake para ON . Consulte a biblioteca nomeando o nome da opção.
Como alternativa, todas as bibliotecas podem ser ativadas com a opção KENGINE_ALL_SYSTEMS .
Observe que as sub-bibliotecas precisam que sua biblioteca pai seja ativada: Kengine_Imgui_entity_editor requer Kengine_Imgui.
As bibliotecas são nomeadas, dependendo do caminho relativo para a raiz do motor. As barras no caminho são simplesmente substituídas por sublinhados, por exemplo:
kengine_corekengine_imgui_toolEsses nomes são:
KENGINE_CORE para kengine_core )KENGINE_CORE_EXPORT para kengine_core )É possível testar a existência de uma biblioteca durante a compilação graças ao C ++ Definir macros. Eles têm o mesmo nome que as opções de cmake, por exemplo:
# ifdef KENGINE_CORE
// The kengine_core library exists
# endifAlgumas bibliotecas usam o VCPKG para gerenciamento de dependência.
Como as bibliotecas são detectadas automaticamente pelos CMakeLists.txt root, a criação de uma nova biblioteca é bastante fácil.
As bibliotecas se vinculam automaticamente contra kengine_core , pois fornece ajudantes que devem ser usados por todas as bibliotecas (como o LOG_HELPER e o Profiling_Helper).
As sub-bibliotecas se vinculam automaticamente aos pais. Por exemplo, Kengine_Imgui_entity_editor vincula automaticamente contra o kengine_imgui.
Os arquivos de origem dos subdiretórios de helpers e systems de uma biblioteca são adicionados automaticamente. Se nenhum for encontrado, a biblioteca será uma biblioteca de interface CMake.
O código de registro e reflexão do tipo pode ser gerado automaticamente para componentes. Por padrão, todos os cabeçalhos nos subdiretórios data e functions de uma biblioteca serão passados para os scripts de geração.
Da mesma forma que os arquivos de origem, se os arquivos *.tests.cpp forem encontrados nos subdiretórios de helpers/tests ou systems/tests de uma biblioteca, um executável do GoogleTest será adicionado automaticamente.
CMakeLists.txt personalizados.txt As bibliotecas básicas não devem precisar de seus próprios CMakeLists.txt , pois seus arquivos de origem serão automaticamente. No entanto, se uma biblioteca precisar de comportamento personalizado (por exemplo, adicionar fontes extras ou vincular uma biblioteca de terceiros), ela poderá adicionar seus próprios CMakeLists.txt . Esse CMakeLists.txt será chamado após a chamada para add_library .
As seguintes variáveis e funções são definidas antes de chamar os CMakeLists.txt :
kengine_library_name : o nome da bibliotecakengine_library_tests_name : o nome do alvo Googletest da bibliotecalink_type : o tipo de link da biblioteca ( PUBLIC ou INTERFACE , dependendo se as fontes foram encontradas ou não)kengine_library_link_public_libraries(libraries) : links contra outras bibliotecas (publicamente)kengine_library_link_private_libraries(libraries) : links contra outras bibliotecas (em particular)register_types_from_headers(headers) : adiciona cabeçalhos para o qual os cabeçalhos de registro e reflexão podem ser geradossubdirectory_is_not_kengine_library(path) : indica aos CMakeLists.txt que não deve processar path como uma biblioteca Kengine