-
Notifications
You must be signed in to change notification settings - Fork 3
ECS Parser
A parser is a function used to fill components
using json
files. Parsers are used by systems
to help configure themselves using json
files.
All the parsers should take a json
file path as parameter.
For example:
- A Position2DParser might open the
json
file and check for aPosition2DComponent
field in the file. If the field is found, the parser will check if bothx
andy
field can be found in the component field. If they're found, the parser will return an instance of thePosition2DComponent
with fulfilled parameters.
If you want to use a component but didn't create it, you can check the R-TypeGame/Parser/
folder and find the parser you want.
If you just wrote a component and want to create a parser for it, that's doable, by creating files using C++
If you haven’t already read the ECS Component section, please take a moment to review it.
In this tutorial, we'll create a Position2DParser
. This parser requires a single component: the PositionComponent. The parser will read a json and return an instance of this component.
First, create a Position2DParser
folder with a Position2DParser.hpp
file and a Position2DParser.cpp
one.
In a second part, we'll start by editing the header file:
As said previously, a parser is a function that takes a json
file path as parameter, and return an instance of the linked component:
#include "Position2DComponent.hpp"
#include <memory>
std::shared_ptr<Position2DComponent> parsePosition2D(std::string pathFile);
Then, we'll focus on the Position2DParser.cpp
file:
#include <fstream>
#include <iostream>
#include <exception>
#include <json/json.h>
#include "Position2DComponent.hpp"
std::shared_ptr<Position2DComponent> parsePosition2D(std::string pathFile)
{
try {
...
} catch {std::exception e) {
std::cerr << e.what() << std::endl;
}
}
Now, we'll go deep into the implementation of the parser.
Foremost, declare jsoncpp
variables and open the file
// Declare jsoncpp variables
std::ifstream file(pathFile);
Json::Reader reader;
Json::Value root;
if (!file.is_open()) // If the file is already open, we can't open it a second time
return nullptr;
if (!reader.parse(file, root, false)) // Check the format of the json file
return nullptr;
Then, catch the Position2DComponent
field in the json:
const Json::Value& position2D = root["Position2DComponent"];
if (!position2D) // Checks if the field is found or not
return nullptr;
Then catch the fields inside the Position2DComponent
one, and if they exist, return the needed instance:
const Json::Value& x = position2D["x"];
const Json::Value& y = position2D["y"];
if (x && y) // Checks if the fields are found or not
return std::make_shared<Position2DComponent>(x.asInt(), y.asInt());
return nullptr;
You now have your own parser, but...
A parser is used in initialization systems where entities need to be configured from json
files.
Here's an example of the system that initialize a shoot entity:
void ShootInitSystem::_initShoot(ECS::Registry& reg, int idxPacketEntities)
{
std::shared_ptr<TextureRectComponent> textureRect = parseTextureRect(PATH_JSON);
if (textureRect) {
reg.register_component<IComponent>(textureRect->getType());
reg.set_component<IComponent>(idxPacketEntities, textureRect, textureRect->getType());
}
std::shared_ptr<ScaleComponent> scale = parseScale(PATH_JSON);
if (scale) {
reg.register_component<IComponent>(scale->getType());
reg.set_component<IComponent>(idxPacketEntities, scale, scale->getType());
}
std::shared_ptr<Position2DComponent> position2D = parsePosition2D(PATH_JSON);
if (position2D) {
reg.register_component<IComponent>(position2D->getType());
reg.set_component<IComponent>(idxPacketEntities, position2D, position2D->getType());
}
std::shared_ptr<SpeedComponent> speed = parseSpeed(PATH_JSON);
if (speed) {
reg.register_component<IComponent>(speed->getType());
reg.set_component<IComponent>(idxPacketEntities, speed, speed->getType());
}
std::shared_ptr<VelocityComponent> velocity = parseVelocity(PATH_JSON);
if (velocity) {
reg.register_component<IComponent>(velocity->getType());
reg.set_component<IComponent>(idxPacketEntities, velocity, velocity->getType());
}
std::shared_ptr<ShootComponent> shoot = parseShoot(PATH_JSON);
if (shoot) {
reg.register_component<IComponent>(shoot->getType());
reg.set_component<IComponent>(idxPacketEntities, shoot, shoot->getType());
}
}
Here you can see, the all shoot entity is configured using the same json
file using parsers.
For the same example, here's the json of the shoot entity:
{
"TextureRectComponent":
{
"path":"./Assets/shots.gif",
"left": 250,
"top": 90,
"width": 17,
"height": 6
},
"ScaleComponent": 5,
"Position2DComponent":
{
"x": 100,
"y": 100
},
"SpeedComponent":
{
"x":5,
"y":5
},
"VelocityComponent":
{
"vx": 600,
"vy": 0
},
"ShootComponent":
{
"damage": 1,
"friendlyFire": true,
"type": "Player"
}
}