Meta Spatial Anchors saving anchors

I am working on implementing Spatial Anchors in my app. I used this tutorial: Meta Quest Spatial Anchors Tutorial
In the tutorial he only shows how to spawn, save and load one prefab, but my app needs different prefabs.
I have figured out how to spawn in different prefabs with a Spatial Anchor component, but the saving is the thing I can’t figure out.

I have tried to find other tutorials, but they all only save one prefab.

Any ideas on how to do that or where to find more information on this?
Thank you in advance.

Anchor Loader script:

using System;
using UnityEngine;

public class AnchorLoader : MonoBehaviour
{
    private SpatialAnchorManager spatialAnchorManager;
    [SerializeField]
    private OVRSpatialAnchor anchorPrefab;
    Action<OVRSpatialAnchor.UnboundAnchor, bool> _onLoadAnchor;

    void Start() {
        spatialAnchorManager = GetComponent<SpatialAnchorManager>();
        _onLoadAnchor = OnLocalized;
    }

    public void LoadAnchorsByUuid() {
        if (!PlayerPrefs.HasKey(SpatialAnchorManager.NumUuidsPlayerPref)) {
            PlayerPrefs.SetInt(SpatialAnchorManager.NumUuidsPlayerPref, 0);
        }

        var playerUuidCount = PlayerPrefs.GetInt(SpatialAnchorManager.NumUuidsPlayerPref);

        if (playerUuidCount == 0) {
            Debug.Log("No anchors to load");
            return;
        }

        var uuids = new Guid[playerUuidCount];
        for (int i = 0; i < playerUuidCount; i++) {
            var uuidKey = "uuid" + i;
            var currentUuid = PlayerPrefs.GetString(uuidKey);

            uuids[i] = new Guid(currentUuid);
        }

        Load(new OVRSpatialAnchor.LoadOptions {
            Timeout = 0,
            StorageLocation = OVRSpace.StorageLocation.Local,
            Uuids = uuids
        });
        Debug.Log("Load Anchors by UUID method called");
    }

    private async void Load(OVRSpatialAnchor.LoadOptions options) {
        var anchors = await OVRSpatialAnchor.LoadUnboundAnchorsAsync(options);
    
        if (anchors == null) {
            return;
        }
    
        foreach (var anchor in anchors) {
            if (anchor.Localized) {
                _onLoadAnchor(anchor, true);
            } else if (!anchor.Localizing) {
                var result = await anchor.LocalizeAsync();
                _onLoadAnchor(anchor, result);
            }
        }
    }

    private void OnLocalized(OVRSpatialAnchor.UnboundAnchor unboundAnchor, bool success) {
        OVRSpatialAnchor prefab;
        if (spatialAnchorManager.GetAnchorPrefab() == null) {
            prefab = anchorPrefab;
        } else {
            prefab = spatialAnchorManager.GetAnchorPrefab();
        }

        if (!success) return;
        
        var pose = unboundAnchor.Pose;
        var spatialAnchor = Instantiate(prefab, pose.position, pose.rotation);
        unboundAnchor.BindTo(spatialAnchor);
        Debug.Log("Localized anchor with UUID: " + spatialAnchor.Uuid);        
    }
}

Spatial Anchor Manager script:

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

public class SpatialAnchorManager : MonoBehaviour
{
    public static SpatialAnchorManager instance;

    private OVRSpatialAnchor anchorPrefab;
    public const string NumUuidsPlayerPref = "numUuids";
    [SerializeField]
    private List<GameObject> spatialAnchorPrefabs;
    private List<OVRSpatialAnchor> anchors = new List<OVRSpatialAnchor>();
    private OVRSpatialAnchor lastCreatedAnchor;
    private AnchorLoader anchorLoader;

    void Awake() {
        if (instance == null) {
            instance = this;
        }
    }
    void Start() {
        anchorLoader = GetComponent<AnchorLoader>();
        LoadSavedAnchors();
    }

