Skip to main content

Initialize the app

This guide is the second part of a five-part series on creating a wayfinding experience that helps a user understand where they are, choose a destination, localize to that location, and follow AR guidance in the space. The first page, Set up the project, shows how to configure an existing project with the Niantic Spatial SDK (NSDK).

This section shows you how to initialize the wayfinding app in the following four steps:

Initialize the Unity app workflow: create the Landing scene, present sign-in, request permissions, and load the placeholder SitesMap scene.

Figure: Initialize the app creates WayfindingLanding, handles sign-in and device permissions, and loads the placeholder WayfindingSitesMap scene. Build the map experience replaces that placeholder with the live Sites map and selection flow.

Files overview

The Unity landing step has four responsibilities:

  1. Create the Landing scene.
  2. Present sign-in.
  3. Request permissions.
  4. Load the Sites map.

Your app can organize those responsibilities in different files. The following table shows one working structure for the standalone Unity app in this tutorial and the matching files and scenes in the vps2-wayfinding-sample sample.

ResponsibilityStandalone appSample reference
Create the Landing sceneAssets/Scripts/WayfindingSceneNames.cs, Assets/Scenes/WayfindingLanding.unity, and Assets/Scenes/WayfindingSitesMap.unity create the landing flow and the next scene it opensAssets/Scripts/WayfindingScenes.cs, Assets/Scenes/Landing.unity, and Assets/Scenes/SitesMap.unity
Present sign-inAssets/Scripts/Auth/AuthEndpoints.cs, Assets/Scripts/Auth/AuthRequests.cs, Assets/Scripts/Auth/LoginManager.cs, Assets/Scripts/Auth/NSSampleSessionManager.cs, and Assets/Resources/Auth/WayfindingSignInEndpoints.asset restore a sample session or start sign-inAssets/Scripts/Auth/AuthEndpoints.cs, Assets/Scripts/Auth/AuthRequests.cs, Assets/Scripts/Auth/LoginManager.cs, Assets/Scripts/Auth/NSSampleSessionManager.cs, and Assets/Resources/Auth/AuthProdEndpoints.asset
Request permissionsAssets/Scripts/WayfindingPermissionHelper.cs checks camera and location accessAssets/Scripts/PermissionHelper.cs
Load the Sites mapAssets/Scripts/Landing/WayfindingLandingController.cs updates the Landing UI and loads WayfindingSitesMap when sign-in and permissions are readyAssets/Scripts/Landing/LandingController.cs

Create the Landing scene

To create the app-entry part of the Unity wayfinding flow, set up the first scenes so the app opens one shared Landing scene. Later steps on this page reuse those scenes when they add sign-in, permissions, and the Sites scene transition.

There are two steps to create the Landing scene, shown in detail in the following sections:

  1. Create scene names for the landing flow.
  2. Create the Landing scene shell so later steps can wire the UI and controller.

Use the following steps to create or update the Landing scene:

Create scene names

Create a new empty C# script, Assets/Scripts/WayfindingSceneNames.cs to keep the scene names in one place:

// Keep scene identifiers in one place so scene transitions do not repeat string literals.
// In your own app, replace these constants with your scene names or routing abstraction.
public static class WayfindingSceneNames
{
// Each value must exactly match an enabled scene in the Unity Scene List.
public const string Landing = "WayfindingLanding";
public const string SitesMap = "WayfindingSitesMap";
public const string WayfindingAR = "WayfindingAR";
}

Create the Landing scene shell

