User Tools

Site Tools


besiege:modding:mappertypes

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

besiege:modding:mappertypes [2018/06/18 16:54] (current)
von created
Line 1: Line 1:
 +====== Custom Mapper Types ======
  
 +The different element types displayed in the block mapper (and for entities, ''​GenericDataHolder''​s etc.) are called mapper types. Using the ''​CustomMapperTypes''​ API it is possible to add new mapper types to the game.
 +
 +To add a mapper type two elements are required: The type itself (extending ''​MCustom'',​ analogous to ''​MKey'',​ ''​MToggle'',​ etc.) and a ''​CustomSelector''​ that handles displaying the type in the block mapper.
 +
 +After registering them, custom mapper types can be added to any ''​SaveableDataHolder''​ (Blocks, Entities, ''​GenericDataHolder''​) using the ''​AddCustom''​ method.
 +
 +Adding a mapper type is a somewhat complex topic and creating the interface properly can require a lot of work depending on the interface. It is not recommended for beginners or when it only provides marginal benefits over using a combination of the default mapper types.
 +
 +There is a step-by-step [[.:​example-guides:​CreatingMapper|guide to creating a mapper type]] available as well.
 +
 +===== Creating a MapperType =====
 +
 +The base class for all custom mapper types is ''​MCustom<​T>''​. The type parameter indicates what kind of value is edited by the mapper type, this can be a primitive type (like ''​string''​ or ''​int''​) or a more complex type.
 +
 +A mapper type contains three values: The default value, the current value and a "load value"​. The load value is basically used for the undo system and is normally handled automatically.
 +
 +There are three things required in a class extending ''​MCustom<​T>'':​ A constructor,​ ''​SerializeValue'',​ and ''​DeSerializeValue''​.
 +
 +==== Required overrides ====
 +
 +The constructor usually has a signature similar to the the default mapper types: ''​string displayName,​ string key, T defaultValue''​. Whether or not this structure is followed exactly, it is **required** to call the base constructor with the same signature.
 +
 +The ''​XData SerializeValue(T value)''​ method is used to specify how a value of type ''​T''​ should be serialized into an ''​XData''​ object which is what is used for saving.
 +
 +The ''​key''​ of the returned ''​XData''​ should be the ''​SerializationKey''​ property, not the ''​Key''​ property!
 +
 +Similarly, ''​T DeSerializeValue(XData data)''​ is used to deserialize an ''​XData''​ that was serialized by the ''​SerializeValue''​ method.
 +
 +The easiest way of converting to and from an ''​XData''​ object is to convert whatever type is being edited to a primitive type supported by the ''​XData''​ system (if it is not already) and then convert to ''​XString'',​ ''​XInteger'',​ etc. (Note that ''​XStringArray'',​ etc. are also supported which may make serialization easier for more complex types.)
 +
 +Both of these methods may be called from the base class constructor,​ before the constructor of the subclass is called. Make sure these methods are written such that they can handle this.
 +
 +Overriding these methods is sufficient for value types (i.e. the value is changed by assigning a new one, not by changing fields of the value object). If the underlying type is not a value type, it is likely necessary to override some of the optional override below as well.
 +
 +==== Optional overrides ====
 +
 +The ''​Serialize()'',​ ''​SerializeDefault()'',​ ''​SerializeLoadValue()'',​ and ''​DeSerialize(XData)''​ methods are automatically implemented using the ''​SerializeValue''​ and ''​DeSerializeValue''​ methods. They can be overridden manually if necessary, but the default implementations should normally suffice.
 +
 +The ''​MCustom''​ base class provides a default ''​Changed''​ event, which is always invoked from ''​InvokeChanged''​. If desired, a subclass can add another event with a more appropriate signature. This should then also be called from ''​InvokeChanged'',​ but the base method has to be called from the override!
 +
 +''​ResetDefaults''​ should reset the ''​value''​ back to the default specified in the constructor. The default implementation does this by assigning ''​defaultValue''​ to ''​value''​ which is likely not correct for non-value types.
 +
 +''​ResetValue''​ and ''​ApplyValue''​ set ''​value''​ to ''​loadValue''​ or ''​loadValue''​ to ''​value''​ respectively. ''​ApplyValue''​ should also call ''​InvokeChanged''​ with the new value. The same reservations as for ''​ResetDefaults''​ apply.
 +
 +If the underlying type is not a value type, the constructor should also do some additional work: The default constructor will set ''​value = loadValue = defaultValue''​ which is only correct for value types. If a reference type is used, the subclass needs to correctly create copies of the default value after the base constructor has run.
 +
 +The ''​isDefaultValue''​ property has a default implementation comparing ''​Value''​ and ''​defaultValue''​ using the ''​.Equals()''​ method. If this is not appropriate or there is a more suitable comparison available, this can be overridden.
 +
 +===== Creating the Selector =====
 +
 +Each mapper type has an associated ''​Selector''​ class handling the interface in the block mapper. Custom selectors are created by extending the ''​CustomSelector<​T,​ TMapper>''​ class, where ''​T''​ is the same type parameter as in ''​MCustom<​T>''​ and ''​TMapper''​ is the mapper type (derived from ''​MCustom''​) that this selector is associated to.
 +
 +To define a selector, override ''​CreateInterface''​ and ''​UpdateInterface''​.
 +
 +==== Creating an interface ====
 +
 +Writing code to create the interface can be somewhat bothersome because it requires correctly positioning and creating UI elements from code. The block mapper uses a UI system based on complete GameObjects instead of either the legacy Unity ''​GUI''​ system or the newer ''​Canvas''​-based system.
 +
 +The ''​CustomSelector''​ class provides some helpers to assist creating the UI by using the ''​Elements''​ property which has methods like ''​MakeText''​ or ''​MakeBox''​ to create elements and ''​AddButton''​ or ''​ScaleOnMouse''​ to add behaviour to elements that is consistent with the other game UI.
 +
 +All UI objects should be put beneath the ''​Content''​ game object in the hierarchy. This is done automatically when using the helper methods from ''​Elements''​.
 +
 +Also in the interest of consistency,​ the ''​Materials''​ property provides access to materials used elsewhere in the block mapper which make it possible to create UI that fits in to the block mapper.
 +
 +When creating an interface, also make sure to scale the ''​Background''​ element correctly. It determines the complete size of the selector interface, so it must have the correct size to ensure other elements don't overlap. TODO: True? The background scale should ideally be set as the first step in the interface creation, otherwise elements could appear offset.
 +
 +The coordinate system in the interface is based directly on how Unity handles the elements: This means that (0, 0) is the middle point of the interface and the extent of the coordinate system is determined by the background scale.
 +
 +Note that the positions given to the ''​Elements''​ helper methods also specify the middle point of elements, so that passing a position of (0, 0) will always center them in the selector. (This also applies to text, although the anchor properties of the text element can of course be changed on the returned object if desired.)
 +
 +==== The UpdateInterface method ====
 +
 +''​UpdateInterface''​ is automatically called when the value of the underlying mapper type changes and should update the UI appropriately,​ for example by updating displayed texts.
 +
 +==== Changing the mapper type's value ====
 +
 +The ''​CustomMapperType''​ property can be used to access the underlying mapper type.
 +
 +If the underlying type that is being edited is a value type (i.e. the value object should be swapped out instead of changing properties of it), it can be set using the ''​Value''​ property of the mapper type. Setting this will also automatically invoke the ''​Changed''​ event of the mapper type.
 +
 +If the underlying type is edited by changing its properties, call the ''​InvokeChanged''​ method manually whenever the object is edited.
 +
 +In either case, the ''​OnEdit''​ method of the ''​CustomSelector''​ must also be called every time the value is edited.
 +
 +===== Registering the custom mapper type =====
 +
 +Custom mapper types must be registered so the game knows about them. This is done by calling the ''​CustomMapperTypes.AddMapperType<​T,​ TMapper, TSelector>​()''​ method, passing the appropriate types that were created in the earlier steps. This should be called during execution of ''​OnLoad''​.
besiege/modding/mappertypes.txt ยท Last modified: 2018/06/18 16:54 by von