    public void CreateSpatialAnchor(int prefabIndex) {
        var position = new Vector3(OVRInput.GetLocalControllerPosition(OVRInput.Controller.RTouch).x, 0, OVRInput.GetLocalControllerPosition(OVRInput.Controller.RTouch).z);
        var rotation = Quaternion.Euler(0, OVRInput.GetLocalControllerRotation(OVRInput.Controller.RTouch).eulerAngles.y, 0);
        
        GameObject instance = Instantiate(spatialAnchorPrefabs[prefabIndex], position, rotation);
        anchorPrefab = instance.AddComponent<OVRSpatialAnchor>();

        StartCoroutine(AnchorCreated(anchorPrefab));
    }

    private IEnumerator AnchorCreated(OVRSpatialAnchor workingAnchor) {
        while (!workingAnchor.Created && !workingAnchor.Localized) {
            yield return new WaitForEndOfFrame();
        }
        
        Guid anchorGuid = workingAnchor.Uuid;
        anchors.Add(workingAnchor);
        lastCreatedAnchor = workingAnchor;

        Debug.Log("Created anchor with UUID: " + anchorGuid);
    }

    public async void SaveLastCreatedAnchor() {
        if (lastCreatedAnchor == null) {
            Debug.Log("No anchor to save");
            return;
        }
        var success = await lastCreatedAnchor.SaveAsync();
    
        if (success) {
            Debug.Log("Saved anchor with UUID: " + lastCreatedAnchor.Uuid);
        } else {
            Debug.Log("Failed to save anchor");
        }
        SaveUuidToPlayerPrefs(lastCreatedAnchor.Uuid);
    }

    void SaveUuidToPlayerPrefs(Guid uuid) {
        if (!PlayerPrefs.HasKey(NumUuidsPlayerPref)) {
            PlayerPrefs.SetInt(NumUuidsPlayerPref, 0);
        }

        int playerNumUuids = PlayerPrefs.GetInt(NumUuidsPlayerPref);
        PlayerPrefs.SetString("uuid" + playerNumUuids, uuid.ToString());
    }

    public async void UnSaveLastCreatedAnchor() {
        var success = await lastCreatedAnchor.EraseAsync();

        if (success) {
            Debug.Log("Unsaved anchor with UUID: " + lastCreatedAnchor.Uuid);
            Destroy(lastCreatedAnchor.gameObject);
        } else {
            Debug.Log("Failed to unsave anchor");
        }
    }

    public void UnsaveAllAnchors() {
        foreach (var anchor in anchors) {
            UnsaveAnchor(anchor);
        }
        
        anchors.Clear();
        ClearAllUuidsFromPlayerPrefs();
        Debug.Log("Unsaved all anchors method called");
    }

    private async void UnsaveAnchor(OVRSpatialAnchor anchor) {
        await anchor.EraseAsync();
    
        Debug.Log("Unsaved anchor with UUID: " + anchor.Uuid);
    }

    private void ClearAllUuidsFromPlayerPrefs() {
        if (PlayerPrefs.HasKey(NumUuidsPlayerPref)) {
            int playerNumUuids = PlayerPrefs.GetInt(NumUuidsPlayerPref);
            for (int i = 0; i < playerNumUuids; i++) {
                PlayerPrefs.DeleteKey("uuid" + i);
            }
            PlayerPrefs.DeleteKey(NumUuidsPlayerPref);
            PlayerPrefs.Save();
        }
    }

    public void LoadSavedAnchors() {
        anchorLoader.LoadAnchorsByUuid();   
    }

    public OVRSpatialAnchor GetAnchorPrefab() {
        return anchorPrefab;
    }
}

I am also desperate about this topic.
I just cannot understand how nobody explains anything about this, after all… who wants to just spawn the same prefab on and on??

So I figured it out myself. There is probably a better way to do it, but this is the best I could come up with.

I created a new script: Spatial Anchor Save Data to hold the necessary data, in this case Anchor Guid and Name. This script is also responsible for turning those variables into a string and back into the class. I was looking for a way to do this without using MonoBehaviour, but I couldn’t figure it out.
In the inspector I added the class to every prefab that I am going to spawn in and set the name in the inspector as well. If you want to set the name via the script I think you can do that in the Spatial Anchor Manager script on line 48, just add saveData.Name = workingAnchor.name and I think it should work.