Create the Landing and Sites scenes, make WayfindingLanding the first scene the app opens, and add the initial Landing layout and placeholder UI:

  1. Create two new empty scenes, and save them as Assets/Scenes/WayfindingLanding.unity and Assets/Scenes/WayfindingSitesMap.unity.
  2. Add both scenes to File > Build Profiles, with WayfindingLanding first.
  3. Copy the sample UI assets into your app so this scene can use the same icon and gradient resources. In the sample project, copy Assets/Resources/UI/ into your app at Assets/Resources/UI/, including each asset's .meta file. Do not place these files under Assets/TextMesh Pro/Resources/.
  4. Open Assets/Scenes/WayfindingLanding.unity, then build the Landing UI in four parts:
    1. Create the full-screen Landing layout:
      1. In the Hierarchy for the open scene, create UI > Canvas and rename it LandingCanvas.
      2. In LandingCanvas, in the Canvas Scaler component, set UI Scale Mode to Scale With Screen Size, Reference Resolution to 1080 x 1920, and Match to 0.5.
      3. In LandingCanvas, select Create Empty and name the new GameObject UIContent.
      4. With UIContent selected, set its anchor preset to stretch full screen, then in the Rect Transform set Left, Right, Top, and Bottom to 0 so the layout can place the landing content across the screen.
      5. With UIContent selected, use Add Component in the Inspector to add a Vertical Layout Group.
      6. In the Vertical Layout Group, set Padding > Top to 300 and set Child Alignment to Upper Center to stack the landing content from the top.
      7. In the same Vertical Layout Group, clear Control Child Size for Width and Height, clear Use Child Scale for Width and Height, leave Child Force Expand checked for Width, and clear Child Force Expand for Height.
    2. Add the background and app branding:
      1. In LandingCanvas, create UI > Image and name it Background.
      2. With Background selected, set its anchor preset to stretch full screen, then in the Rect Transform set Left, Right, Top, and Bottom to 0.
      3. In the Image component, set Color to Hexadecimal FDFDFD and drag Assets/Resources/UI/GradientShader into Material to render the landing gradient.
      4. In UIContent, create UI > Raw Image and name it Icon.
      5. With Icon selected, set Width and Height to 750, drag Assets/Resources/UI/spatial-icon.png into the Texture field so the landing screen displays the app icon, and set Color to Hexadecimal A9B7FA.
      6. Use Add Component to add a Layout Element to Icon, then leave its values at the defaults.
    3. Add the title and primary sign-in action:
      1. In UIContent, create UI > Legacy > Text and name it TitleLabel.
      2. With TitleLabel selected, in the Text field enter Niantic Spatial VPS2, add a blank line, and enter Wayfinding Sample.
      3. Set Width to 900, Height to 210, Font Size to 60, Font Style to Bold, Horizontal Overflow to Wrap, Vertical Overflow to Overflow, Alignment to centered, Best Fit on, and Color to Hexadecimal 274CC6.
      4. Use Add Component to add a Layout Element to TitleLabel, then set Preferred Width to 900 and Layout Priority to 2.
      5. In UIContent, create UI > Legacy > Button and name it PrimaryButton.
      6. With PrimaryButton selected, set Width to 900, Height to 108, and in the Image component set Color to Hexadecimal 2563EB.
      7. In the child Text object under PrimaryButton, set the Text field to Sign in to get started, set Font Size to 36, set Alignment to centered, and set Color to Hexadecimal FFFFFF.
    4. Add the message area for sign-in and permissions:
      1. In UIContent, create UI > Legacy > Text and name it StatusLabel.
      2. With StatusLabel selected, clear the Text field so it starts blank.
      3. Set Width to 800, Height to 280, Font Size to 36, Alignment to centered, Best Fit on, Horizontal Overflow to Wrap, Vertical Overflow to Overflow, and Color to Hexadecimal 1F2937.
      4. Use Add Component to add a Layout Element to StatusLabel, then set Preferred Width to 800 and Preferred Height to 280.

Validate this step:

  • In the Game view, confirm that the landing screen shows the gradient background, the large icon, the blue two-line title, the blue sign-in button, and a blank area for status text following the button, as shown in the following image:

    The Unity landing scene contains a background, icon, text, and a button.

Present sign-in

This step adds the copied sign-in helper files, sign-in settings asset, and mobile browser callback settings that the Landing scene uses to start sign-in and restore a sample session.

