Unity

Unity Runtime UI – Crib Notes

Initial Setup

Every UI Document component references a UI Document asset (.uxml file) that defines the UI and a Panel Settings asset that renders it. You can connect more than one UI Document asset to a single Panel Settings asset.

Right-click Project window, and then select Create > UI Toolkit > UI Document

  1. In the Hierarchy right click the Scene, select GameObject > UI Toolkit > UI Document. This creates the following:
    • A UI Toolkit folder with a Panel Settings asset and a default runtime theme.
    • A GameObject with a UI Document component which in turn the is connected to the Panel Settings asset.
  2. In the Hierarchy select the UIDocument  then drag SimpleRuntimeUI.uxml from your Project window to the inspector (Source Asset)
  3. Create a companion script that derives from MonoBehaviour.
  4. Add SimpleRuntimeUI.cs as a component of the UIDocument GameObject.
  5. Click on the PanelSettings object Project > Assets > UI Toolkit then drag the theme style sheet (.tss) to the Panel.
  1. Use the Package Manager to install the Input System package.
  2. Select Edit > Project Settings > Player.
  3. Set Active Input Handling to Input System Package (New).
  4. Select GameObject > UI > Event System. This adds an Event System GameObjectThe fundamental object in Unity scenes, which can represent characters, props, scenery, cameras, waypoints, and more. A GameObject’s functionality is defined by the Components attached to it. More info
    See in Glossary that includes a Standalone Input Module in the Scene. The module shows an error message along with a button in the Inspector window.

Custom USS

  • Right click Project Create UI ToolkitStyle Sheet
  • Add style sheet to umxl view
    • Add a Style tag with src attribute pointing to the style sheet
    • In the UI Builder upper left corner (+▼) → Add Existing USS

External Links

https://docs.unity3d.com/2022.2/Documentation/Manual/UIToolkits.html

https://docs.unity3d.com/2022.2/Documentation/Manual/UIE-get-started-with-runtime-ui.html

https://docs.unity3d.com/ScriptReference/UIElements.VisualElement.html

https://docs.unity3d.com/Manual/UIE-USS.html

https://docs.unity3d.com/Manual/UIE-create-tabbed-menu-for-runtime.html

JSON Serialization in Unity

JSONUtility (Unity Class)

Unity has it’s own JSON serialization package [ref] which is very close in usage to the native C# serialization. It works with public field, or private fields marked with the ‘SerializeField’ attribute. It does not work with class properties, or fields marked ‘readonly’. To otherwise skip a field mark it with the ‘NonSerialized’ attribute.

// Will serialize
public string name;
[SerializeField] private string value;

// Will not serialize
public readonly string name;
public string name {get; set;}
[NonSerialized] int age;

The serialization method does not require type information, it will simply create json text based on the public field values. This means you do not need to deserialize into the same object you serialized from. The class it’s self must be marked with the ‘Serializable’ attribute.

using UnityEngine;

[System.Serializable]
public class PlayerState : MonoBehaviour{
    public string name;
    public int age;

    public string SaveToString(){
        return JsonUtility.ToJson(this);
    }

    // Given:
    // name = "Dr Charles"
    // age = 33
    // SaveToString returns:
    // {"name":"Dr Charles","age":33}
}

Unlike serialization, deserialization requires a type. Any values found in the JSON text that are not fields in the provided type will be ignored.

PlayerState playerState = JsonUtility.FromJson<PlayerState>(string);

JSONEncoder (Helper Class)

In order to automatically decode a json object to the correct type I have included the type name in the JSON text object. The emitted JSON has two root fields: type & instance.

using UnityEngine;
using System;

public class JSONDetails<T>{
    public T instance;
    public string type;

    public JSONDetails(T instance) {
        this.instance = instance;
        this.type = instance.GetType().ToString();
    }    
}

public class JSONEncoder {
    public string type = "";
    public JSONEncoder() { }

    public static string Serialize<T>(T anObject) {
        return JsonUtility.ToJson(new JSONDetails<T>(anObject), true);
    }

    public static object Deserialize(string json) {
        JSONEncoder wrapper = JsonUtility.FromJson<JSONEncoder>(json);
        Type g = Type.GetType(wrapper.type);
        Type t = typeof(JSONDetails<>).MakeGenericType(new Type[] { g });
        var hydrated = JsonUtility.FromJson(json, t);   
        return t.GetField("instance").GetValue(hydrated);
    }
}

At times serialization may require type information to ensure the correct type is serialized. Deserialization returns an object which can be cast as necessary. Typically this should used to detect the presence of an interface and cast accordingly.

public void ReceiveEvent(string json, string source) {
    var isEvent = JSONEncoder.Deserialize(json);

    if (isEvent is IModelEvent<M> != false) {
        IModelEvent<M> modelEvent = (IModelEvent<M>)isEvent;
        this.model = modelEvent.UpdateModel(this.model);
    }

    if (isEvent is IModelEvent<N> != false) {
        this.Broadcast(isEvent, source);
    }
}