Simple Persistent AR: Complete code

All of the code for the project can be found below or on GitHub.

^PersistentARinDomain.cs:^:

using System.Collections;
using System.Collections.Generic;
using Auki.ConjureKit;
using Auki.ConjureKit.Manna;
using Auki.Integration.ARFoundation;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using Random = UnityEngine.Random;

public class PersistentARinDomain : MonoBehaviour
{
    [SerializeField] private Camera arCamera;
    [SerializeField] private GameObject cube;
    [SerializeField] private GameObject calibrateUI;
    [SerializeField] private ARRaycastManager raycastManager;
    [SerializeField] private Button createCubeButton;

    private const string AppKey = "YOUR_APP_KEY";
    private const string AppSecret = "YOUR_APP_SECRET";

    private IConjureKit _conjureKit;
    private Manna _manna;
    private bool _calibrated = false;
    private List<ARRaycastHit> _arRaycastHits = new List<ARRaycastHit>();
    private SaveData _saveData = new SaveData();
    
    private void Start()
    {
        _conjureKit = new ConjureKit(
            arCamera.transform,
            AppKey,
            AppSecret);
        _manna = new Manna(_conjureKit);
    
        var textureProviderComp = CameraFrameProvider.GetOrCreateComponent();
        textureProviderComp.OnNewFrameReady += frame => _manna.ProcessVideoFrameTexture(frame.Texture, frame.ARProjectionMatrix, frame.ARWorldToCameraMatrix);
        _manna.OnLighthouseTracked += OnLighthouseTracked;
        
        createCubeButton.onClick.AddListener(OnCubeButtonClick);
    
        _conjureKit.Connect();
    }
    
    private void OnLighthouseTracked(Lighthouse lighthouse, Pose qrPose, bool isCalibrationGood)
    {
        // If the QR detection was good enough and the QR code is static (generated from the posemesh console),
        // hide the calibration view and show the cube marker 
        if (isCalibrationGood && lighthouse.Type == Lighthouse.LighthouseType.Static)
        {
            if(!_calibrated)
            {
                _calibrated = true;
                calibrateUI.SetActive(false);
                cube.SetActive(true);
                LoadLocally();
            }
        }
    }

    private void Update()
    {
        // Make a raycast from the center of the screen to an AR plane (floor, wall, or any other surface detected by ARFoundation)
        var ray = arCamera.ViewportPointToRay(Vector3.one * 0.5f);
        if (raycastManager.Raycast(ray, _arRaycastHits, TrackableType.PlaneWithinPolygon))
        {
            // Place the cube where the raycast hits a plane. Move it half the cube size along the hit normal (up if on the ground, forward if on the wall)
            cube.transform.position = _arRaycastHits[0].pose.position + _arRaycastHits[0].pose.up * cube.transform.localScale.x / 2f;
            // Rotate the cube only around y axis to always face the camera
            cube.transform.rotation = Quaternion.Euler(Vector3.Scale(arCamera.transform.rotation.eulerAngles, Vector3.up));
        }
    }
    
    private void PlaceCube(Vector3 position, Quaternion rotation, Color color)
    {
        var placedCube = Instantiate(cube, position, rotation);
        placedCube.GetComponent<Renderer>().material.color = color;
        placedCube.gameObject.SetActive(true);
    }
    
    private void OnCubeButtonClick()
    {
        var color = Random.ColorHSV();
        // Place the cube where the cube marker is 
        PlaceCube(cube.transform.position, cube.transform.rotation, color);
        // Save the position and rotation information locally
        _saveData.cubes.Add(new CubeData(cube.transform.position, cube.transform.rotation, color));
        SaveLocally();
    }
    
    private void SaveLocally()
    {
        var json = JsonUtility.ToJson(_saveData);
        PlayerPrefs.SetString("_saveData", json);
        PlayerPrefs.Save();
    }
    
    private void LoadLocally()
    {
        if(!PlayerPrefs.HasKey("_saveData"))
            return;
    
        var json = PlayerPrefs.GetString("_saveData");
        _saveData = JsonUtility.FromJson<SaveData>(json);

        foreach (var savedCube in _saveData.cubes)
        {
            PlaceCube(savedCube.position.ToVector3(), savedCube.rotation.ToQuaternion(), savedCube.color.ToColor());
        }
    }
}

^SaveData.cs^:

using System;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class SaveData
{
    public List<CubeData> cubes = new List<CubeData>();
}

// Because Unity's Vector3, Quaternion and Color structs are not marked as [Serializable] they can't be serialized into JSON.
// For that we create serializable versions of each one. There can be other approaches depending on how you serialize/deserialize the data.
[Serializable]
public class CubeData
{
    public SerializableVector3 position;
    public SerializableQuaternion rotation;
    public SerializableColor color;

    public CubeData() {}

    public CubeData(Vector3 position, Quaternion rotation, Color color)
    {
        this.position = new SerializableVector3(position);
        this.rotation = new SerializableQuaternion(rotation);
        this.color = new SerializableColor(color);
    }
}

[Serializable]
public class SerializableVector3
{
    public float x, y, z;
    
    public SerializableVector3() {}

    public SerializableVector3(Vector3 sourceVector)
    {
        x = sourceVector.x;
        y = sourceVector.y;
        z = sourceVector.z;
    }

    public Vector3 ToVector3() => new Vector3(x, y, z);
}

[Serializable]
public class SerializableQuaternion
{
    public float x, y, z, w;
    
    public SerializableQuaternion() {}

    public SerializableQuaternion(Quaternion sourceQuaternion)
    {
        x = sourceQuaternion.x;
        y = sourceQuaternion.y;
        z = sourceQuaternion.z;
        w = sourceQuaternion.w;
    }
    
    public Quaternion ToQuaternion() => new Quaternion(x, y, z, w);
}

[Serializable]
public class SerializableColor
{
    public float r, g, b, a;
    
    public SerializableColor() {}

    public SerializableColor(Color sourceColor)
    {
        r = sourceColor.r;
        g = sourceColor.g;
        b = sourceColor.b;
        a = sourceColor.a;
    }
    
    public Color ToColor() => new Color(r, g, b, a);
}

The full project can be found on GitHub.

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