Architectural overview

Introduction

A games team is faced with the question how the models, textures, animations and code created during a production are put together to one game. At Spielwerke, we call the process of putting everything together "game composition" or just "composition". Composition is usually done by custom-made proprietary tools. Chief among those is normally a "Level Editor" or "Scene Editor, which might be a stand-alone tool or a complex plug-in for a 3d package. Other composition tools can include tools to add tags to keyframes in animations or elements of geometry to be used later by the game logic or even the tools used in the build process.

Writing composition tools is a major effort and often a lot of this effort can only be used for one game or one engine. This is not by accident: writing reusable composition tools or even reusable composition tool components is complicated by the fact that useful composition tools need to have intricate knowledge about the data they are editing, and this data is often very closely tight to not only the game engine but also the game itself.

As an example, for a lot of games, the composition data mainly consists out of "entities" and each entity has a type, which is directly corresponding to an entity type in the game. An editor used to place down and modify those entities needs to have more than a little knowledge about those entity types to be an efficient tool in the hands of a games team. You can find more detail on why we think it is beyond the scope of a normal games production or even a moderately sized game studio to write truly efficient and reusable composition tools components, in the talk we held on the Develop Conference available on our website www.spielwerke.com.

In our view, writing reusable composition tool components is in the light of this tight coupling of the tools and the game only possible if the tool components are all based on one common, game- and engine-agnostic data API. At the same time, this data API must be able to transport game-specific information to the tools components, to allow customisation as required. Seeing that this is a task which at the same time is game/engine-independent as well as requiring significantly more effort than available in a game production or even a moderately size game studio, we created the Spielwerke Libraries to allow games teams to write and use the best possible tools for their game.

Pattern for reusable tools components

Spielwerke Libraries provides a platform for to write reusable composition tools efficiently. The essential pattern of tools written with the Spielwerke Libraries can be seen in figure 1. This structure ensures the reusability of the tools components: the tools component uses the data API to retrieve all data which it treats generically (e. g. a property tree would use the data API to get the list of properties in an object), and it uses its tools interfaces implemented by attributes to get all information which goes beyond that (e. g. a slider would use an attribute to get its range, or a 2d/3d view would use an attribute to get the path to the 3d model to be displayed for an entity). This allows to reuse the tools component in different contexts, either by just reconfiguring the attributes or by writing a new attribute implementing the interface.

PatternForToolsComponents
Figure 1 - The standard pattern for tools components

Please note that the red code dependencies depicted in figure 1 between the components are "static" or "code" or "syntactic" dependencies, i. e. if component A dependents component B, A needs B to be built. Due to the nature of reflection, it is important to note that there are also "dynamic" or "data" or "semantic" dependencies. These arise if a component requires another component to fulfill certain conditions during runtime - e. g. the attributes implementing the tools component interfaces will be often configured to use properties with a specific name to retrieve the data required for the tools component, and if the property required is not available in the data, a runtime error will occur. For reusable tools components, it is important to ensure that those kind of dependencies only exist between the attributes and the data - that is, the tools component itself makes no assumption about the data structure it is using but instead uses attributes when this kind of information is required.

The data API, data consumers and data providers

The data API is the hinge between the tools components which display the data to the user and allow him to manipulate it - the data consumers, examples are the UI controls and the 2d/3d view - and the tools components which provide the data - the data providers, examples is the entity system or the live connection to a game.

All data is provided to the data consumers via the same structure: properties which have a name, a type, attributes and carry a value, which in turn can again have properties. This creates a property tree - see figure 2 for an illustration. The central interface for the data consumers is the Spielwerke.Data.PropertyHandle struct.

: Property
  • Name : string
  • PropertyType : Type
  • Attribute[0] : Attribute
  • ...
  • Attribute[N] : Attribute
  • Value : object
    • Property 0 : Property
      • Name : string
      • PropertyType : Type
      • ...
    • ...
    • Property M : Property
      • ...
Figure 2 - The property tree

The data providers on the other hand are accessed by the Spielwerke.Data.PropertyHandle in two possible ways:

  1. Via .NET reflection - all public properties are accessible to the property consumers, this is normally only used for types which are used by one of the property consumers directly, e. g. a Microsoft.DirectX.Vector3 or a Microsoft.DirectX.Matrix,
  2. or if the property container implements System.ComponentModel.ICustomTypeDescriptor, it can provide arbitrary properties to the property consumers. This is primarily used by property providers which are configured during runtime, e. g. the Spielwerke EntitySystem.

So the data API falls on a closer look into two parts: on one hand the PropertyHandle class which is the API used by data consumers and the reflection API used by the PropertyHandle to retrieve the data, involving System.ComponentModel.TypeDescriptor, System.ComponentModel.PropertyDescriptor and System.ComponentModel.ICustomTypeDescriptor. Usually, a user of the Spielwerke Libraries would use the Spielwerke EntitySystem as a data provider and focus on the data consumer side and the attributes while writing the tools.

Attributes in the Spielwerke Libraries

Attributes are used in the Spielwerke Libraries to provide the data consumers with information beyond the generic property structure. Attributes either implement interfaces used by data consumers or are used directly by data consumers.

Attribute classes and the interfaces they implement always belong to the data consumer and are defined by the data consumers. This ensures the reusability of the data consumer for different data structures. Often, changing the configuration of the existing attributes will be sufficient to make a new data structure "digestible" to an existing data consumer.

However, sometimes a new implementation of the attribute might be required - e. g. the interface Spielwerke.Attributes.Graphics.Visualizations.IBoxData, is used to provide data to the bounding box visualization in the 2d/3d view. The implementations of the interface (the classes Spielwerke.Attributes.Graphics.Visualizations.BoxData and Spielwerke.Attributes.Graphics.Visualizations.BoxAttribute) are currently only catering for the case when the position of the box is available as a child property of type Microsoft.DirectX.Matrix. For other cases, additional implementations of the versions of Spielwerke.Attributes.Graphics.Visualizations.IBoxData would have to be added.

The data transforms

Tools components which are used to restructure the data and hence at the same time use the data consumer and the data provider API are called "Data Transforms". They can be used to provide the user different views on the data structure without actually reorganizing it. Data transforms can be found in the Spielwerke.Data.Transform namespace.

Examples for transforms are the multiplexer which allows the data consumer to see a summary of several property containers at once and to manipulate them all at the same time, and the property grouping which can be used to add an additional level in the hierarchy of a property tree for the property consumer.

Depending on the complexity of the restructuring operation, you might want to consider to store the result of the operation in a persistent new property tree.

Namespace structure

The Spielwerke namespaces follow certain rules:

Further reading