This step has three parts:

  1. Copy the sign-in helper scripts from the sample.
  2. Create the sign-in endpoints asset that those scripts load at runtime.
  3. Register the mobile sign-in callback so browser sign-in can reopen the app.

Use the following steps to create or update your sign-in flow:

Copy the sign-in helper scripts

Copy the sample sign-in helpers and types used by the Landing controller:

  1. Create Assets/Scripts/Auth/.
  2. Copy AuthEndpoints.cs, AuthRequests.cs, LoginManager.cs, and NSSampleSessionManager.cs from the sample app's Assets/Scripts/Auth/ folder into your app.
  3. Open Assets/Scripts/Auth/LoginManager.cs and change ?redirectType=nsdk-external to ?redirectType=nsdk-samples so the sign-in request matches the callback registered later on this page.
  4. Let Unity finish recompiling before you continue.

Create the sign-in endpoints asset

Create Assets/Resources/Auth/WayfindingSignInEndpoints.asset to store the sign-in endpoint settings used by the Landing scene as follows:

  1. Create Assets/Resources/Auth/.
  2. In the new folder, create a scriptable object by selecting Create > Scriptable Objects > AuthEndpoints.
  3. Name the asset WayfindingSignInEndpoints so Resources.Load<AuthEndpoints>("Auth/WayfindingSignInEndpoints") can find it.
  4. Leave the default endpoint values from the sample unless you have a different sign-in environment for your app.

Validate this step:

  • Select Assets/Resources/Auth/WayfindingSignInEndpoints.asset and make sure the Inspector shows editable sign-in endpoint fields instead of a missing script.

Register the mobile sign-in callback

Register the callback for the platform you plan to test:

Update Assets/Plugins/Android/AndroidManifest.xml so Android can route nsdk-samples://signin back into the app:

  1. In the Unity Project window, create Assets/Plugins/Android/ if it does not already exist.
  2. If Assets/Plugins/Android/AndroidManifest.xml already exists, open it and update the activity that launches the Unity app. Keep the rest of the file as-is.
  3. Add the following VIEW intent filter to that activity so Android can reopen the app after sign-in:
<!-- Handle the browser callback that returns to the Unity app after sign-in. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Match the callback URL used by LoginManager.cs: nsdk-samples://signin -->
<data android:scheme="nsdk-samples" android:host="signin" />
</intent-filter>
  1. If you do not already have a manifest file, create Assets/Plugins/Android/AndroidManifest.xml and use the following full example:
Expand to view AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<!-- Launch the Unity player activity when the user opens the app. -->
<activity
android:name="com.unity3d.player.UnityPlayerGameActivity"
android:theme="@style/BaseUnityGameActivityTheme"
android:exported="true">
<!-- Normal app launch from the launcher icon. -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Browser callback that returns to the app after sign-in completes. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Match the callback URL used by LoginManager.cs: nsdk-samples://signin -->
<data android:scheme="nsdk-samples" android:host="signin" />
</intent-filter>
</activity>
</application>
</manifest>

If you plan to build for both Android and iOS, complete both platform-specific callback settings before you test sign-in on each device type.

This callback setup is validated later when sign-in finishes in the browser and returns to the app on device.

Request permissions

This step adds one shared helper that checks camera and fine-location access before the Landing scene opens the Sites scene. A developer token can satisfy sign-in for testing, but it does not grant camera or location access. This step still needs to complete before the app can open WayfindingSitesMap.

Create Assets/Scripts/WayfindingPermissionHelper.cs to check and request camera and location permissions:

Expand to view WayfindingPermissionHelper.cs
using System;
using System.Collections;
using UnityEngine;
#if UNITY_ANDROID
using UnityEngine.Android;
#endif

