ECS: Get and Set the Color

Let's now learn how to get and set data on a component using a system.

Get and Set the Color

The component data is a ^byte[]^, and it's up to the developer to decide what data to store and how. Create two methods to convert ^Color^ to ^byte[]^ and vice versa;

// Convert Color32 to byte array
private byte[] ColorToByteArray(Color32 color)
{
    byte[] colorBytes = new byte[4];
    colorBytes[0] = color.r;
    colorBytes[1] = color.g;
    colorBytes[2] = color.b;
    colorBytes[3] = color.a;

    return colorBytes;
}

// Convert byte array to Color32 
public Color32 ByteArrayToColor(byte[] bytes)
{
    if (bytes.Length < 4)
    {
        Debug.LogError("Byte array must have at least 4 elements (R, G, B, A).");
        return Color.clear;
    }

    byte r = bytes[0];
    byte g = bytes[1];
    byte b = bytes[2];
    byte a = bytes[3];

    Color32 color = new Color32(r, g, b, a);
    return color;
}

Now create a ^SetColor^ method that will update the component with the new color locally and broadcast the update to all the other participants. We'll need to first verify that an entity with the given id exists. If the entity exists but doesn't yet have the ^color^ component, add a new one.

public bool SetColor(uint entityId, Color color)
{
    // Check if the entity with the given id exists
    var entity = _session.GetEntity(entityId);
    if (entity == null) return false;
    
    // Store the data locally  
    _entityColorDataMap[entityId] = color;

    // If the entity doesn't already have Color component add one
    var component = _session.GetEntityComponent(entityId, COLOR_COMPONENT_NAME);
    if (component == null)
    {
        _session.AddComponent(
            COLOR_COMPONENT_NAME,
            entityId,
            ColorToByteArray(color),
            () => {}, 
            error => Debug.LogError(error) 
        );

        return true;
    }
    else
    {
        return _session.UpdateComponent(
            COLOR_COMPONENT_NAME,
            entityId, 
            ColorToByteArray(color)
        );
    }
}

Create a ^GetColor^ method that will return the locally stored value or a default one if the component or the entity doesn't exist.

public Color GetColor(uint entityId)
{
    if (_session.GetEntity(entityId) == null || !_entityColorDataMap.ContainsKey(entityId))
        return Color.clear;
    
    return _entityColorDataMap[entityId];
}

Override the Delete method that will be called by ConjureKit when other participants remove a component from an entity and a ^DeleteColor^ method to delete the color locally.

public override void Delete(IReadOnlyList<(EntityComponent component, bool localChange)> deleted)
{
    foreach (var (entityComponent, localChange) in deleted)
    {
        var entity = _session.GetEntity(entityComponent.EntityId);
        if (entity == null) continue;

        _entityColorDataMap.Remove(entity.Id);
    }
}

In this tutorial, we are not going to use the delete functionality, but it is necessary to override the ^Delete^ method. You can later implement a public ^DeleteColor^ method that can be called to remove a color component from an entity.

public void DeleteColor(uint entityId)
{
    _session.DeleteComponent(COLOR_COMPONENT_NAME, entityId, () =>
    {
        _entityColorDataMap.Remove(entityId);
    });
}

Initialize the system and update the color for everyone

In the ^ConjureKitManager^ class, declare a ^ColorSystem^ and a Dictionary to store the cubes.

private ColorSystem _colorSystem;
private Dictionary<uint, Renderer> _cubes = new Dictionary<uint, Renderer>();

In ^Conjurekit.Onjoined^ callback, register the color system to the joining participant

_conjureKit.OnJoined += session =>
{
    sessionID.text = session.Id.ToString();
    
    !!
    _colorSystem = new ColorSystem(session);
    session.RegisterSystem(_colorSystem, () => Debug.Log("System registered in session"));
    _colorSystem.OnColorComponentUpdated += OnColorComponentUpdated;
    !!
};

Create a method that will change the cube's color when its component is updated.

private void OnColorComponentUpdated(uint entityId, Color color)
{
    _cubes[entityId].material.color = color;
}

Simplify the ^TouchableByHand^ class to stop updating the color locally and instead trigger an event when the cube is touched.

public class TouchableByHand : MonoBehaviour
{
    public event Action OnTouched;

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("hand"))
        {
            OnTouched?.Invoke();
        }
    }
}

Update the ^CreateCubeEntity^ and the ^CreateCube^ methods to initialize a cube with white color and update with random color when touched.

public void CreateCubeEntity()
{
    if (_conjureKit.GetState() != State.Calibrated)
        return;

    Vector3 position = arCamera.transform.position + arCamera.transform.forward * 0.5f;
    Quaternion rotation = Quaternion.Euler(0, arCamera.transform.eulerAngles.y, 0);

    Pose entityPos = new Pose(position, rotation);

    _conjureKit.GetSession().AddEntity(
        entityPos,
        onComplete: entity =>
        {
        !!
            // Initialize with white color
            _colorSystem.SetColor(entity.Id, Color.white);
        !!    
            
            CreateCube(entity);
        },
        onError: error => Debug.Log(error));
}

private void CreateCube(Entity entity)
{
    if (entity.Flag == EntityFlag.EntityFlagParticipantEntity) return;

    var pose = _conjureKit.GetSession().GetEntityPose(entity);
    var touchableCube = Instantiate(cube, pose.position, pose.rotation).GetComponent<TouchableByHand>();
    !!
    _cubes[entity.Id] = touchableCube.GetComponent<Renderer>();
    _cubes[entity.Id].material.color = _colorSystem.GetColor(entity.Id);

    touchableCube.OnTouched += () =>
    {
        _colorSystem.SetColor(entity.Id, Random.ColorHSV());
        _cubes[entity.Id].material.color = _colorSystem.GetColor(entity.Id);
    };
    !!
}

Build and run on two or more devices, and you should see cubes with shared colors.

Need a refresher on the essentials?

Check out more lessons, DIY kits and essentials reading material at the developer learning centre homepage.

Want to build on the posemesh and need a helping hand?

Apply for a grant of AUKI tokens to get your project off the ground, and work directly with the Auki Labs team to get your creation to market. Successful applicants may be granted up to 100k USD worth of AUKI tokens, and development and marketing support from the Auki Labs team.