Next in the method SaveLastCreatedAnchor I get the component Spatial Anchor Save Data from the lastCreatedAnchor and send that to the method SaveUuidToPlayerPrefs which turns the Spatial Anchor Save Data into a string and saves it in PlayerPrefs.

In the Anchor Loader script the method LoadAnchorsByUuid gets the string from PlayerPrefs and, via the CreateString method in Spatial Anchor Save Data, turns it back into the class and gets the AnchorUuid. Afterwards I destroy the saveData GameObject, this is necessary because the class derives from MonoBehaviour and otherwise you can’t instantiate it. This is not ideal and I am looking for a different way of doing it.

For getting the correct prefab, I created two methods: string GetAnchorNameByUuid(Guid anchorUuid) and GameObject GetAnchorByName(string name). The first one returns the name of the prefab by comparing the Guid passed to the method with the Guid in Player.Prefs. The second method returns the GameObject associated with the name by comparing the name passed to the method with the name of the prefab in the SpatialAnchorPrefabs list.
I use these methods in the OnLocalized method to set the anchorPrefab to the correct one and instantiate it. If the variable from the method is null, it sets anchorPrefab to the defaultPrefab which is set in the inspector.

And that is basically it. It took me quite a while to figure this out so I am happy that it finally worked.
If anyone has any suggestions to improve the code or do it some other way feel free to leave a comment.

Anchor Loader script

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

public class AnchorLoader : MonoBehaviour
{
    [SerializeField]
    private OVRSpatialAnchor defaultAnchor;
    private Action<bool, OVRSpatialAnchor.UnboundAnchor> _onLocalized;

    private int _playerUuidCount;
    
    void Start() {
        _onLocalized = OnLocalized;
    }

    public void LoadAnchorsByUuid() {
        if (!PlayerPrefs.HasKey(SpatialAnchorManager.NumUuidsPlayerPref)) {
            PlayerPrefs.SetInt(SpatialAnchorManager.NumUuidsPlayerPref, 0);
        }

        _playerUuidCount = PlayerPrefs.GetInt(SpatialAnchorManager.NumUuidsPlayerPref);
        if (_playerUuidCount == 0) {
            Debug.Log("No anchors to load");
            return;
        }

        var uuids = new Guid[_playerUuidCount];
        for (int i = 0; i < _playerUuidCount; i++) {
            var playerPrefs = PlayerPrefs.GetString("uuid" + i);
            var saveData = SpatialAnchorSaveData.CreateFromString(playerPrefs);
            uuids[i] = saveData.AnchorUuid;

            Destroy(saveData.gameObject);
        }
        Load(uuids);
    }

    private async void Load(IEnumerable<Guid> uuids) {
        var unboundAnchors = new List<OVRSpatialAnchor.UnboundAnchor>();
        var result = await OVRSpatialAnchor.LoadUnboundAnchorsAsync(uuids, unboundAnchors);
    
        if (result.Success) {
            Debug.Log("Anchors loaded successfully.");

            foreach (var anchor in result.Value) {
                anchor.LocalizeAsync().ContinueWith(_onLocalized, anchor);
            }
        } else {
            Debug.LogError($"Failed to load anchors: {result.Status}");
        }
    }

    private void OnLocalized(bool success, OVRSpatialAnchor.UnboundAnchor unboundAnchor) {
        if (!success) return;

        string name = GetAnchorNameByUuid(unboundAnchor.Uuid);
        GameObject getAnchor = GetAnchorByName(name);
        OVRSpatialAnchor anchorPrefab;

        if (getAnchor != null) {
            anchorPrefab = getAnchor.GetComponent<OVRSpatialAnchor>();
        } else {
            anchorPrefab = defaultAnchor;
        }
        
        if (unboundAnchor.TryGetPose(out Pose pose)) {
            OVRSpatialAnchor spatialAnchor = Instantiate(anchorPrefab, pose.position, pose.rotation);

            unboundAnchor.BindTo(spatialAnchor);
            Debug.Log("Localized anchor with UUID: " + spatialAnchor.Uuid + " and name: " + name);

            SpatialAnchorManager.instance.anchors.Add(spatialAnchor);
        } else {
            Debug.LogError("Failed to get pose for unbound anchor with UUID: " + unboundAnchor.Uuid);
        }      
    }

