Placeholder Image

Subtitles section Play video

  • Hi XR developers!

  • In previous videos we've looked at a lot of mixed reality features such as pass-through, scene understanding and environment occlusion.

  • We've also looked at several methods on how to place objects in our room with the MRUK or Mixed Reality Utility Kit or MetaScene API with the OVR Scene Manager.

  • But what if we want to save our objects consistently across game sessions in our scene and load them where they were before?

  • This is where spatial anchors come into play.

  • So today we're going to look at how to create, save, unsave and load spatial anchors in our scene.

  • If you like this type of content, please take a second to like and subscribe to this channel.

  • If you'd like to get access to the source code of each project, please consider to join my Patreon.

  • If you have any questions, feel free to join our growing XR developer community on Discord.

  • And now, let's jump into OVR Spatial Anchors.

  • As always, we start by setting up a new Unity project.

  • I am using the MetaXR SDK version 60.

  • I also installed the Mixed Reality Utility Kit for a later showcase.

  • To know how to use the Utility Kit, watch my video here.

  • Make sure you set up your project correctly with Meta's project setup tool as well.

  • To set up our scene, we used Meta's building blocks, which I explained in detail in a previous video, that you can watch here.

  • For this scene, I only need a camera rig and a pass-through layer.

  • I then created a new game object called Spatial Anchor Manager, where we will attach our custom components in a minute.

  • I would like to start with a simple anchor placement script.

  • Inside this class, we declare a public variable called AnchorPrefab to hold a reference to the prefab you want to instantiate in the game.

  • Inside the update method, there's a conditional check using the OVR input to determine if the primary index trigger button on the right Oculus Touch controller has been pressed.

  • If you are interested to learn in detail how to read the input from your OVR controllers, check out my video about it here.

  • When pressed, it calls the CreateSpatialAnchor method.

  • This method creates an instance of the prefab at the current position and rotation of the right controller, utilizing the OVR input, and then adds an OVRSpatialAnchor component to newly created prefab instance.

  • Let's set it up in our scene now by applying the script to our new game object.

  • We then need a prefab we would like to spawn.

  • The prefab we will use here has no logic on it, it simply contains the visuals of our anchor.

  • Drag the prefab into the inspector, and let's give this a try.

  • Fantastic!

  • As you can see, we can now easily create anchors at the pressing the trigger button.

  • Let's go one step further and see how we can save or unsave these anchors at this exact location in our space.

  • We create a new script for our extended logic that we will call SpatialAnchorManager.

  • We can keep the reference to our prefab, but we want to add a little bit more information about our anchor.

  • We create a private variable for a canvas that we will later add to our prefab.

  • This canvas holds two text fields for the anchor's ID and also a text that tells us if the anchor has been saved or not.

  • We then need a list of anchors to keep track of them in order to save or unsave them.

  • Lastly, we define a private spatial anchor called LastCreatedAnchor to be able to either save or unsave the currently created anchor.

  • Perfect!

  • We check again if we pressed our trigger button, and if so, we call the CreateSpatialAnchor method.

  • The difference here is that we will already apply an OVR spatial anchor component to our prefab, so we can simply instantiate it here without adding this component.

  • We then save the canvas and text fields of this particular anchor in our variables.

  • Our goal is to get the anchor's ID after it has been created.

  • Since this process is working asynchronously by the Oculus Runtime, we can best handle this delay by calling a coroutine which we will call AnchorCreated.

  • In this case, we provide this method with our created anchor and then wait until the creation is complete and the anchor has been localized by the Oculus Runtime.

  • If so, we can go ahead to fetch the ID of the anchor.

  • We then add the newly created anchor to our list of anchors.

  • After that, we save the new anchor as the anchor we last created.

  • This allows us to finally set our ID text as well as our status text.

  • Let's set this up in our Unity scene now.

  • We can remove the old script and apply the SpatialAnchorManager.

  • We then have to apply a new prefab.

  • You can see that I already created one which contains an OVR spatial anchor component.

  • Furthermore, I added a canvas which contains two text fields.

  • We can now apply this prefab to our and test out our new prefab creation logic.

  • Great!

  • As you can see, we are now able to not only create but wait for and display each anchor's ID which is crucial for our next step.

  • In the next step, we want to be able to save and also unsave the currently selected anchor.

  • To do that, we make use of Unity Player Prefs.

  • Unity Player Prefs are a simple key value storage system used to save and load small amounts of data such as player preferences or game settings between game sessions without requiring complex file handling.

  • To save those key values, we create a constant string that will save the anchor's IDs.

  • We use constant strings for Unity Player Prefs keys to prevent typos and ensure consistency.

  • In the update method, we would like to introduce two new buttons.

  • If we press button 1, we would like to save our anchor and we unsave it when pressing button 2.

  • If we now look at the save last created anchor method, we can see that all we need to do is to call the save method.

  • If the saving was successful, we would like to update the status text on the anchor to saved.

  • Lastly, to save the anchor to our Player Prefs, we call the save ID to Player Prefs method and provide it with the anchor's ID.

  • It first checks if the key exists, indicating the total count of IDs saved.

  • If not found, it initializes this count to zero.

  • Then, it retrieves the current number of IDs saved, saves the new ID using a key formed by appending this number to UUID, and increments the saved ID count by updating the string with the new total.

  • Lastly, to unsave, we literally just have to call the erase method from Oculus, from our anchor, and update the status text.

  • Optionally, if you would like to actually delete your anchor, you could now simply call the destroy method from Unity.

  • Let's go back to Unity and give this a go.

  • By pressing the A button, we can now save the anchor and easily unsave it again by pressing the B button.

  • Fantastic!

  • We now built the foundation for our last and final iteration of our code.

  • Here, I would like to show you how to unsave all anchors at once, and how to load anchors we have saved before.

  • Firstly, we create another private variable called anchorLoader.

  • This will be a script we create in a second.

  • Since we want to attach this component to the same game object, we can fetch a reference directly in the awake method.

  • Then, in the update method, if we press the grab button, we want to unsave all the anchors in our list and the ones that are already saved to the player prefs.

  • If we press down on the thumbstick, we would like to load all the anchors we have previously saved to the player prefs.

  • In the unsaveAllAnchors method, we iterate through our list of anchors and call the unsaveAnchor method for each of them.

  • We provide this method with the anchor parameter and call the erase method on that anchor similarly to before.

  • We then get a reference to the status text of each anchor and set the text to not saved.

  • After that, back in the unsaveAllAnchors method, we clear the anchors list and call the clearAllIds from player prefs method to not only remove our list of anchors, but also the anchors that were already saved previously.

  • This method checks if the key exists in player prefs, indicating there are already saved IDs.

  • If it exists, it retrieves the count of saved IDs and iterates through each one, deleting the keys associated with them.

  • After deleting all ID keys, it removes the key itself.

  • Effectively resetting the count and finally calls playerPrefs.save to actually commit these changes to the metaquest device.

  • Next, for loading our saved anchors, we call the loadAnchorsById method from a new script that we are going to create now called anchorLoader.

  • The script starts with a declaration of several important variables.

  • First, we declare a private variable that holds a reference to the anchor prefab.

  • We then create another variable that holds a reference to the SpatialAnchorManager component.

  • Lastly, we create a unity action that defines a callbacks related to anchor loading operations.

  • The boolean parameter indicates the success of the loading operation, since this component will sit on the same game object as the SpatialAnchorManager.

  • We can easily get the component in the awake method.

  • We can then also get a reference to the prefab that we already assigned in the SpatialAnchorManager.

  • Lastly, we call the onLocalized method as soon as our loading process has been successful.

  • This loadAnchorsById method, which we call directly from the SpatialAnchorManager, initiates the process of loading spatial anchors that have been previously saved by their IDs.

  • It starts by checking if there is a record in player prefs for the number of IDs saved.

  • If no record exists, it sets this count to zero, indicating there are IDs to load.

  • It then retrieves the count of IDs and proceeds only if there is at least one ID to load.

  • For each saved ID, it constructs a GUID object from the string retrieved from player prefs.

  • These IDs are used to configure a loadOptions object, specifying that the anchors should be loaded from local storage with no timeout, and then passed to the load method.

  • There is two options of saving and loading anchors, locally or in the cloud.

  • We will look at cloud saving and loading in a different video about shared spatial anchors.

  • The load method is responsible for the asynchronous loading of unbound anchors based on the provided load options, which include the IDs to load, the storage location, and a timeout setting.

  • It calls the static method loadUnboundedAnchors on the OVR spatial anchor to load these anchors, providing a callback function that is called once the loading process completes.

  • For each loaded anchor, the method checks if it is already localized.

  • If so, it immediately triggers the onLoadAnchor callback with the anchor and a true success flag.

  • If the anchor is not currently localizing, it initiates the localization process using the onLoadAnchor callback to handle completion.

  • This callback function is invoked for each anchor that has been successfully localized or was already localized at the time of loading.

  • It first checks if the localization was successful.

  • If not, it exits early.

  • For successful localizations, it instantiates the prefab at the pose of the unbound anchor.

  • It then binds the unbound anchor to this newly instantiated game object, creating a permanent link between the anchor data and the game object.

  • Lastly, it updates the text components of the instantiated anchor game object with the ID of the anchor and a message indicating that the anchor has been loaded from device storage, providing visual feedback within the scene regarding the status of each loaded anchor.

  • Let's attach this script to our game object and press play.

  • We can now test all our features.

  • First, we create some anchors and press the A button on the anchors we want to save.

  • If we then stop the game and start it again, we can simply press the thumbstick to load all previously saved anchors.

  • This process may take a few seconds if the number of anchors is large.

  • By pressing the grab button, we can erase all our anchors from storage.

  • When we then start the game again and try to load our anchors, no anchors will be loaded anymore.

  • Fantastic!

  • This was a lot of code, but you now know exactly how to handle every aspect of spatial anchors.

  • Let's look at one last use case before we close off this video.

  • I prepared a scene using the Mixed Reality Utility Kit, where I want to create some logic to attach anchors to my wall.

  • For this, I replaced the SpatialAnchorManager with a new script called WallPrefabPlacer, which we created in a previous video.

  • I added the whole logic of the SpatialAnchorManager to this script.

  • The only difference is that we need to wait until the room model is created.

  • For this, we create this private variable isInitialized and set it true when the initialized method is called.

  • Then, in the update method, if we initialized our script, we get the controller position and rotation.

  • We then shoot a ray in the direction of our controller and check if we hit a wall in our room using the mRUKSingleton and its getCurrentRoom method.

  • If we hit a wall and we are pressing the trigger button, like before, we would like to call the createSpatialAnchor method, with the difference that we now send the position and rotation of the prefab according to the hit position of the ray.

  • In the inspector, we assign the AnchorManager that contains our script to the SceneLoadedEvent of the MixedRealityUtilityKit component.

  • From there, we can call the initialized method.

  • Also, make sure that you apply a prefab that contains a OVR SpatialAnchor component to the script.

  • I created this beautiful image frame that we can now apply to our walls.

  • Before I forget it, the last thing we have to do is to fetch the new WallPrefabPlacer in our AnchorLoader, instead of the SpatialAnchorManager.

  • Let's give this a go.

  • And as you can see, we can now use the same functionalities as before, with the difference that our anchor now accurately attaches to our walls and gets loaded in exactly the same position when we load them from our device.

  • I can't wait to see what you guys will build with this.

  • Alright guys, and that's it for this video.

  • I hope you learned a lot, and again, please take a second to like and subscribe to this channel.

  • Subscribe to my Patreon if you want to get access to all the source code.

  • If you have any questions, feel free to join our growing XRDeveloper community on Discord.

  • Thank you so much for watching, and see you in the next one.

Hi XR developers!

Subtitles and vocabulary

Click the word to look it up Click the word to find further inforamtion about it