Skip to content

Zenith Object Format

Adrian edited this page Feb 5, 2021 · 30 revisions

Overview

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.

Format Specification

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.

ZOFTree

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;
    }