    private GameObject GetAnchorByName(string name) {
        foreach (var anchor in SpatialAnchorManager.instance.SpatialAnchorPrefabs) {
            var anchorData = anchor.GetComponent<SpatialAnchorSaveData>();
            if (anchorData.Name.Contains(name)) {
                return anchor;
            }
        }
        return null;
    }

    private string GetAnchorNameByUuid(Guid anchorUuid) {
        string name;
        for (int i = 0; i < _playerUuidCount; i++) {
            var anchorData = PlayerPrefs.GetString("uuid" + i);
            var saveData = SpatialAnchorSaveData.CreateFromString(anchorData);
            if (saveData.AnchorUuid == anchorUuid) {
                name = saveData.Name;
                Destroy(saveData.gameObject);
                return name;
            }
        }
        return null;
    }
}

Spatial Anchor Manager script

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

public class SpatialAnchorManager : MonoBehaviour
{
    public static SpatialAnchorManager instance;

    private AnchorLoader anchorLoader;
    public const string NumUuidsPlayerPref = "numUuids";

    public List<GameObject> SpatialAnchorPrefabs;
    public List<OVRSpatialAnchor> anchors = new();

    private OVRSpatialAnchor lastCreatedAnchor;

    void Awake() {
        if (instance == null) {
            instance = this;
        }
    }

    void Start() {
        anchorLoader = GetComponent<AnchorLoader>();
    }

    public void CreateSpatialAnchor(int prefabIndex) {
        var position = new Vector3(OVRInput.GetLocalControllerPosition(OVRInput.Controller.RTouch).x, 0, OVRInput.GetLocalControllerPosition(OVRInput.Controller.RTouch).z);
        var rotation = Quaternion.Euler(0, OVRInput.GetLocalControllerRotation(OVRInput.Controller.RTouch).eulerAngles.y, 0);
        
        GameObject workingAnchor = Instantiate(SpatialAnchorPrefabs[prefabIndex], position, rotation);        

        StartCoroutine(AnchorCreated(workingAnchor.GetComponent<OVRSpatialAnchor>()));
    }

    private IEnumerator AnchorCreated(OVRSpatialAnchor workingAnchor) {
        while (!workingAnchor.Created && !workingAnchor.Localized) {
            yield return new WaitForEndOfFrame();
        }
        
        Guid anchorGuid = workingAnchor.Uuid;
        anchors.Add(workingAnchor);

        var saveData = workingAnchor.GetComponent<SpatialAnchorSaveData>();
        saveData.AnchorUuid = anchorGuid;

        lastCreatedAnchor = workingAnchor;

        Debug.Log("Created anchor with UUID: " + anchorGuid);
    }

    public async void SaveLastCreatedAnchor() {
        if (lastCreatedAnchor == null) {
            Debug.Log("No anchor to save");
            return;
        }
        var result = await lastCreatedAnchor.SaveAnchorAsync();
    
        if (result.Success) {
            Debug.Log("Saved anchor with UUID: " + lastCreatedAnchor.Uuid);
            var saveData = lastCreatedAnchor.GetComponent<SpatialAnchorSaveData>();
            SaveUuidToPlayerPrefs(saveData);
        } else {
            Debug.Log("Failed to save anchor");
        }
    }

    void SaveUuidToPlayerPrefs(SpatialAnchorSaveData data) {
        if (!PlayerPrefs.HasKey(NumUuidsPlayerPref)) {
            PlayerPrefs.SetInt(NumUuidsPlayerPref, 0);
            Debug.Log("Save: NumUuidsPlayerPref not found, creating new one");
        }
        
        int playerNumUuids = PlayerPrefs.GetInt(NumUuidsPlayerPref);
        PlayerPrefs.SetString("uuid" + playerNumUuids, data.ToString());
        Debug.Log("Saved UUID to player prefs: " + data.ToString() + " with key: " + "uuid" + playerNumUuids);
        PlayerPrefs.SetInt(NumUuidsPlayerPref, ++playerNumUuids);
    }

