Show / Hide Table of Contents

Creating a custom mapper type

This is a step-by-step tutorial to adding a custom mapper type to the game.

Mapper types are basically values that can be edited using the block mapper (also applies to entities, GenericDataHolder, etc.).

Custom mapper types are a somewhat advanced feature, but they allow for interesting possibilities by expanding what the block mapper can display.

An existing mod with the ability to run custom code is required to follow this tutorial. See Creating a mod and Custom Code for information on how to get to this point.

The three components of a mapper type

A custom mapper type is composed of three types: The underlying value that is edited (T in the remainder of this document), a class extending MCustom<T> (TMapper) and a class extending CustomSelector<T, TMapper> (TSelector).

The basic idea is that T is the type we ultimately want to attach to an object and edit.

TMapper wraps this type and provides methods for (de)serializing it as well as behaviour for the undo system and some other things.

TSelector is responsible for creating and handling the interface that is display in the block mapper to actually edit the mapper type.

It is easiest to create a custom mapper type using an immutable value type (e.g. all C# primitive types) as underlying type, or structuring the remaining code in such a way that the underlying type is treated as immutable even if it is not. It is also possible to use mutable types but this requires more care and more code.

The remainder of this document will assume T is an immutable value type, for more information on what is required for other cases, see the complete documentation.

The first step:

  • Decide on a type to use for T (or create one)

The TMapper class

  • Create a class extending MCustom<T>

By convention, these are named MSomething (e.g. MKey, MToggle, etc.) but this is not required. In this document we'll call it TMapper.

public class TMapper : MCustom<T> {

}
  • Create a constructor

There must be a constructor that calls the base constructor. By convention it has the signature string displayName, string key, T defaultValue and calls the base constructor with the same arguments, but it is also possible to receive additional parameters or omit the default value to always pass the same one to the base constructor.

public TMapper(string displayName, string key, T defaultValue) : base(displayName, key, defaultValue)
{ }
  • Create (de)serialization methods

The game stores mapper type values using the XData framework and thus there needs to be a way of converting T from and to XData objects. This is achieved by overriding the XData SerializeValue(T value) and T DeSerializeValue(XData data) methods.

One simple possibility is converting between T and a string (which is demonstrated below) but depending on T there may be more appropriate ways (e.g. using other primitive types or array types).

The XData object returned from SerializeValue should always have a key equivalent to the SerializationKey property.

public XData SerializeValue(T value) {
    return new XString(SerializationKey, value.ToString());
}

public T DeSerializeValue(XData data) {
    return T.FromString((string) (XString) data);
}

There are other methods that can be overridden if necessary, but these are the only ones that must always be specified. For more information see the complete documentation.

The TSelector class

  • Create a class extending CustomSelector<T, TMapper>
public class TSelector : CustomSelector<T, TMapper>` {

}

The convention for naming these is <name of TMapper without the "M">Selector, e.g. a ToggleSelector for MToggle etc. There is again no hard requirement to follow this convention though.

  • Creating the interface

Override the CreateInterface method and add code to create the actual UI for editing the mapper type.

This is where creating a mapper type becomes somewhat tedious because the UI system used in the block mapper is not well-suited to creating it from code.

The UI consists of normal GameObjects that should all be beneath the Content object in the hierarchy.

The Elements property contains some helper methods to create common objects and add common behaviour that is consistent with the remainder of the game UI. The Materials property gives access to materials commonly used in the block mapper. Depending on the exact UI, these may be sufficient, otherwise it is necessary to create objects by hand.

When adding the behaviour to these elements that actually edits the underlying value, it is important to ensure InvokeChanged is called on the TMapper instance (done automatically when Value is set) and OnEdit is called on the selector.

  • Updating the interface

UpdateInterface must also be overridden and is called whenever the underlying value changed and should update the interface to display the new value.

For more information on creating the interface, see the complete documentation.

Registering the mapper type

As a last step, the mapper type needs to be registered by calling CustomMapperTypes.AddMapperType<T, TMapper, TSelector>(). This should be done during execution of OnLoad and before any attempt is made to add the mapper type to any object.

Now the mapper type can be added using the AddCustom(TMapper) method of any SaveableDataHolder (block, entities, GenericDataHolder).

Back to top Generated by DocFX