/// <summary>
/// Checks and requests the camera and location permissions required by the wayfinding flow.
/// Android uses runtime permission callbacks. iOS requests camera authorization and starts
/// Unity's location service, which triggers the location prompt when needed.
///
/// In your own app, replace this helper if permission ownership already lives in an app-level
/// service. Preserve the rule that both permissions complete before Sites or AR starts.
/// </summary>
public static class WayfindingPermissionHelper
{
// Report permission state using the API for the active player platform.
public static bool HasRequiredPermissions()
{
#if UNITY_ANDROID && !UNITY_EDITOR
return Permission.HasUserAuthorizedPermission(Permission.Camera)
&& Permission.HasUserAuthorizedPermission(Permission.FineLocation);
#elif UNITY_IOS && !UNITY_EDITOR
return Application.HasUserAuthorization(UserAuthorization.WebCam)
&& Input.location.isEnabledByUser;
#else
// Editor playback and spoofed-location validation do not use mobile permission prompts.
return true;
#endif
}

public static void RequestRequiredPermissions(MonoBehaviour coroutineHost, Action<bool> onComplete)
{
if (HasRequiredPermissions())
{
onComplete?.Invoke(true);
return;
}

#if UNITY_ANDROID && !UNITY_EDITOR
// Request missing permissions before the app opens the Sites scene.
RequestNextMissingAndroidPermission(onComplete);
return;
#elif UNITY_IOS && !UNITY_EDITOR
// iOS authorization APIs are asynchronous, so use the Landing controller as
// the coroutine host while the permission prompts are active.
if (coroutineHost == null)
{
Debug.LogError("WayfindingPermissionHelper: An iOS coroutine host is required.");
onComplete?.Invoke(false);
return;
}
coroutineHost.StartCoroutine(RequestIosPermissions(onComplete));
return;
#endif
onComplete?.Invoke(HasRequiredPermissions());
}

#if UNITY_ANDROID && !UNITY_EDITOR
private static void RequestNextMissingAndroidPermission(Action<bool> onComplete)
{
if (HasRequiredPermissions())
{
onComplete?.Invoke(true);
return;
}

var permission = !Permission.HasUserAuthorizedPermission(Permission.Camera)
? Permission.Camera
: Permission.FineLocation;

var callbacks = new PermissionCallbacks();
callbacks.PermissionGranted += _ => RequestNextMissingAndroidPermission(onComplete);
callbacks.PermissionDenied += _ => TryComplete(onComplete);
callbacks.PermissionDeniedAndDontAskAgain += _ => TryComplete(onComplete);
Permission.RequestUserPermission(permission, callbacks);
}
#endif

#if UNITY_IOS && !UNITY_EDITOR
private static IEnumerator RequestIosPermissions(Action<bool> onComplete)
{
// Request camera access before AR Foundation tries to open the camera.
if (!Application.HasUserAuthorization(UserAuthorization.WebCam))
{
yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);
}

if (!Application.HasUserAuthorization(UserAuthorization.WebCam))
{
onComplete?.Invoke(false);
yield break;
}

// Starting the location service prompts for location access when it has not
// already been granted. A real app can replace this with its shared location service.
Input.location.Start();
yield return null;

const float timeoutSeconds = 20f;
var elapsed = 0f;
while (Input.location.status == LocationServiceStatus.Initializing && elapsed < timeoutSeconds)
{
elapsed += Time.unscaledDeltaTime;
yield return null;
}

var locationReady = Input.location.isEnabledByUser
&& Input.location.status != LocationServiceStatus.Failed;
onComplete?.Invoke(locationReady);
}
#endif

// Report the final permission state back to the Landing scene.
private static void TryComplete(Action<bool> onComplete)
{
onComplete?.Invoke(HasRequiredPermissions());
}
}

Load the Sites map

This step adds the Landing scene controller and a placeholder WayfindingSitesMap scene. The controller updates the Landing UI, starts sign-in when needed, requests permissions, and loads WayfindingSitesMap when both are ready. The next page replaces the placeholder scene with the real Sites map and Site selection flow.

This step has three parts:

  1. Create the landing controller for the Landing scene.
  2. Wire the Landing scene to that controller.
  3. Create the placeholder Sites scene so this page can validate the scene load.

