I am fairly new to c++ and am attempting to write a simple 2D game engine. I am currently working on my object model: a pure component system, similar to that described in the Data Locality chapter in Game Programming Patterns, whereby the components are fixed types, each stored in a separate array in the game world class. My current code looks a bit like this:
#include <iostream>
#define SID(str) HashString(str)
typedef unsigned long long StringID;
// Simple FNV string hash.
StringID HashString(const char* str) {
const unsigned int fnv_prime = 0x811C9DC5;
unsigned int hash = 0;
unsigned int i = 0;
unsigned int len = strlen(str);
for (i = 0; i < len; i++) {
hash *= fnv_prime;
hash ^= (str[i]);
}
return hash;
}
// Component base class
class Component
{
public:
static const int MAX_COMPONENTS = 10;
Component() {};
virtual ~Component() {};
virtual void Update() = 0;
StringID mID;
};
// Basic 2D vector.
struct Vec2 {
float x;
float y;
};
// Component for storing world position.
class TransformComponent : public Component
{
public:
TransformComponent() {}
TransformComponent(float x, float y) {
Vec2 mPosition = { x, y };
}
virtual ~TransformComponent() {};
virtual void Update() { /* No need to update- only to store data */ };
private:
Vec2 mPosition;
};
// A struct for storing components in a contiguous way.
struct Components
{
int CurrentTransformComponents = 0;
TransformComponent* TransformComponents = new TransformComponent[Component::MAX_COMPONENTS];
};
// Variarg function to add all listed components
template <typename T>
inline void AddComponents(StringID gameObjectID, T component) {
Application* app = Application::GetApplication();
std::cout << "Adding component..." << std::endl;
// Ugly placement of componet in array :)
if (typeid(T) == typeid(TransformComponent)) {
component.mID = gameObjectID;
// Add component to the correct place in the array;
app->mComponents.TransformComponents[app->mComponents.CurrentTransformComponents] = component;
++app->mComponents.CurrentTransformComponents;
}
}
template <typename Arg, typename ... Args>
inline void AddComponents(StringID gameObjectID, Arg first, Args ... args) {
AddComponents(gameObjectID, first);
AddComponents(gameObjectID, args...);
}
// Adds componets AND returns ID.
template <typename ... Args>
inline StringID CreateGameObject(const char* name, Args ... args) {
std::cout << "Creating GameObject " << name << " ";
StringID id = SID((char*)name);
std::cout << "Hash ID is " << id << std::endl;
AddComponents(id, args...);
return id;
}
// A base app class.
// This is a singleton :(
class Application
{
template <typename T>
friend void AddComponents(StringID gameObjectID, T component);
public:
Application() : mComponents() {
if (mApplication != nullptr) {
std::cout << "Application already exists. You can only create 1 Application" << std::endl;
}
mApplication = this;
}
virtual ~Application() {}
static Application* GetApplication() { return mApplication; }
// Debug run to check components have been added correctly.
void Run() {
StringID testObject1ID = CreateGameObject("testObject1", TransformComponent(0, 0));
StringID testObject2ID = CreateGameObject("testObject2", TransformComponent(0, 0), TransformComponent(10, 10));
std::cin.get();
}
private:
Components mComponents;
static Application* mApplication;
};
Application* Application::mApplication = nullptr;
class TestGame : public Application {
};
int main() {
TestGame* testGame = new TestGame();
testGame->Run();
delete testGame;
return 0;
}
Pros:
- It is cache-friendly
- It is relatively flexible
Cons:
- Template functions are slow
- The repeated
typeid
is very bad :)
I don't know if using variarg templates is the best option, because it means i have to use all of those typeid
s. Also, I feel like the variarg template functions aren't the best either, however the only alternatives I can think of are functions for each component, eg.
void AddTransformComponent(StringID gameObjectID, TransformComponent component);
or overloaded functions, such as
void AddComponent(StringID gameObjectID, TransformComponent component);
If there is any code which you need which is missing, please say.
Thanks for the help, and i would appreciate any advice.
//Data...
or/* Data args... */
? Can't I readc++
or have you removed anything? On Code Review you need to post complete code. \$\endgroup\$Application
class? This construct looks like a way to try to legitimize global variables. Why not put the code inRun
inmain
or at least a free function? \$\endgroup\$