Spec Oracle Editor
The Spec Oracle Editor is a way to edit Spec Oracles, which are lists of definitions for different types of objects or concepts (for instance, game objects, abilities, loot types, or whatever your game needs). There are some implementations of Spec Oracles that are included with HeroEngine (such as the Fx System, which has a customized Fx Spec Editor), and you can leverage the Spec Oracle system to create your own. Essentially this serves as the foundation of building blocks for your game system editors, and takes care of a lot of the busy work for you.
The Spec Oracle Editor is comprised of two basic controls:
- The Spec Selector Control, which displays a list of all specs for a particular spec oracle, allowing the creation of new specs, and invoking of an editor for existing specs
- The Spec Editor Control, an extensible generic node property editor which assembles itself dynamically through DOM reflection (i.e., analysis of the classes and fields that make up the spec)
The Spec Selector Control
The Spec Selector Control is accessed via the HotSpot Menu. Click on the "Tools" tab, and then choose the Oracle you wish to edit under the "Spec Oracles" heading. That will launch the Spec Selector Control, with which you can Add, Edit (View), Glom, or Clone the individual Specs. The list can be sorted by Key, Name, or Description, by clicking on the appropriate heading.
Alternately, it may be launched using the chat command /heoracle.
/heoracle open <specoraclename> example: /heoracle open _fxspecoracle
The Spec Editor Control
|list of <scaler>||_nodePropertyEditorCellList note: editing existing values only, adding new is not currently supported|
|lookuplist indexed by <X> of <scalar>||_nodePropertyEditorCellTextInputBox note: editing existing values only, adding new is not currently supported|
When the Spec Selector Control initiates editing for a spec , the control calls the method InvokeSpecEditorGUI() on the spec and if not overridden the fundamental parent class for all specs 'baseSpec handles it by invoking the generic editor. Consequently, every spec has the opportunity to override this method to invoke a custom GUI, and example of which would be FX Specs which invoke the highly customized FX Editor.
By default, specs use the generic node property editor control (GUI prototype: _NodePropertyEditorWindow) which knows how to dynamically construct a property grid of controls using the external functions for DOM reflection (i.e. analyzing the class and field definitions of your data object model). Each field is represented by a row control which contains two cells, one for the field name and one for the field value. The behaviors for this control are implemented in the _GUINodePropertyEditorClassMethods script and a number of _GUINodePropertyEditorCell<type> classes.
Many specs eventually have field types that are not handled by default necessitating the creation of custom cell controls to display and edit the field.
Creating Custom Value Cells
- Create a new Class that inherits from _GUINodePropertyEditorCell and is of archetype guicontrol
- Create a new GUI Control using the new class
- Override the commonly replaced behaviors including; _getCollectionCellHeight(), _updateNodeFieldWithCellValue(), and _setCollectionCellValueFromNodeRefByField()
The method _getCollectionCellHeight() is often overridden because the custom editor cell requires a complex calculation to determine the necessary space for the control.
method _getCollectionCellHeight() as Float return 20.0 .
The _setCollectionCellValueFromNodeRefByField() method is called by the node property editor to notify the cell it needs to update its displayed value (typically during initialization). Custom controls must always override this method because only the control knows how to update its structure.
// Sample code is from boolean cell _GUINodePropertyEditorCellBoolean method _setCollectionCellValueFromNodeRefByField( fieldName as String ) theNode as NodeRef = me._getNodePropertyEditorEditNode() me._GUINodePropertyEditorCellFieldName = fieldName dropDownBox as NodeRef of Class _GUIDropDownBox = FindGUIControlByName( me, "dropDownBox" ) values as List of String = dropDownBox._getDropDownBoxValues() if values.length < 2 // need to init this so add true and false add back true to values add back false to values dropDownBox._populateDropDownBox( values ) . value as Boolean var fieldType = GetFieldType( fieldName ) when tolower( fieldType ) is "boolean" value = theNode.fieldCollection[ fieldName ] . default value = "false" . . dropDownBox._setDropDownBoxValue( value ) .
The _updateNodeFieldWithCellValue() method is called by the editor to notify the cell that it needs to update the node with the displayed value. Custom controls must always override this method as only the control itself knows how to update the a field (of a particular type) based on the cells structure.
// Sample code is from boolean cell _GUINodePropertyEditorCellBoolean method _updateNodeFieldWithCellValue() dropDownBox as NodeRef of Class _GUIDropDownBox = FindGUIControlByName( me, "dropDownBox" ) var fieldType = me._getNodePropertyEditorCellLocalFieldType() if not me._getNodePropertyEditorCellReadOnly() var theNode = me._getNodePropertyEditorEditNode() value as Boolean = dropDownBox._getDropDownBoxValue() when tolower( fieldType ) is "boolean" b as Boolean = theNode.fieldCollection[ me._GUINodePropertyEditorCellFieldName ] if b <> value theNode.fieldCollection[ me._GUINodePropertyEditorCellFieldName ] = value . . . . .
Using a Custom Cell
During the analysis of a spec using DOM Reflection, the generic node property editor provides the classes comprising a node the opportunity to specify alternate editing controls. This is accomplished through a series of shared function calls allowing modification of the default behaviors. A template script (_templateNodePropertyEditorOverrides) is included detailing the shared functions used by the node property editor during its dynamic construction.
To use a custom cell, simply implement the shared function _createNodePropertyEditorCellForField() in the class methods script for the class of which the field is a member.
shared function _createNodePropertyEditorCellForField( fieldName as String ) as NodeRef of Class _GUINodePropertyEditorCell editCell as NodeRef of Class _GUINodePropertyEditorCell when tolower( fieldName ) is "<yourFieldName>" editCell = CreateNodeFromPrototype( "_NodePropertyEditorCellIDField" ) editCell.build = true . . return editCell .
Replacing the Spec Editor Control
For some types of specs, the generic editor (while extremely extensible) may simply not fit requirements. Replacing the generic editor with a custom one requires you implement the method InvokeSpecEditorGUI() in the spec class(es) used by your spec oracle. The FX Specs use this mechanic to replace the default editor with the highly customized FX Editor.
// This displays the code used by default and is implemented in the baseSpecClassMethods script method InvokeSpecEditorGUI( parentGUI as ID ) as NodeRef of Class GUIControl // Invokes the Edit GUI for a particular spec, override this method if you want to invoke a gui other than the generic // specOracle property editor // window as NodeRef of Class GUIControl = CreateNodeFromPrototype("_NodePropertyEditorWindow") window.build = true glomClass("SpecOraclePropertyEditor", window) where window is kindof SpecOraclePropertyEditor window.SOPEparentGUI = parentGUI window.SOPEtype = me.GetMySpecOracle().getSpecOracleClass() window.NPEnode = me . window._changeWindowTitleText( "Spec Editor - " + getPrimaryClassOnNode( me ) + "(" + me.SpecKey + ")" ) var editor = FindGUIControlByName(window.getClientarea(), "nodepropertyeditor") editor._setNodePropertyEditorEditNode(me) editor._allowTitlebar(false) editor.centerControlOver(None) return editor .