Setting Up Object Persistence with 6D

Rik Basu
6D.ai
Published in
12 min readOct 9, 2018

How To Make a Sample App with the 6D platform

Welcome to the 6D platform. One of the primary features of the 6D Reality Platform is Persistence — meaning that content left by a user in a location is retrievable across app sessions and devices.

We put together this step-by-step guide to help those new to 6D get familiar with the process and required components for relocalization and persistence capabilities on our platform.

In this tutorial, we will make a sample app with a standard Unity sphere that persists in a certain location — demonstrating 6D’s relocalization and persistence capabilities.

We will go through setting up the project first, then we will create a simple AR app where we can place the sphere into the world. Lastly, we will add in functionality from the 6D platform that will enable persistence across app sessions.

Set Up the Project

Before you start, please download the 6D SDK unity package from the 6D Developer Dashboard (https://dashboard.6d.ai/)

Please note, the following information is key to building your project.
The project will not build successfully otherwise.

  1. Open Unity and start a new project. Make sure Template is set to 3D.

2. After creating the project, go to File > Build Settings, select iOS as your platform, and click the Switch Platform button.

3. Still within the Build Settings window, click the Player Settings button. This will bring up a list of options within the Inspector window on the right. Several options within the Inspector must be adjusted.

4. In the ‘Rendering’ section, uncheck the Auto Graphics API option. Use the Graphics APIs parameter to add OpenGLES3 using the plus icon, and then delete Metal using the minus icon. The result should look like this:

5. In the ‘Configuration’ section, find Camera Usage Description and Location Usage Description. We cannot leave these blank, so type something into both of the corresponding text fields. The result should look like this:

6. Also in the ‘Configuration’ section, select the Allow ‘unsafe’ Code option. The result should look like this:

7. Go to Assets > Import Package > Custom Package and find where you saved the 6D SDK .unitypackage we downloaded earlier. Make sure all components are selected, then click the Import button.

8. Remove the Main Camera and Directional Light objects from the Hierarchy.

9. Finally, go into the Assets/6D SDK/Prefabs directory and drag the AR Background prefab and the AR Scene prefab to the GameObject Hierarchy.

You now have the necessary files and prefabs for a new project using the 6D SDK. Your screen should look similar to the image below:

Make a UI

A UI is necessary for buttons and icons, so let’s create one! Begin by creating a new Canvas GameObject [GameObject>UI>Canvas], and name it AR Debug UI.

  1. Select the AR Debug UI object, go to the Inspector, and in the ‘Canvas Scaler’ section change the UI Scale Mode to Scale with Screen Size.
  2. In that same section, change the Reference Resolution to 2436 by 1125 (this is the resolution of the iphone X).
  3. Also in that same section, change the Screen Match Mode to Shrink.
  4. Add a Canvas Renderer component. [Add Component button > Rendering > Canvas Renderer]

Add in App Functionality

Now that we have our bare bone project and UI, we can begin constructing a functional app. We will start with a simple app that allows the user to spawn an object into the world by tapping on the screen. We will need two scripts for this functionality, and we have one of them already.

  1. A game controller script, which allows us to actually place and save a sphere in the real world. We will create this script first.
  2. An SDK controller script which interacts with the 6D API. We already have this script in the bare bone project, it is included in the AR Scene prefab.

Part I: Placing an Object into the AR Scene

  1. First, we need to create the object that we will have the user spawn. In this example, we will use a simple sphere prefab:
    a) Create a sphere in the scene. [GameObject>3DObject>Sphere]
    b) Drag the sphere object from the scene and drop it into the prefab folder.
    c) Now delete the sphere that is in the scene.
    d) Click on the prefab sphere you made. In the ‘Transform’ section of the inspector, change the sphere’s Scale to 0.2 for all axes.
  2. Next, we need to create a script that will handle placing the object.
    Create an empty object [GameObject>Empty] and call it GameController.
  3. Select the GameController object, and add a new script as a component. [Add Component button > New Script].
  4. Name the new script GameController. The new script GameController.cs will automatically be saved to the Assets folder of your project.
  5. Open GameController.cs in your favorite text editor and paste this code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class GameController : MonoBehaviour {public Camera ARCamera;public GameObject ballReference;private List<GameObject> balls;void Start()
{
balls = new List<GameObject>();
}
void Update()
{
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
LaunchBall(touch);
}
}
void LaunchBall(Touch touch)
{
if (EventSystem.current.currentSelectedGameObject == null &&
touch.phase == TouchPhase.Began)
{
Vector3 position = new Vector3(Input.mousePosition.x, Input.mousePosition.y, .5f);
position = ARCamera.ScreenToWorldPoint(position);
GameObject ball = Instantiate(ballReference, position, ballReference.transform.rotation);
balls.Add(ball);
}
}
}