    public async void UnSaveLastCreatedAnchor() {
        if (lastCreatedAnchor == null) {
            Debug.Log("No anchor to unsave");
            return;
        }

        var result = await lastCreatedAnchor.EraseAnchorAsync();

        if (result.Success) {
            Debug.Log("Unsaved anchor with UUID: " + lastCreatedAnchor.Uuid);
            anchors.Remove(lastCreatedAnchor);
            Destroy(lastCreatedAnchor.gameObject);
        } else {
            Debug.Log("Failed to unsave anchor");
        }
    }

    public void UnsaveAllAnchors() {
        foreach (var anchor in anchors) {
            if (anchor == null) continue;
            UnsaveAnchor(anchor);
            Destroy(anchor.gameObject);
        }
        
        anchors.Clear();
        ClearAllUuidsFromPlayerPrefs();
        Debug.Log("Unsaved all anchors method called");
    }

    private async void UnsaveAnchor(OVRSpatialAnchor anchor) {
        await anchor.EraseAnchorAsync();
    
        Debug.Log("Unsaved anchor with UUID: " + anchor.Uuid);
    }

    private void ClearAllUuidsFromPlayerPrefs() {
        if (PlayerPrefs.HasKey(NumUuidsPlayerPref)) {
            int playerNumUuids = PlayerPrefs.GetInt(NumUuidsPlayerPref);
            for (int i = 0; i < playerNumUuids; i++) {
                PlayerPrefs.DeleteKey("uuid" + i);
            }
            PlayerPrefs.DeleteKey(NumUuidsPlayerPref);
            PlayerPrefs.Save();
        }
    }

    public void LoadSavedAnchors() {
        anchorLoader.LoadAnchorsByUuid();   
    }
}

Spatial Anchor Save Data script

using System;
using UnityEngine;

public class SpatialAnchorSaveData : MonoBehaviour
{
    public Guid AnchorUuid;
    public string Name;

    public override string ToString() {
        return $"{AnchorUuid}, {Name}";
    }

    public static SpatialAnchorSaveData CreateFromString(string data)
    {
        GameObject tempGameObject = new("TempSaveData");
        var saveData = tempGameObject.AddComponent<SpatialAnchorSaveData>();
        saveData.FromString(data);
        return saveData;
    }

    public SpatialAnchorSaveData FromString(string data) {
        var parts = data.Split(new[] { ", " }, StringSplitOptions.None);
        if (parts.Length != 2) {
            throw new FormatException("Invalid data format");
        }
        AnchorUuid = new Guid(parts[0]);
        Name = parts[1];
        return this;
    }
}

That sounds great! I will try soon. I am desperate looking for a simple approach to spawn several prefabs on a previously fixed position on every game load, and such obvious and simple thing seems so complicated.

I hope i will understand how to implement and include your scripts.

Thanks a lot for the work and for sharing it!

Hey! I was like … 4 hours trying to implement your system, but once I load again my game, it just spawns nothing on the previously spawned anchors, I tried to figure out how to make it work … but i have to give up.

Could you please guide me on how to make it work to spawn automatically my prefabs on game load on the previously positioned anchors??

I would appreciate it so much

Thanks a lot in advance

Make sure that you have added the components OVRSpatialAnchor and SpatialAnchorSaveData to each prefab you are trying to spawn and save. In the inspector under SpatialAnchorSaveData make sure to fill in a name.

What I probably forgot to put in my post is that you have to manually save each anchor.
I use Meta’s Controller Buttons Mapper Building Block:

Another thing is that you have to call LoadSavedAnchors() somewhere at the start of your application. I do it in the MRUK Building Block:

If you did that, try the application while you are running it in the Unity Editor instead of building it on the headset so you can see if it actually saves something and what it saves into PlayerPrefs, it should save the UUID and the name of the anchor in the format: uuid, name.
PlayerPrefs is located on Windows here:
Computer\HKEY_CURRENT_USER\SOFTWARE\Unity\UnityEditor{CompanyName}{ApplicationName}
It should look something like this if you have saved a couple anchors:

Hopefully this helps you figure it out. If not feel free to ask, I am happy to help.

Thank you so much!! It took me a while, but now i got it working.

I modified the script, so now it cycles through the list of prefabs in sequential order, ALSO! It saves anchors automatically one second after spawning them :slight_smile:
Also after unsaving all anchors, it resets the cycling.

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

