I've tried out more complex classes and here are the results.
Animation class stays the same, let's add GraphicsComponent class:
class GraphicsComponent {
public:
GraphicsComponent();
Json::Value serializeAnimations() const;
void deserializeAnimations(const Json::Value& root);
static void registerClass(MetaBuilder<GraphicsComponent>& builder);
private:
std::string filename;
std::map<std::string, Animation> animations;
Animation specialAnimation;
};
Here's how register function looks:
void GraphicsComponent::registerClass(MetaBuilder<GraphicsComponent>& builder)
{
builder.add("filename", &GraphicsComponent::filename);
builder.add<decltype(animations)>("animations");
auto& animationsProperty = builder.get<decltype(animations)>("animations");
animationsProperty.setMemberSerializer(&GraphicsComponent::serializeAnimations);
animationsProperty.setMemberDeserializer(&GraphicsComponent::deserializeAnimations);
builder.add("specialAnimation", &GraphicsComponent::specialAnimation);
}
Here's how JSON file for this class looks (all I have to do to get this is call
Meta<GraphicsComponent>::serialize) :
{
"animations" :
[
{
"frame" : [ 0, 32, 32, 32 ],
"frameCount" : 30,
"isLooped" : true,
"name" : "idle"
},
...
],
"filename" : "res/hero.png",
"specialAnimation" :
{
"frame" : [ 0, 64, 32, 32 ],
"frameCount" : 20,
"isLooped" : false,
"name" : "megaAttack"
}
}
Serializing/deserializing map of animations is tricky, because it's array in JSON and we want to use animation names as map keys in C++. So, let's add functions which will be called each time Meta<GraphicsComponent> tries to serialize/deserialize animations map:
Json::Value GraphicsComponent::serializeAnimations() const
{
Json::Value animationsArray;
for (const auto& animationPair : animations) {
auto& animation = animationPair.second;
animationsArray.append(Meta<Animation>::serialize(animation));
}
return animationsArray;
}
void GraphicsComponent::deserializeAnimations(const Json::Value& root)
{
for (int i = 0; i < root.size(); ++i) {
Animation a;
Meta<Animation>::deserialize(a, root[i]);
animations[a.getName()] = a;
}
}
Great! What about
Animation specialAnimation? If no functions are provided,
Meta<T>::serialize /
Meta<T>::deserialize get called. If the meta class was not explicitly created for the T (using T::registerClass function) , it will have no properties, so the first function will return
Json::nullValue and the second function will return
T().
As for MetaWrapper object, here's how useful it is:
auto metaGc = Meta<GraphicsComponent>::getMeta(gc);
metaGc.set("filename", std::string("res/test.png"));
int frameCount = metaGc.get<int>("frameCount");
The code is much cleaner and easier to read!
Oh, and as Laurent suggested, doing this is possible:
builder.add<int>("frameCount");
auto& frameCountProperty = builder.get<int>("frameCount");
frameCountProperty.setGetter(&Animation::getFrameCount);
frameCountProperty.setSetter(&Animation::setFrameCount);
As you can see, there's no need to provide
&Animation::frameCount for this property. What happens if I don't provide setter and try to call
set for this property? (Or try to get some property without getter?) Assertion tell me about it ("Forgot to add setter/getter!"), and everything is alright
(if I don't use assertions, C++ silently writes to nullptr and the stack gets corrupted!)
And now I'm getting back to work on Animation tool... which will later become Entity Creation Tool!
But I'll have to rewrite a lot of stuff to store all component data in JSON instead, so this will take a while. I'll try not to stop working on the game and will draw art and think about levels/mechanics in parallel.