6. After adding this code and saving, go back to the Unity Engine, and select the GameController object. In the Inspector, you will notice that some new values are available for the GameController script: Ball Reference and AR Camera. Drag the sphere from the prefab folder into the empty field next to Ball Reference.

7. Drag the ARCamera from our scene into the the empty field next to AR Camera.

You should have something like this:

8. Save this project and build it. Instructions on building a 6D application can be found here.

9. If your build was successful, you should now have a simple app that places a sphere half a meter in front of you if you tap the screen. Great! We learned how to place an object in AR.

The next step is to add in some persistence via the 6D SDK.

Part II: Implement Object Persistence

Now, let’s persist this content across sessions. We will need to modify the GameController script, and we will also need to add a script called FileControl. The file control script allows us to save and load the actual data of the spheres location in the world in a csv file.

Open GameController.cs, and replace the code you have with this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using SixDegrees;
public class GameController : MonoBehaviour
{
#if UNITY_IOS
[DllImport("__Internal")]
public static extern void GetAPIKey(StringBuilder apiKey, int bufferSize);
#else
public static void GetAPIKey(StringBuilder apiKey, int bufferSize) { }
#endif
public Camera ARCamera;
public GameObject ballReference;public FileControl fileControl;public SDKController sdkController;private List<GameObject> balls;private static string apiKey = "";private string filename;void Start()
{
balls = new List<GameObject>();
sdkController.OnSaveSucceededEvent += SaveCSV;
sdkController.OnLoadSucceededEvent += RetrieveFile;
}
void Update()
{
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
LaunchBall(touch);
}
}
void LaunchBall(Touch touch)
{
if (EventSystem.current.currentSelectedGameObject == null &&
touch.phase == TouchPhase.Began)
{
Vector3 position = new Vector3(Input.mousePosition.x, Input.mousePosition.y, .5f);
position = ARCamera.ScreenToWorldPoint(position);
GameObject ball = Instantiate(ballReference, position, ballReference.transform.rotation);
balls.Add(ball);
}
}
private void GetFilename()
{
if (string.IsNullOrEmpty(apiKey))
{
StringBuilder sb = new StringBuilder(32);
GetAPIKey(sb, 32);
apiKey = sb.ToString();
}
if (string.IsNullOrEmpty(apiKey))
{
Debug.Log("API Key cannot be found");
filename = "";
}
if (string.IsNullOrEmpty(SDPlugin.LocationID))
{
Debug.Log("Location ID is missing");
filename = "";
}
filename = apiKey + "-" + SDPlugin.LocationID;
}
public void SaveCSV()
{
GetFilename();
if (string.IsNullOrEmpty(filename))
{
Debug.Log("Error evaluating the filename, will not save content CSV");
return;
}
string filePath = GetPath();
StreamWriter writer = new StreamWriter(filePath);
writer.WriteLine(balls.Count);
for (int i = 0; i < balls.Count; i++)
{
writer.WriteLine(balls[i].transform.position.x + "," + balls[i].transform.position.y + "," + balls[i].transform.position.z);
}
writer.Flush();
writer.Close();
StartCoroutine(fileControl.UploadFileCoroutine(filename));
}
public void ReadTextFile(string csv)
{
StringReader reader = new StringReader(csv);
string line = reader.ReadLine();
int ballCount = int.Parse(line);
for (int i = 0; i < ballCount; i++)
{
line = reader.ReadLine();
string[] parts = line.Split(',');
Vector3 ballPosition = new Vector3();
ballPosition.x = float.Parse(parts[0]);
ballPosition.y = float.Parse(parts[1]);
ballPosition.z = float.Parse(parts[2]);
GameObject ball = Instantiate(ballReference, ballPosition, ballReference.transform.rotation);
balls.Add(ball);
}
reader.Close();
}
public string GetPath()
{
return Application.persistentDataPath + "/" + SDPlugin.LocationID + ".csv";
}
public void RetrieveFile()
{
GetFilename();
if (string.IsNullOrEmpty(filename))
{
Debug.Log("Error evaluating the filename, will not load content CSV");
return;
}
StartCoroutine(fileControl.GetTextCoroutine(filename));
}
}