Use the following steps to create or update the Sites scene transition:

Create the landing controller

Create Assets/Scripts/Landing/WayfindingLandingController.cs to manage the Landing UI, sign-in flow, permissions, and scene transition:

Expand to view WayfindingLandingController.cs
using NianticSpatial.NSDK.AR.Settings;
using NianticSpatial.NSDK.AR.Loader;
using NianticSpatial.NSDK.AR.Utilities.Auth;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

/// <summary>
/// Landing scene: OAuth sign-in, permissions, then navigate to Sites map.
/// </summary>
public class WayfindingLandingController : MonoBehaviour
{
private const int MinUnexpiredTimeLeft = 60;

// This asset points the sample login helpers at the sign-in environment.
// Replace it if your app uses a different auth environment or endpoint set.
[SerializeField]
private AuthEndpoints signInEndpoints;

// Replace this label and button with your own UI objects if your app uses a different Landing layout.
[SerializeField]
private Text statusLabel;

[SerializeField]
private Button primaryButton;

// Editor-only deep-link override for testing browser callbacks without a device sign-in round trip.
[SerializeField]
[Tooltip("Optional: mock deep link URL when testing in the Unity Editor")]
private string mockDeepLinkUrl;

private bool _permissionsReady;
private bool _permissionRequestAttempted;
private bool _navigating;

private void Awake()
{
Debug.Log("WayfindingLandingController: Landing scene opened.");

// Load the sign-in settings asset this tutorial created under Resources/Auth/.
// If your app injects auth settings another way, replace this Resources.Load call.
if (signInEndpoints == null)
{
signInEndpoints = Resources.Load<AuthEndpoints>("Auth/WayfindingSignInEndpoints");
}

// Register those settings with the shared sample auth helpers.
signInEndpoints?.SetAsSettings();

#if UNITY_EDITOR
if (!string.IsNullOrEmpty(mockDeepLinkUrl))
{
// Editor-only shortcut that simulates the browser callback URL.
LoginManager.MockDeepLink(mockDeepLinkUrl);
}
#endif

// Restore the sample auth session, then publish it to NSDK before any SitesClientManager
// request can run. This ordering is what makes the NSDK context available in SitesMap.
// In your own app, replace both calls with your auth/session bootstrap, but preserve the
// requirement that NSDK session access is configured before loading the Sites scene.
NSSampleSessionManager.Start();
NSSampleSessionManager.SetupSessionAccess();

#if !UNITY_EDITOR
if (!string.IsNullOrEmpty(Application.absoluteURL))
{
// Handle the real mobile callback URL after the browser sign-in flow returns to the app.
LoginManager.MockDeepLink(Application.absoluteURL);
}
#endif
}

private void OnEnable()
{
// Subscribe to the sample auth events so the Landing UI refreshes when sign-in finishes or logout occurs.
// Replace these with your app's auth events if you do not use LoginManager.cs.
LoginManager.LoginComplete += OnLoginComplete;
LoginManager.LogoutComplete += OnAuthStateChanged;

if (primaryButton != null)
{
primaryButton.onClick.AddListener(OnPrimaryButtonClicked);
}
}

private void OnDisable()
{
LoginManager.LoginComplete -= OnLoginComplete;
LoginManager.LogoutComplete -= OnAuthStateChanged;

if (primaryButton != null)
{
primaryButton.onClick.RemoveListener(OnPrimaryButtonClicked);
}
}

private void Start()
{
if (statusLabel == null || primaryButton == null)
{
Debug.LogError(
"WayfindingLandingController: Assign Status Label and Primary Button from the Landing scene.");
return;
}

Debug.Log("WayfindingLandingController: Landing UI is ready.");
RefreshUi();
BeginPermissionFlow();
}

private void BeginPermissionFlow()
{
// Request permissions before the scene loads WayfindingSitesMap.
if (WayfindingPermissionHelper.HasRequiredPermissions())
{
Debug.Log("WayfindingLandingController: Permissions already granted.");
_permissionsReady = true;
TryAdvance();
return;
}

Debug.Log("WayfindingLandingController: Waiting for camera and location permissions.");
RefreshPermissionUi();
}

private void OnPrimaryButtonClicked()
{
// Use the same button for permissions first, then sign-in after permissions are granted.
if (!_permissionsReady)
{
Debug.Log("WayfindingLandingController: Permissions button pressed.");
OnRequestPermissionsClicked();
return;
}

if (IsAuthenticated())
{
Debug.Log("WayfindingLandingController: Sign-in button ignored because auth is already ready.");
return;
}

if (LoginManager.IsLoginInProgress)
{
Debug.Log("WayfindingLandingController: Sign-in cancelled.");
// Sample login helpers support cancelling the in-progress browser flow.
LoginManager.CancelLoginRequested();
RefreshUi();
return;
}

Debug.Log("WayfindingLandingController: Sign-in requested.");
// Replace this with your own sign-in entry point if your app does not use LoginManager.cs.
LoginManager.LoginRequested();
RefreshUi();
}

private void OnRequestPermissionsClicked()
{
_permissionRequestAttempted = true;
statusLabel.text = "Requesting permissions…";
Debug.Log("WayfindingLandingController: Requesting camera and location permissions.");
// Pass this controller as the coroutine host for the iOS permission flow.
WayfindingPermissionHelper.RequestRequiredPermissions(this, granted =>
{
_permissionsReady = granted;
if (granted)
{
Debug.Log("WayfindingLandingController: Permissions granted.");
RefreshUi();
TryAdvance();
}
else
{
Debug.LogWarning("WayfindingLandingController: Permissions were not granted.");
RefreshPermissionUi();
}
});
}

private void RefreshPermissionUi()
{
if (statusLabel == null || primaryButton == null)
{
return;
}

// This tutorial intentionally uses legacy Unity UI Text. If your button uses
// TextMeshPro, change this lookup and the serialized label types to TMP_Text.
var buttonText = primaryButton.GetComponentInChildren<Text>();
primaryButton.gameObject.SetActive(true);
primaryButton.interactable = true;

if (_permissionRequestAttempted)
{
statusLabel.text =
"Permissions are required. Enable camera and location in system settings.";
if (buttonText != null)
{
buttonText.text = "Try again";
}
}
else
{
statusLabel.text = "Camera and location access are required for AR wayfinding.";
if (buttonText != null)
{
buttonText.text = "Grant permissions";
}
}
}

private void OnLoginComplete()
{
Debug.Log("WayfindingLandingController: Sign-in completed.");
TryAdvance();
}

private void OnAuthStateChanged()
{
Debug.Log("WayfindingLandingController: Auth state changed.");
RefreshUi();
}

private void TryAdvance()
{
// Open the next scene only after permissions and sign-in are both ready.
if (_navigating || !_permissionsReady || !IsAuthenticated())
{
Debug.Log(
$"WayfindingLandingController: Waiting to load SitesMap (permissionsReady={_permissionsReady}, authenticated={IsAuthenticated()}, navigating={_navigating}).");
RefreshUi();
return;
}

_navigating = true;
Debug.Log("WayfindingLandingController: Loading WayfindingSitesMap.");
// Replace this with your app's next scene if you use a different scene flow after Landing.
SceneManager.LoadScene(WayfindingSceneNames.SitesMap);
}

private bool IsAuthenticated()
{
// Developer-token path used by this tutorial for simple local testing.
// Remove or replace this branch if your app should always use interactive sign-in.
if (!string.IsNullOrEmpty(NsdkSettings.Instance.AccessTokenOverride))
{
return true;
}

// Refresh-token path used by the copied sample auth helpers.
// Replace this with your own session-validity check if your app stores auth differently.
if (NsdkSettings.Instance.UseDeveloperAuthentication
&& !AuthPublicUtils.IsEmptyOrExpiring(NsdkSettings.Instance.RefreshToken, MinUnexpiredTimeLeft))
{
return true;
}

// Final fallback: ask the copied LoginManager whether sign-in already completed.
return LoginManager.IsLoggedIn;
}

private void RefreshUi()
{
if (statusLabel == null || primaryButton == null)
{
return;
}

if (!_permissionsReady)
{
RefreshPermissionUi();
return;
}

// Keep this component type consistent with the text component under PrimaryButton.
// A TextMeshPro-based app should use GetComponentInChildren<TMP_Text>() instead.
var buttonText = primaryButton.GetComponentInChildren<Text>();

if (IsAuthenticated())
{
statusLabel.text = "Signed in. Opening site map…";
primaryButton.gameObject.SetActive(false);
return;
}

primaryButton.gameObject.SetActive(true);

if (LoginManager.IsLoginInProgress)
{
statusLabel.text = "Complete sign-in in your browser, then return to this app.";
if (buttonText != null)
{
buttonText.text = "Cancel sign-in";
}
}
else
{
statusLabel.text = "Sign in with your Niantic Spatial account to load your Sites.";
if (buttonText != null)
{
buttonText.text = "Sign in to get started";
}
}

primaryButton.interactable = true;
}
}

