ECS: Complete Code

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.

This is the end of this lesson

Need a refresher on the essentials?

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

ポーズメッシュを構築するのにサポートが必要ですか?

プロジェクトをスタートさせるためにAUKIトークンの助成金を申請し、Auki Labsチームと直接連携して、あなたのクリエイションをマーケットへ。選ばれた申請者は最大10万米ドル相当のAUKIトークンの助成を受け、アウキラボチームによる開発、マーケティング支援を受けることができます。