英文:
entrypoint in game engine
问题
I am asking myself, how you would achieve something in a game engine, where a user just defines one class and can compile. Maybe let me elaborate with some code, this would just be what the user of the game engine does:
我在思考,如何在游戏引擎中实现这样的功能,用户只需定义一个类并进行编译。也许让我用一些代码详细说明一下,这只是游戏引擎的用户所做的事情:
#include <engine.h>
class game : engine::application
{
void main_loop() override;
void initialize() override;
void destruct() override;
};
how would i achieve it, that the game engine could be compiled into a library or package of sorts, with the engine still being able to access an instance or something like the singleton of this game class. So first youd compile the engine, and then be able to use it with your game.
我该如何实现,使游戏引擎能够编译成库或包,同时引擎仍能够访问这个游戏类的实例或类似单例的东西。首先,您需要编译引擎,然后才能将其与您的游戏一起使用。
I've tried a method using the extern
keyword where the user just defines a function like this:
我尝试过一种使用 extern
关键字的方法,用户只需定义一个这样的函数:
engine::application* get_game() {return new game();}
but its quite messy to use, like i dont know why but you have to write code that uses this function in a header file, cause when you compile the code in a source file, it would not know of this function and say undefined and something. I also tried some research, but really found nothing on this topic.
但是使用这种方法会变得很混乱,我不知道为什么,但您必须在头文件中编写使用此函数的代码,因为当您在源文件中编译代码时,它不会知道这个函数,会报未定义的错误等。我也尝试过一些研究,但实际上在这个主题上找不到太多信息。
英文:
I am asking myself, how you would achieve something in a game engine, where a user just defines one class and can compile. Maybe let me elaborate with some code, this would just be what the user of the game engine does:
#include <engine.h>
class game : engine::application
{
void main_loop() override;
void initialize() override;
void destruct() override;
};
how would i achieve it, that the game engine could be compiled into a library or package of sorts, with the engine still being able to access an instance or something like the singleton of this game class. So first youd compile the engine, and then be able to use it with your game.
I've tried a method using the extern
keyword where the user just defines a function like this:
engine::application* get_game() {return new game();}
but its quite messy to use, like i dont know why but you have to write code that uses this function in a header file, cause when you compile the code in a source file, it would not know of this function and say undefined and something. I also tried some research, but really found nothing on this topic.
答案1
得分: 0
以下是您要翻译的内容:
您可以从现有的几个单元测试框架中获取灵感。通常有一种用于定义测试用例的宏。通常,这些宏会导致创建一个全局对象,该对象访问存储有关已注册单元测试的信息的单例对象(Boost测试和GoogleTest都是如此)。
在您的情况下,您只需要修改这个单例对象,游戏实现中所需的只是类似于以下内容(为此演示将application
简化为单个成员函数)。
game.cpp
#include <iostream>
#include "engine/engine.hpp"
class my_game : public engine::application
{
public:
void DoSomething() override
{
std::cout << "Hello World!\n";
}
};
ENGINE_GAME(my_game);
引擎实现
我在这里使用了一个CMake项目,并假设在Windows上使用MSVC(未经其他编译器/系统测试)。
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(EngineDemo)
# 使用相同的目录存储dll和exe,以避免查找dll时出现问题
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
include(GenerateExportHeader)
add_library(engine SHARED engine.cpp include/engine/engine.hpp)
target_include_directories(engine PUBLIC include ${CMAKE_CURRENT_BINARY_DIR}/include)
generate_export_header(engine BASE_NAME include/engine/engine EXPORT_MACRO_NAME ENGINE_EXPORT)
target_sources(engine PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include/engine/engine_export.h)
target_compile_features(engine PUBLIC cxx_std_17)
add_executable(game game.cpp)
target_link_libraries(game PRIVATE engine)
engine.hpp
#ifndef ENGINE_ENGINE_HPP
#define ENGINE_ENGINE_HPP
#include <cassert>
#include "engine/engine_export.h"
int ENGINE_EXPORT main();
namespace engine
{
template<class T>
struct application_registrar;
class application;
// std::unique_ptr没有dll接口,因此我们实现了自己的
class ENGINE_EXPORT application_ptr
{
application* m_ptr;
public:
application_ptr(application* app = nullptr) noexcept
: m_ptr(app)
{
}
~application_ptr();
application_ptr(application_ptr&& other) noexcept
: m_ptr(other.m_ptr)
{
other.m_ptr = nullptr;
}
application_ptr& operator=(application_ptr&& other) noexcept;
explicit operator bool() const noexcept
{
return m_ptr != nullptr;
}
application* operator->() const noexcept
{
assert(m_ptr != nullptr);
return m_ptr;
}
application& operator*() const noexcept
{
assert(m_ptr != nullptr);
return *m_ptr;
}
};
class ENGINE_EXPORT application
{
template<class T>
friend struct application_registrar;
friend int ::main();
static application_ptr s_applicationInstance;
public:
virtual ~application() = 0;
virtual void DoSomething() = 0;
static application& instance() noexcept
{
[[maybe_unused]] bool const applicationAlreadyRegistered = static_cast<bool>(application::s_applicationInstance);
assert(applicationAlreadyRegistered);
return *s_applicationInstance;
}
};
template<class T>
struct application_registrar
{
application_registrar()
{
[[maybe_unused]] bool const applicationAlreadyRegistered = static_cast<bool>(application::s_applicationInstance);
assert(!applicationAlreadyRegistered);
try
{
application::s_applicationInstance = application_ptr(new T());
}
catch (...)
{
assert(!"an exception was thrown initializing the application");
throw;
}
}
};
inline application_ptr::~application_ptr()
{
delete m_ptr;
}
inline application_ptr& application_ptr::operator=(application_ptr&& other) noexcept
{
delete m_ptr;
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
return *this;
}
} // namespace engine
/**
* 用于注册游戏的宏
*/
#define ENGINE_GAME(type) \
namespace EngineGameRegistrationImpl \
{ \
static ::engine::application_registrar<type> g_gameRegistrator##type##__LINE__ ; \
}
#endif // ENGINE_ENGINE_HPP
希望这可以帮助您理解这段代码的内容。如果您需要进一步的解释或翻译,请随时提问。
英文:
You could take inspiration from several unit test frameworks out there. Usually there's some kind of macro for defining test cases. Usually those result in the creation of a global object that accesses a singleton storing the info about registered unit tests. (Boost test and GoogleTest both do this.)
In your case you'd just need to modify this for a single instance and all you need to do in the game implementation is something like this (application
reduced to a single member function for this demo).
game.cpp
#include <iostream>
#include "engine/engine.hpp"
class my_game : public engine::application
{
public:
void DoSomething() override
{
std::cout << "Hello World!\n";
}
};
ENGINE_GAME(my_game);
Engine Implementation
I'm using a cmake project here and assume usage of MSVC for Windows (untested for other compilers/systems).
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(EngineDemo)
# use the same dir for dlls and exes to avoid issues with finding the dll
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
include(GenerateExportHeader)
add_library(engine SHARED engine.cpp include/engine/engine.hpp)
target_include_directories(engine PUBLIC include ${CMAKE_CURRENT_BINARY_DIR}/include)
generate_export_header(engine BASE_NAME include/engine/engine EXPORT_MACRO_NAME ENGINE_EXPORT)
target_sources(engine PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include/engine/engine_export.h)
target_compile_features(engine PUBLIC cxx_std_17)
add_executable(game game.cpp)
target_link_libraries(game PRIVATE engine)
engine.hpp
#ifndef ENGINE_ENGINE_HPP
#define ENGINE_ENGINE_HPP
#include <cassert>
#include "engine/engine_export.h"
int ENGINE_EXPORT main();
namespace engine
{
template<class T>
struct application_registrar;
class application;
// std::unique_ptr has no dll interface, so we implement our own
class ENGINE_EXPORT application_ptr
{
application* m_ptr;
public:
application_ptr(application* app = nullptr) noexcept
: m_ptr(app)
{
}
~application_ptr();
application_ptr(application_ptr&& other) noexcept
: m_ptr(other.m_ptr)
{
other.m_ptr = nullptr;
}
application_ptr& operator=(application_ptr&& other) noexcept;
explicit operator bool() const noexcept
{
return m_ptr != nullptr;
}
application* operator->() const noexcept
{
assert(m_ptr != nullptr);
return m_ptr;
}
application& operator*() const noexcept
{
assert(m_ptr != nullptr);
return *m_ptr;
}
};
class ENGINE_EXPORT application
{
template<class T>
friend struct application_registrar;
friend int ::main();
static application_ptr s_applicationInstance;
public:
virtual ~application() = 0;
virtual void DoSomething() = 0;
static application& instance() noexcept
{
[[maybe_unused]] bool const applicationAlreadyRegistered = static_cast<bool>(application::s_applicationInstance);
assert(applicationAlreadyRegistered);
return *s_applicationInstance;
}
};
template<class T>
struct application_registrar
{
application_registrar()
{
[[maybe_unused]] bool const applicationAlreadyRegistered = static_cast<bool>(application::s_applicationInstance);
assert(!applicationAlreadyRegistered);
try
{
application::s_applicationInstance = application_ptr(new T());
}
catch (...)
{
assert(!"an exception was thrown initializing the application");
throw;
}
}
};
inline application_ptr::~application_ptr()
{
delete m_ptr;
}
inline application_ptr& application_ptr::operator=(application_ptr&& other) noexcept
{
delete m_ptr;
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
return *this;
}
} // namespace engine
/**
* macro for registering the game
*/
#define ENGINE_GAME(type) \
namespace EngineGameRegistrationImpl \
{ \
static ::engine::application_registrar<type> g_gameRegistrator##type##__LINE__ ; \
}
#endif // ENGINE_ENGINE_HPP
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论