Create the FileControl Script

You might see some errors pop up, but that’s because we don’t have a FileControl object or script yet. Similar to what we did with the GameController script, we are going to create the FileControl script:

  1. Create an empty object [GameObject>Empty] and call it FileControl.
  2. Select the FileControl object, and add a new script as a component. [Add Component button > New Script].
  3. Name the new script FileControl. The new script FileControl.cs will automatically be saved to the Assets folder of your project.
  4. Open FileControl.cs in your favorite text editor and paste this code:
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class FileControl : MonoBehaviour
{
public string downloadURL = "https://persistence-demo.api.6d.ai/?action=get&file=";public string uploadURL = "https://persistence-demo.api.6d.ai/?action=post&file=";public GameController gameController;public IEnumerator GetTextCoroutine(string locID)
{
string fullDownloadURL = downloadURL + locID + ".csv";
UnityWebRequest www = UnityWebRequest.Get(fullDownloadURL);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
else
{
string csv = www.downloadHandler.text;
gameController.ReadTextFile(csv);
}
yield return null;
}
public IEnumerator UploadFileCoroutine(string filename)
{
string localFileName = gameController.GetPath();
string fullUploadURL = uploadURL + filename + ".csv";
WWW localFile = new WWW("file:///" + localFileName);
yield return localFile;
if (localFile.error == null)
{
Debug.Log("Loaded file successfully");
}
else
{
Debug.Log("Open file error: " + localFile.error);
yield break;
}
WWWForm postForm = new WWWForm();
postForm.AddBinaryData("Datafile", localFile.bytes, localFileName, "text/plain");
WWW upload = new WWW(fullUploadURL, postForm);
yield return upload;
if (upload.error == null)
{
Debug.Log("upload done :" + upload.text);
}
else
{
Debug.Log("Error during upload: " + upload.error);
}
yield return null;
}
}

Configure the Scene

Now that the FileControl object and script are created, we can configure the scene:

  1. Select the FileControl object in the Hierarchy.
  2. In the Inspector, find the empty field next to Game Controller and drag your GameController object into it.
  3. Select the GameController object in the Hierarchy.
  4. In the Inspector, find the empty field next to File Control and drag your FileControl object into it.
  5. Find the empty field next to SDK Controller, and drag the AR Scene object into it.
  6. We also need to create a small script that will verify your 6D SDK API key. Using your favorite text editor, create a file named GetAPI.mm in your Assets/plugins/ios folder with the following text:
extern "C" {
void GetAPIKey(char* apiKey, int bufferSize)
{
NSString* plistFile = [[NSBundle mainBundle] pathForResource:@"SixDegreesSDK" ofType:@"plist"];
if (plistFile)
{
NSDictionary *plistDict = [NSDictionary dictionaryWithContentsOfFile:plistFile];
if (plistDict)
{
id dictApiKey = [plistDict valueForKey:@"SIXDEGREES_API_KEY"];
if (dictApiKey && [dictApiKey isKindOfClass:[NSString class]])
{
strcpy(apiKey, [dictApiKey UTF8String]);
}
}
}
}
}

Save and Load Buttons

We need buttons to enable a user to save their session, and load up the map/content from a previous session:

  1. First we need to add two child buttons to AR Debug UI. Right click on the AR Debug UI object, and select UI > Button. Rename the button ‘Save Button’. Repeat this procedure to create a ‘Load Button’.
  2. Resize and position the buttons. Click on a button, and within the ‘Rect Transform’ section of the Inspector make the width 200, and height 100.
  3. Within that same section, change the Pos X and Pos Y values to whatever you prefer. For this example, we will place the Save Button at -250x, and -150y, and the Load Button at -250x, and -350y.
  4. Now we need to make sure the buttons do something. Select the Save Button object, and within the Inspector find the On Click () parameter within the ‘Button(Script)’ section. Click the plus button to add an OnClick() callback for the Save Button.
  5. Drag the AR Scene object from the Hierarchy into the empty field.
  6. Change the function from No Function to SDKController > Save ().
  7. Repeat steps 4–6 for the Load Button, but make sure to change the function from No Function to SDKController > Load () instead.
  8. Finally, we need to make sure the buttons have the proper text on them. Expand the Save Button object in the Hierarchy, and click on the Text object. In the ‘Text(Script)’ section of the Inspector, type a suitable label for the save button.
  9. Repeat step 8 for the Load Button object.

