Defining a Custom Property
From C4 Engine Wiki
In the C4 Engine, a property refers to any special piece of information that can be attached to a scene graph node. A property is represented by a subclass of the Property class and can contain as little information as its type alone, or it can contain several individual settings.
Contents |
Defining a Custom Property
An application can define custom properties, and they can be registered with the engine so that the World Editor knows about them. The first thing that a custom property needs is a type identifier, which is a 32-bit number normally represented by a four-character code. In this article, we will use the example of a property called ColorProperty that encapsulates a color value. We can define its type as follows.
enum { kPropertyColor = 'colr' };
(Type identifiers consisting of only uppercase letters and numbers are reserved for use by the engine. Anything else is okay for an application to use.)
Next, the property subclass needs to be defined. We declare the ColorProperty class to be a subclass of the Property class as follows, and we include a single data member to hold the color value. Several member functions are included to handle serialization and user interface—these are discussed below.
class ColorProperty : public Property { private: ColorRGB theColor; ColorProperty(const ColorProperty& colorProperty); Property *Replicate(void) const; public: ColorProperty(); ColorProperty(const ColorRGB& color); ~ColorProperty(); const ColorRGB& GetColor(void) const { return (theColor); } void SetColor(const ColorRGB& color) { theColor = color; } static bool ValidNode(const Node *node); // Serialization functions void Pack(Packer& data, unsigned long packFlags) const; void Unpack(Unpacker& data, unsigned long unpackFlags); // User interface functions long GetSettingCount(void) const; Setting *GetSetting(long index) const; void SetSetting(const Setting *setting); };
The constructor and destructor for this example would typically be implemented as follows.
ColorProperty::ColorProperty() : Property(kPropertyColor) { // Set a default value theColor.Set(1.0F, 1.0F, 1.0F); } ColorProperty::ColorProperty(const ColorRGB& color) : Property(kPropertyColor) { theColor = color; } ColorProperty::~ColorProperty() { }
Notice that the property's type kPropertyColor is passed to the base class's constructor.
The default constructor only needs to set a default value if the property will be exposed in the World Editor. Otherwise, the default constructor will only be called right before the property is deserialized, thus filling in the data members.
The copy constructor and the Replicate function must be included if you want the property to be copied when any node that it's attached to gets duplicated in the World Editor. For this example, the copy constructor would be implemented as follows.
ColorProperty::ColorProperty(const ColorProperty& colorProperty) : Property(colorProperty) { theColor = colorProperty.theColor; }
This time, the reference to the colorProperty object is passed to the base class's constructor.
The Replicate function simply constructs a new instance of the property using the copy constructor. This would usually be implemented as follows.
Property *ColorProperty::Replicate(void) const { return (new ColorProperty(*this)); }
Property Registration
If you want your custom property to appear in the World Editor, then it must be registered with the engine. This is accomplished by creating a PropertyReg object. The property registration contains information about the property type and its name, and it's mere existence registers the property type that it represents. A property registration for the ColorProperty class would normally look like the following.
// Define the property registration PropertyReg<ColorProperty> colorPropertyReg;
The registration object is initialized with the following code.
colorPropertyReg(kPropertyColor, "Glow color");
The ValidNode function declared in the ColorProperty class is used by the World Editor to determine what kind of node the property was meant to be assigned to. If this function is not included in the class definition, then the property can be assigned to any node. Otherwise, the ValidNode function should return true when it's okay to assign the property to the node passed to it, and false if it's not okay. As an example, if we only wanted ColorProperty objects to be assigned to Geometry nodes, then we would implement the ValidNode function as follows.
bool ColorProperty::ValidNode(const Node *node) { return (node->GetNodeType() == kNodeGeometry); }
Notice that the ValidNode function is declared static. The World Editor calls this function automatically when it needs to know whether the property can be used with a specific node.
Serialization
A custom property must implement the Pack and Unpack functions so that its data can be written to a file and later restored. (These functions override the virtual functions in the Packable class.) Each of these functions needs to first call its counterpart in the Property base class. For the ColorProperty example, these functions would typically be implemented as follows.
void ColorProperty::Pack(Packer& data, unsigned long packFlags) const { Property::Pack(data, packFlags); // Write the ColorRGB object data << theColor; } void ColorProperty::Unpack(Unpacker& data, unsigned long unpackFlags) { Property::Unpack(data, unpackFlags); // Read the ColorRGB object data >> theColor; }
User Interface
The Property class is a subclass of the Configurable class, which means it can expose a user interface that appears in the World Editor. The Property object is queried by the World Editor for its configurable settings when the user selects the property. The GetSettingCount function returns the number of individual settings that need to be shown, and the settings are retrieved or changed using the GetSetting and SetSetting functions. For the ColorProperty example, there is one setting representing the color stored in the property. The user interface to change this color would be implemented as follows.
long ColorProperty::GetSettingCount(void) const { // There's only one setting return (1); } Setting *ColorProperty::GetSetting(long index) const { // Is it asking for the first setting? if (index == 0) { // Yes, return a new color setting return (new ColorSetting('glow', theColor, "Glow color", "New glow color")); } return (nullptr); } void ColorProperty::SetSetting(const Setting *setting) { // Are we setting the glow color? if (setting->GetSettingIdentifier() == 'glow') { // Yes, grab the RGB color from the setting theColor = static_cast<const ColorSetting *>(setting)->GetColor().GetColorRGB(); } }
The first parameter passed to the ColorSetting constructor ('glow') is just an identifier that the property uses to keep track of which setting is which—it can be anything you want. The second parameter is the color that will initially be shown to the user. The third parameter is the title of the setting that, in this case, will be displayed next to the color box. The last parameter is the title that will be used for the color picker dialog when the user clicks on the color box.
Documentation Links
-
Propertyclass -
PropertyRegclass -
Packableclass -
Configurableclass -
Settingclass
