-
Notifications
You must be signed in to change notification settings - Fork 5
Implementing your own properties for the Totem assets. Custom filters
To understand how to create a property we'll have to understand what is the property in the context of a Totem asset, and even what magic beast the Totem asset is in the first place.
In it's core the Totem asset is just a nonsensical set of bits, randomly placed 0's and 1's. What gives sense to the asset and, as a result, defines the properties of an assets is the filter. The binary representation of an asset has a name: DNA
Filter is a set of rules, where each rule defines a unique property by telling us how to interpret those bits in the asset . We use filters in a form of a JSON file.
Each rule of the filter requires a few values to be set:
-
description
: a readable text description of the asset's property. Mostly used in totem-explorer for better readability. -
id
: an id of the property. This is, essentially, the name of the property and the name of the class member to which the value of this property will be mapped. -
type
: sets the type of the property. Possible values are:bool, int, map, range, Color
. I'll go into more details about every type later in the post. -
gene
: indicates on which gene of the DNA the property value starts. Gene is a set of 32 bits. So it tells the interpreter for how much N * 32 bits to shift from the start. -
start
: indicates on which bit of the gene the property value begins. -
length
: indicates the length of the value in bits. -
values
: a mapping array for possible values when thetype
isrange
ormap
.
Example of the canonical rule:
{"description": "Range", "id": "range_nd", "type": "int", "gene": 8, "start": 0, "length": 32}
Firstly, we get the binary representation of the property's value.
To do that, we read the {length}
bits, starting on {gene * 32 + start}
bit of the DNA from the asset.
On the second step we convert the result from binary to unsigned decimal value (except when the type
is Color
). Let's call the result of this step - decimal_value
.
The next step is dependant on the type
of the rule. If the type is:
-
map
: the result is a key from thevalues
array of the rule based on thedecimal_value
. The field in the mapped object has to be of a string type. Map rule example:
{"description": "Element", "id": "classical_element", "type": "map", "gene": 0, "start": 10, "length": 2,
"values": [{"key": "Air", "value": 0}, {"key": "Earth", "value": 1}, {"key": "Water", "value": 2}, {"key": "Fire", "value": 3}]
},
-
bool
: the result is false if thedecimal_value
is 0, otherwise true. Thelength
of this rule has to be 1, so the possible values fordecimal_value
could only be 1 or 0. The field in the mapped object has to be of a bool type. Bool rule example:
{"description": "Body Strength", "id": "body_strength", "type": "bool", "gene": 0, "start": 13, "length": 1}
-
int
: the result is thedeciam_value
itself. Make sure to not set thelength
higher then 32 or it will lead to an overflow of uint. The field in the mapped object has to be of a uint type. Int rule exmaple:
{"description": "Range", "id": "range_nd", "type": "int", "gene": 8, "start": 0, "length": 32},
-
range
: the result is a key from thevalues
array based on thedecimal_value
. The field in the mapped object has to be of a string type Range rule example:
{"description": "Weapon Material", "id": "weapon_material", "type": "range", "gene": 4, "start": 0, "length": 4,
"values": [
{"key": "Wood", "value": [0, 7]},
{"key": "Bone", "value": [8, 11]},
{"key": "Flint", "value": [12, 14]},
{"key": "Obsidian", "value": [15, 15]}
]
}
-
Color
: in this case we dont use thedecimal_value
. We skip the second step and divide the binary representation of the property's value into 3 8bit parts. Each part represents R,G and B channel of the color.length
of the rule has to be at least 24. The field in the mapped object has to be of a Color type. Color rule example:
{"description": "Primary Color", "id": "primary_color", "type": "Color", "gene": 2, "start": 0, "length": 24}
It might be confusing to create a custom filter from scrath, so that's why it's recomended to make a copy of one of the default filters and add new rules based on the existing ones.
To use your brand new filter you'll have to do two final steps.
- Create a new class that has a member for each rule. Keep in mind that name of the class field has to be the same as the
id
of the rule and type of the class field is defind by the rule'stype
. - Load the text of the filter, create an instance of
TotemDNAFilter
with parametered constructor and retrieve assets with it. A good place to put the filter file would be Resources folder of your project.
A small snippet based on Music Rash custom filter for avatars:
public class MusicRushAvatar: TotemDNADefaultAvatar
{
public string genre;
}
TotemDNAFilter mdFilter = new TotemDNAFilter(Resources.Load<TextAsset>($"{pathToFilter}").text);
totemCore.GetUserAvatars<MusicRushAvatar>(user, mdFilter , (avatars) =>
{
foreach (var avatar in avatars)
{
Debug.Log(avatar.genre);
}
});