You should have something that looks like this:

Build the App

You can now save the project and build the app! The walkthrough for building a 6D application can be found here. Once the app is built, it should have the following capabilities:

  1. The user can place spheres into the world with a tap on the screen.
  2. Clicking on the save button will save the map and the position of those spheres.
  3. The app will ‘remember’ the location of the spheres across sessions. Try closing the app and opening it again. Once re-opened, press the Load button. It may take a few seconds, but your app should recognize the environment, relocalize, and spawn the spheres from your last app session in the place that you left them.

Congrats! You have successfully built an app where objects can be placed into the AR scene with basic persistence.

Please post on the developer Slack with any questions and comments. We are looking forward to seeing what you build.

Additional Explanations

We did a lot of copy-pasting in the tutorial, and did not explain a whole lot about what exactly the prefabs and scripts are doing. This section contains a more in-depth explanation of the game objects, prefabs, and scripts that we used for this tutorial.

Explanation of GameObjects

AR Background — This has two main elements. The BackCam and the BackCanvas, with a child object, the CameraImage.

  • BackCam — This has the SDBackCam script attached to it as well as a camera component.
  • BackCanvas and CameraImage — There is one public RawImage you can set. This is the CameraImage, which is a child of the BackCanvas to render the background to a texture.

AR Scene — The AR Scene has two scripts that allow the actual AR experience to happen.

  • SD Plugins — This script is the plugin to the native code.
  • SDKController — This script allows the developer to tap into the 6D API (Saving/Loading maps).

AR Camera — This is the camera used to place content on top of the real world. (Augmented Reality). The SDCamera script’s main function is to update the projection matrix.

AR Mesh — This combines GameObject with the SDMesh script. This script allows the 6D SDK to mesh the real world.

Explanation of Scene

In GameController, we have a native method called GetAPIKey as well as the string filename. GetAPIKey is called at runtime, and allows us to access the APIKey from the Sixdegrees.plist file. We use this key as well as the locationID (given to us by SDPlugin) to uniquely name the file that we are saving to our 6D persistence server. This filename is saved in the filename string when GetFilename is called.

In our Start function, you may notice we have added:

sdkController.OnSaveSucceededEvent += SaveCSV;sdkController.OnLoadSucceededEvent += RetrieveFile;

What this does is calls the function SaveCSV when OnSaveSucceededEvent succeeds, and RetrieveFile when the OnLoadSuccededEvent succeeds.

Save Scene

  1. Call SDKController.Save -> Call GameController.SaveCSV -> Call FileControl.UploadFileCoroutine.

In this example, this order is necessary because once the map is saved using SDKController we save the locationID to SDPlugin.locationID. Now we use this locationID and the APIKey as unique identifiers for the filename we are saving to the 6D persistence file server. Eventually once you make a game or experience with your own content, you will want to change the upload URL to point to your content server.

SaveCSV saves the positions of the spheres in a csv file to your phone. SaveCSV’s counterpart is ReadTextFile which takes a string, and parses the data to allow you to re-instantiate the spheres from the data in the csv file.

Explanation of FileControl Script

  • UploadFileCoroutine — Serializes the csv data that was saved to the phone from the SaveCSV function, and uploads it to the uploadURL (our 6D persistent content server).
  • GetTextCoroutine — Uses the filename from earlier (APIKey and locationID), to identify which file we want to get at from the 6D persistent content server, downloads it, and then tells GameController to read and parse the file.
  • SDKController — Directly interacts with the 6D API. Here is where SixDegreesSDK_SaveToARCloud, and SixDegreesSDK_LoadFromARCloud.

--

--