Complete code for this lesson
^ConjureKitManager.cs^:
using System.Collections.Generic;
using UnityEngine;
using Auki.ConjureKit;
using UnityEngine.UI;
using Auki.ConjureKit.Manna;
using Auki.Ur;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using State = Auki.ConjureKit.State;
public class ConjureKitManager : MonoBehaviour
{
    [SerializeField] private Camera arCamera;
    [SerializeField] private ARSession arSession;
    [SerializeField] private ARRaycastManager arRaycastManager;
    [SerializeField] private Text sessionState;
    [SerializeField] private Text sessionID;
    [SerializeField] private GameObject cube;
    [SerializeField] private Button spawnButton;
    [SerializeField] Button qrCodeButton;
    private bool _qrCodeBool;
    private IConjureKit _conjureKit;
    private Manna _manna;
    private ARCameraManager _arCameraManager;
    private Texture2D _videoTexture;
    [SerializeField] private GameObject fingertipLandmark;
    private HandTracker _handTracker;
    private bool _landmarksVisualizeBool = false;
    [SerializeField] private AROcclusionManager arOcclusionManager;
    private bool _occlusionBool = true;
    [SerializeField] private Transform arSessionOrigin;
    private ColorSystem _colorSystem;
    private Dictionary<uint, Renderer> _cubes = new Dictionary<uint, Renderer>();
    void Start()
    {
        _arCameraManager = arCamera.GetComponent<ARCameraManager>();
        _conjureKit = new ConjureKit(
            arCamera.transform,
            "YOUR_APP_KEY",
            "YOUR_APP_SECRET");
        _manna = new Manna(_conjureKit);
        _manna.GetOrCreateFrameFeederComponent().AttachMannaInstance(_manna);
        _conjureKit.OnStateChanged += state =>
        {
            if (state == State.JoinedSession)
            {
                Debug.Log("State.JoinedSession  " + Time.realtimeSinceStartup);
            }
            if (state == State.Calibrated)
            {
                Debug.Log("State.Calibrated  " + Time.realtimeSinceStartup);
            }
            sessionState.text = state.ToString();
            ToggleControlsState(state == State.Calibrated);
        };
        _conjureKit.OnJoined += session =>
        {
            Debug.Log("OnJoined " + Time.realtimeSinceStartup);
            sessionID.text = session.Id.ToString();
            _colorSystem = new ColorSystem(session);
            session.RegisterSystem(_colorSystem, () => Debug.Log("System registered in session"));
            _colorSystem.OnColorComponentUpdated += OnColorComponentUpdated;
        };
        _conjureKit.OnLeft += session =>
        {
            sessionID.text = "";
        };
        _conjureKit.OnEntityAdded += CreateCube;
        _conjureKit.Connect();
        _handTracker = HandTracker.GetInstance();
        _handTracker.SetARSystem(arSession, arCamera, arRaycastManager);
        _handTracker.OnUpdate += (landmarks, translations, isRightHand, score) =>
        {
            if (score[0] > 0 && _landmarksVisualizeBool)
            {
                var handPosition = new Vector3(
                    translations[0],
                    translations[1],
                    translations[2]);
                var pointerLandmarkIndex = 8 * 3; // Index fingertip
                var pointerLandMarkPosition = new Vector3(
                    landmarks[pointerLandmarkIndex + 0],
                    landmarks[pointerLandmarkIndex + 1],
                    landmarks[pointerLandmarkIndex + 2]);
                fingertipLandmark.SetActive(true);
                fingertipLandmark.transform.position =
                    arCamera.transform.TransformPoint(handPosition + pointerLandMarkPosition);
            }
            else
            {
                fingertipLandmark.SetActive(false);
            }
        };
        _handTracker.Start();
    }
    private void Update()
    {
        _handTracker.Update();
    }
    private void ToggleControlsState(bool interactable)
    {
        if (spawnButton) spawnButton.interactable = interactable;
        if (qrCodeButton) qrCodeButton.interactable = interactable;
    }
    public void ToggleLighthouse()
    {
        _qrCodeBool = !_qrCodeBool;
        _manna.SetLighthouseVisible(_qrCodeBool);
    }
    public void ToggleHandLandmarks()
    {
        _landmarksVisualizeBool = !_landmarksVisualizeBool;
        if (_landmarksVisualizeBool)
        {
            _handTracker.ShowHandMesh();
        }
        else
        {
            _handTracker.HideHandMesh();
        }
    }
    public void ToggleOcclusion()
    {
        _occlusionBool = !_occlusionBool;
        arOcclusionManager.requestedHumanDepthMode = _occlusionBool ? HumanSegmentationDepthMode.Fastest : HumanSegmentationDepthMode.Disabled;
        arOcclusionManager.requestedHumanStencilMode = _occlusionBool ? HumanSegmentationStencilMode.Fastest : HumanSegmentationStencilMode.Disabled;
        arOcclusionManager.requestedEnvironmentDepthMode = _occlusionBool ? EnvironmentDepthMode.Fastest : EnvironmentDepthMode.Disabled;
    }
    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);
        };
    }
    private void OnColorComponentUpdated(uint entityId, Color color)
    {
        _cubes[entityId].material.color = color;
    }
}^ColorSystem.cs^
using System;
using System.Collections.Generic;
using Auki.ConjureKit;
using Auki.ConjureKit.ECS;
using UnityEngine;
/// <summary>
/// The ColorSystem adds and deletes the Color component,
/// maintains and updates a local map with component data 
/// </summary>
public class ColorSystem : SystemBase
{
    // The unique name of the component
    private const string COLOR_COMPONENT_NAME = "color";
    /// <summary>
    /// Triggered when a component data is updated by another participant
    /// </summary>
    public event Action<uint, Color> OnColorComponentUpdated;
    // Local Color component data map
    private readonly IDictionary<uint, Color> _entityColorDataMap = new Dictionary<uint, Color>();
    public ColorSystem(Session session) : base(session)
    {
    }
    // The system will be notified when any component in the returned array is updated or removed
    public override string[] GetComponentTypeNames()
    {
        return new[] { COLOR_COMPONENT_NAME };
    }
    /// Broadcast from the server when another participant updates a Color component with new data.
    public override void Update(IReadOnlyList<(EntityComponent component, bool localChange)> updated)
    {
        foreach (var (entityComponent, localChange) in updated)
        {
            // Update the local data and notify about the update
            _entityColorDataMap[entityComponent.EntityId] = ByteArrayToColor(entityComponent.Data);
            OnColorComponentUpdated?.Invoke(entityComponent.EntityId, _entityColorDataMap[entityComponent.EntityId]);
        }
    }
    /// Broadcast from server when another participant removes a Color component from an entity
    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);
        }
    }
    /// <summary>
    /// Tries to update the Color component data locally and broadcast the update to other participants.
    /// </summary>
    /// <returns> False if entity does not exists, true if component was added/updated successfully.</returns>
    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)
            );
        }
    }
    /// <summary>
    /// Get the local Color component data
    /// </summary>
    public Color GetColor(uint entityId)
    {
        if (_session.GetEntity(entityId) == null || !_entityColorDataMap.ContainsKey(entityId))
            return Color.clear;
        return _entityColorDataMap[entityId];
    }
    /// <summary>
    /// Delete the component locally and notify the other participants
    /// </summary>
    public void DeleteColor(uint entityId)
    {
        _session.DeleteComponent(COLOR_COMPONENT_NAME, entityId, () =>
        {
            _entityColorDataMap.Remove(entityId);
        });
    }
    // 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 
    private 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;
    }
}^TouchableByHand.cs^:
using System;
using UnityEngine;
public class TouchableByHand : MonoBehaviour
{
    public event Action OnTouched;
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("hand"))
        {
            OnTouched?.Invoke();
        }
    }
}The full code for this tutorial can be found on GitHub on the tutorial/ecs branch.
The complete project with all parts and the latest packages is on the master branch of the same repo.
プロジェクトをスタートさせるためにAUKIトークンの助成金を申請し、Auki Labsチームと直接連携して、あなたのクリエイションをマーケットへ。選ばれた申請者は最大10万米ドル相当のAUKIトークンの助成を受け、アウキラボチームによる開発、マーケティング支援を受けることができます。