^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.