public class SpatialAnchorManager : MonoBehaviour
{
    public static SpatialAnchorManager instance;

    private AnchorLoader anchorLoader;
    public const string NumUuidsPlayerPref = "numUuids";

    public List<GameObject> SpatialAnchorPrefabs;  // List of prefabs for anchors
    public List<OVRSpatialAnchor> anchors = new();  // List of anchors that will be created

    private OVRSpatialAnchor lastCreatedAnchor;

    private int currentPrefabIndex = 0;  // To keep track of which prefab is next
    private int currentAnchorIndex = 0;  // To keep track of which anchor is next

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    void Start()
    {
        anchorLoader = GetComponent<AnchorLoader>();
    }

    // Create and cycle through the spatial anchors sequentially
    public void CreateSpatialAnchor()
    {
        if (SpatialAnchorPrefabs.Count == 0 || anchors.Count == 0)
        {
            Debug.LogError("No prefabs or anchors available.");
            return;
        }

        GameObject currentPrefab = SpatialAnchorPrefabs[currentPrefabIndex];
        OVRSpatialAnchor currentAnchor = anchors[currentAnchorIndex];

        if (currentPrefab == null || currentAnchor == null)
        {
            Debug.LogError("Prefab or Anchor is null at index: " + currentPrefabIndex);
            return;
        }

        Vector3 position = OVRInput.GetLocalControllerPosition(OVRInput.Controller.RTouch);
        Quaternion rotation = OVRInput.GetLocalControllerRotation(OVRInput.Controller.RTouch);

        GameObject instantiatedAnchor = Instantiate(currentPrefab, position, rotation);

        StartCoroutine(AnchorCreated(instantiatedAnchor.GetComponent<OVRSpatialAnchor>(), currentAnchor));

        currentPrefabIndex = (currentPrefabIndex + 1) % SpatialAnchorPrefabs.Count;
        currentAnchorIndex = (currentAnchorIndex + 1) % anchors.Count;
    }

    private IEnumerator AnchorCreated(OVRSpatialAnchor instantiatedAnchor, OVRSpatialAnchor currentAnchor)
    {
        while (!instantiatedAnchor.Created && !instantiatedAnchor.Localized)
        {
            yield return new WaitForEndOfFrame();
        }

        yield return new WaitForSeconds(1f);

        Guid anchorGuid = instantiatedAnchor.Uuid;
        anchors.Add(instantiatedAnchor);

        SpatialAnchorSaveData saveData = instantiatedAnchor.GetComponent<SpatialAnchorSaveData>();
        saveData.AnchorUuid = anchorGuid;

        lastCreatedAnchor = instantiatedAnchor;

        SaveLastCreatedAnchor();

        Debug.Log("Created anchor with UUID: " + anchorGuid);
    }

    public async void SaveLastCreatedAnchor()
    {
        if (lastCreatedAnchor == null)
        {
            Debug.Log("No anchor to save");
            return;
        }
        var result = await lastCreatedAnchor.SaveAnchorAsync();

        if (result.Success)
        {
            Debug.Log("Saved anchor with UUID: " + lastCreatedAnchor.Uuid);
            var saveData = lastCreatedAnchor.GetComponent<SpatialAnchorSaveData>();
            SaveUuidToPlayerPrefs(saveData);
        }
        else
        {
            Debug.Log("Failed to save anchor");
        }
    }

    void SaveUuidToPlayerPrefs(SpatialAnchorSaveData data)
    {
        if (!PlayerPrefs.HasKey(NumUuidsPlayerPref))
        {
            PlayerPrefs.SetInt(NumUuidsPlayerPref, 0);
            Debug.Log("Save: NumUuidsPlayerPref not found, creating new one");
        }

        int playerNumUuids = PlayerPrefs.GetInt(NumUuidsPlayerPref);
        PlayerPrefs.SetString("uuid" + playerNumUuids, data.ToString());
        Debug.Log("Saved UUID to player prefs: " + data.ToString() + " with key: " + "uuid" + playerNumUuids);
        PlayerPrefs.SetInt(NumUuidsPlayerPref, ++playerNumUuids);
    }

    public void ResetCycling()
    {
        currentPrefabIndex = 0;
        currentAnchorIndex = 0;
    }