Wire the Landing scene

Open Assets/Scenes/WayfindingLanding.unity and update it as follows:

  1. In the Hierarchy, create an empty GameObject by selecting Create Empty, then name it WayfindingLandingController.
  2. With WayfindingLandingController selected, use Add Component in the Inspector to attach WayfindingLandingController.cs.
  3. Drag the StatusLabel object from LandingCanvas/UIContent into Status Label so the controller can display permission and sign-in state.
  4. Drag the PrimaryButton object from LandingCanvas/UIContent into Primary Button so the controller can handle the current permission or sign-in action.
  5. Leave Sign In Endpoints empty. You can change this field to use different sign-in settings, but that is outside the scope of this guide.
  6. Leave Mock Deep Link Url empty. You can set this field to test a mock browser callback in the Unity Editor, but that is outside the scope of this guide.

Validate this step:

  • Select WayfindingLandingController in the Hierarchy and press Play.
  • The Landing screen now shows the permission message and the Grant permissions button instead of the blank status area from the previous step.

Create the placeholder Sites scene

Open Assets/Scenes/WayfindingSitesMap.unity and update it as follows:

  1. In the Hierarchy, create UI > Canvas and rename it SitesCanvas.
  2. In SitesCanvas, create UI > Text - TextMeshPro and rename it SelectedSiteTitle.
  3. Set the Text Input value to Temporary Sites screen, and the Vertex Color to 010101.
  4. Save the scene.

Validate this step:

  • To validate the first-run permission path again, uninstall the existing device build or clear its camera and location permissions, then build and run the app on the device.
  • Confirm that the Landing screen shows Grant permissions.
  • Tap Grant permissions and confirm that the device requests both camera and location access. Allow both permissions.
  • After you allow camera and location access, the Landing screen shows Sign in to get started.
  • Complete sign-in on the device.
  • Confirm that WayfindingSitesMap opens after sign-in completes and shows the temporary Temporary Sites screen label.
  • In the device log—Logcat on Android or Xcode's console on iOS—confirm that WayfindingLandingController: Loading WayfindingSitesMap appears.

In the vps2-wayfinding-sample sample app, the full reference implementation for this landing flow lives across Assets/Scripts/WayfindingScenes.cs, Assets/Scripts/Auth/AuthRequests.cs, Assets/Scripts/Auth/LoginManager.cs, Assets/Scripts/Auth/NSSampleSessionManager.cs, Assets/Scripts/PermissionHelper.cs, and Assets/Scripts/Landing/LandingController.cs.

Next step

Continue to Build the map experience to build the WayfindingSitesMap scene, load real Sites, and pass the selected anchor payload into the AR scene.