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トークンの助成を受け、アウキラボチームによる開発、マーケティング支援を受けることができます。