Generated Constants
Overview
To meet the goals of enabling developers to achieve their vision and create complex mechanics without rewriting code, a flexible way of referencing objects and states is required. It would be easy to implement something using strings for keys, but the performance and memory impact of this can be a problem if the system is used in a lot of places or many times a frame. NeoFPS gets around this by providing a simple interface for creating constants similar to Unity's layers. These constants are turned into code that makes them easy to use in scripts as well as using them for properties in the inspector.
The ConstantsSettings scriptable object specifies constants to generate for your game. You can have as many of these settings files as you like and add new constants as required. If you do plan to add your own constants then it is best to create a new constants settings file in case the one that comes with NeoFPS is modified in a future version. It is also important to consider whether it is best to use a constant or a string. If the situation involves a set of keys or IDs that are referenced frequently, but will rarely be changed then generated constants are a good fit. If there is a high chance that new keys will be needed or the keys will be changed regularly, then it might be better to use strings for the ID and a dictionary to store items.
Generated Constants Limitations
The biggest limitation with generated constants is that they are serialized by index. This means that reorganising the values will not be reflected in the inspector.
As an example, let's say you have a Vehicle constant with the following values:
- Bike
- Car
- Truck
You also have a monobehaviour that stores a vehicle as a serialized field and in the inspector it is set to Car. Later on you decide to add a number of new values as follows:
- Bike
- Motorbike
- Car
- Van
- Truck
Looking at the previous monobehaviour in the inspector, it would now be set to Motorbike. This is because the monobehaviour only actually references the number 2, not the word "Car". You can work around this by making sure to keep the order the same and only add new values to the end.
If you remove values instead of adding, then any serialized properties that reference values beyond the new limit will be reset to the first constant value. It is best practice to reserve the first value (0) as a default with a name that reflects this.
Generated Constants In Scripts
You can use constants in scripts as though you were working with an enum. For example, the FpsInputAxis
constant can be used like so:
FpsInputAxis axis = FpsInputAxis.MouseX;
Constants can also be implicitly cast as in the following examples:
FpsInputAxis axis = FpsInputAxis.MouseX;
var element = inputAxisArray[axis];
and:
FpsInputAxis axis = 2;
This makes them very efficient as keys to store items. If you were using strings, you would need to store and reference objects from a dictionary as follows:
var dictionary = new Dictionary<string, MyObject>();
//...
MyObject result = dictionary[myStringKey];
This is a relatively expensive operation as the string needs to be hashed, and then the dictionary searched for the key value pair that the key references. If the key is not found then this code would throw an exception so you would also want to add extra error checking for safety. With constants the object can be stored and referenced in an array as follows.
var array = new MyObject[MyConstant.count];
//...
MyObject result = array[myConstantKey];
This is a much faster operation as you are simply accessing an array by index. By allocating an array that is the size of the constant (count is a preset property in the constants templates included with NeoFPS) you are guaranteed that accessing via a constant key will never be out of bounds. Preallocating like this is a good speed optimisation, but if the constant has a lot of values, and the array is sparsely populated then it would be wasteful to implement in this way. If so then you could either use the string option, or a dictionary with constant keys to save the string hashing step.
How They Are Generated
constants are generated by taking a template script and replacing sections with values in the settings file. The following keys are defined which will be replaced:
Key | Description |
---|---|
%NAME% | The generated constants container name. |
%NAMESPACE% | The namespace for the generated constants container. |
%TYPE% | The underlying type for the value (for example int, ushort, byte). |
%VALUES% | The values to be added. These will be written as a number of const values of the specified type. |
%VALUE_NAMES% | An array of strings to be populated with the value names. This is used for the inspector value dropdown among other things. |
%COUNT% | The number of values written. |
The generation process requires 2 templates. One for the output constant, and one for an editor drawer that draws the constant in the inspector as a dropdown.