    public async void UnSaveLastCreatedAnchor()
    {
        if (lastCreatedAnchor == null)
        {
            Debug.Log("No anchor to unsave");
            return;
        }

        var result = await lastCreatedAnchor.EraseAnchorAsync();

        if (result.Success)
        {
            Debug.Log("Unsaved anchor with UUID: " + lastCreatedAnchor.Uuid);
            anchors.Remove(lastCreatedAnchor);
            Destroy(lastCreatedAnchor.gameObject);
        }
        else
        {
            Debug.Log("Failed to unsave anchor");
        }
    }

    public void UnsaveAllAnchors()
    {
        for (int i = anchors.Count - 1; i >= 0; i--)
        {
            var anchor = anchors[i];
            if (anchor == null) continue;

            if (anchor.gameObject.scene.isLoaded)
            {
                UnsaveAnchor(anchor);
                Destroy(anchor.gameObject);
                anchors.RemoveAt(i);
            }
        }

        ClearAllUuidsFromPlayerPrefs();
        Debug.Log("Unsaved all instantiated anchors without modifying prefab or anchor lists.");
    }

    private async void UnsaveAnchor(OVRSpatialAnchor anchor)
    {
        await anchor.EraseAnchorAsync();
        Debug.Log("Unsaved anchor with UUID: " + anchor.Uuid);
    }

    private void ClearAllUuidsFromPlayerPrefs()
    {
        if (PlayerPrefs.HasKey(NumUuidsPlayerPref))
        {
            int playerNumUuids = PlayerPrefs.GetInt(NumUuidsPlayerPref);
            for (int i = 0; i < playerNumUuids; i++)
            {
                PlayerPrefs.DeleteKey("uuid" + i);
            }
            PlayerPrefs.DeleteKey(NumUuidsPlayerPref);
            PlayerPrefs.Save();
        }
    }

    public void LoadSavedAnchors()
    {
        anchorLoader.LoadAnchorsByUuid();
    }
}

Also i created this script that Loads automatically the saved anchors


using UnityEngine;
using UnityEngine.Events;
using System.Collections;

public class SceneLoadEventHandler : MonoBehaviour
{
    // Unity Event exposed in the Inspector
    [Tooltip("Triggered when the scene is loaded.")]
    public UnityEvent OnSceneLoaded;

    private void Start()
    {
        // Start the delayed event coroutine
        StartCoroutine(DelayedInvoke());
    }

    private IEnumerator DelayedInvoke()
    {
        // Wait for 2 seconds before invoking the event
        yield return new WaitForSeconds(2f);

        // Check if there are any saved anchors
        if (PlayerPrefs.HasKey("numUuids") && PlayerPrefs.GetInt("numUuids") > 0)
        {
            OnSceneLoaded?.Invoke();
        }
        else
        {
            Debug.Log("No saved anchors to load.");
        }
    }
}

You don’t need to do this, because the MRUK already has this function build in. So you can just use it to call the LoadSavedAnchors method from the Spatial Anchor Manager.
And you need the MRUK anyway for the scene model.

Also instead of using a Coroutine for delaying the method call, you should use Invoke because you can call it with a delay.

You could do this, but what I did for spawning the anchors is, I created a UI that follows my hand with buttons that send a int to the CreateSpatialAnchor method. I did this so that I have control over which anchor I want to spawn where.

There is a good example of implementing a custom Spatial Anchors Manager in Oculus Samples: Discovery project:

Each anchor is represented by the SpatialAnchorSaveData and can contain a custom type or name of the prefab. This data can be used during the anchor loading method to decide which prefab to instantiate, so you don’t need to use a single prefab as it is in the Spatial Anchors Building Block.
This collection is stored as a JSON file and exchanged with other users, which you can simplify and save locally if you don’t need multiplayer.

The Discover project doesn’t require the MRUK SDK, which can be too much of a hassle if you want to quickly open an app and load your 3D models without scanning your room and detecting walls or furniture.

I did look at the Discover project, but I couldn’t personally figure out how to implement it in my own project.

As for the MRUK, I thought that Scene Support was necessary for Spatial Anchors to work properly, but maybe I misunderstood how it worked.