-
Notifications
You must be signed in to change notification settings - Fork 2
Zenith Object Format
Zenith was built with data driven design in mind. It makes use of a custom objects format, the Zenith Object Format (zof for short), to specify a scene’s elements, so that recompilation is not a burden if we decide to modify object properties or add components to a game object.
Zenith object files, those with a .zof extension, can be parsed within the engine using the ZEngine::LoadZOF
function, as described in the Engine page.
The current implementation uses a simple scanner and recursive descent parser to populate a ZOFTree structure with all object data. This tree can then be walked to retrieve the needed data.
All objects in a zof file should be delimited by an initial identifier and a terminating ^
caret. Properties are delimited between two colons. Here's an example of simple game object:
ZGO_05
:position [10.0, 10.0, 0.0]:
:scale [3.0, 3.0, 3.0]:
:enabled No:
^
White space, new lines and carriage returns are safely ignored, so we are just as well writing:
ZGO_05 :position [10.0, 10.0, 0.0]: :scale [3.0, 3.0, 3.0]: :enabled No: ^
Although this is clearly not as readable.
Note the use of brackets for lists. We can create lists from numbers, strings and identifiers, but a list may only contain items of a single type:
ZGO_15
:strings ["This is a string", "This is not a string, but a lie"]:
:ids [an_identifier, no_quotes_needed, but_no_spaces_allowed]:
:numbers [0.0, 7.0, 7.0, 3.0, 4.0]:
^
Note that we can also include comments. The zof parser will ignore anything after a #
character.
We can nest one object in another by simply remembering to delimit appropriately with a ^
. This is also how we include components in an object:
ZGO_23
:prop "Something relevant":
GraphicsComponent
:model "a/path/to/a/model":
:shaders [ZSH_01]:
^
^
Built in Zenith components are specified in .zof files be omitting the initial Z from the class name (e.g. GraphicsComponent
, CameraComponent
, PhysicsComponent
, ScriptComponent
).
Note that for certain objects and properties, using an id as the property value acts as a reference to the object with that id in the final parse result. The shaders property above, for example, will reference an object named ZSH_01 found in the same file.
Zenith follows certain identifier conventions to resolve the different object types. All game object identifiers should start with ZGO
, all shaders with ZSH
, all textures with ZTEX
, all scripts with ZSCR
and all UI elements with ZUI
. Refer to demo_scene.zof for a more complete object file example.
The internal ZOFParser class parses .zof files to build a tree representation that is stored in a ZOFTree struct instance. A ZOFTree is really just an alias for a ZOFNode which stores a pointer to a root ZOFNode as well as a list of pointers to child nodes. We can use the ToString
method to inspect the contents of a node:
LOG(node->ToString(), ZSeverity::Info);
Most objects parsed will be stored in the ZOFObjectNode subclass. This subclass contains a properties
member that is used to access a properties map, where the key is the property name and the value a ZOFPropertyNode pointer. We can therefore access properties simply by subscripting the properties
member:
std::shared_ptr<ZOFPropertyNode> prop = objNode->properties["yourProp"];
We can use the templated Value
method of the resulting ZOFPropertyNode to fetch the value associated with the property:
auto val = prop->Value<ZOFNumber>(0);
The numeric parameter passed in to the function specifies the position of the value to fetch, as we can have multiple values for a given property. The template argument is a special value type, ZOFValueTerminal, which is itself a templated wrapper type that allows us to store any kind of value we'd like. Currently, the engine comes with four specializations of ZOFValueTerminal:
- ZOFNumber
- ZOFString
- ZOFNumberList
- ZOFStringList
The above call tells the property node to fetch the first ZOFNumber value available.
Subsequently, we can query the underlying value of the ZOFValueTerminal by using its value
member:
float num = val->value; // Assuming val is a ZOFNumber
Here's a complete example from the ZCamera class that populates the camera's movement speed based on a ZOFObjectNode property
ZOFPropertyMap props = node->properties;
if (props.find("speed") != props.end() && props["speed"]->HasValues())
{
std::shared_ptr<ZOFNumber> prop = props["speed"]->Value<ZOFNumber>(0);
movementSpeed_ = prop->value;
}