Skip to content

Implementing your own properties for the Totem assets. Custom filters

Sergey Voropaiev edited this page Jan 31, 2023 · 7 revisions

Overview

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.

Filter structure

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 the type is range or map.

Example of the canonical rule:

{"description": "Range", "id": "range_nd", "type": "int", "gene": 8, "start": 0, "length": 32}

Filter algorithm

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 the values array of the rule based on the decimal_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 the decimal_value is 0, otherwise true. The length of this rule has to be 1, so the possible values for decimal_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 the deciam_value itself. Make sure to not set the length 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 the values array based on the decimal_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 the decimal_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}

Where to start ?

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.

How to use the custom filter ?

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's type.
  • 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);
        }
 
    });