Welcome to the SPI-Glass documentation. Here you’ll find an overview of our core systems: AR tracking, dialogue, and wood-collection via semantic segmentation.
- Full Playthrough: Watch here
- Game Trailer: Watch here
The StumpPlace component encapsulates all AR plane detection and object-placement logic, built on top of Unity’s AR Foundation (or Niantic Lightship ARDK). It:
- Listens for user input (touch or click).
- Performs an AR raycast against detected horizontal surfaces.
- Instantiates a stump prefab at the hit location.
- Creates and manages AR anchors so that stumps stay locked to the real-world position as tracking data updates.
- Distance Filtering: Configurable
minDistanceandmaxDistancesettings ensure stumps are only placed within the desired range from the user’s viewpoint.
Example: StumpPlace.cs (simplified)
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
[RequireComponent(typeof(ARRaycastManager))]
public class StumpPlace : MonoBehaviour
{
[SerializeField] private GameObject stumpPrefab;
[SerializeField] private float minDistance = 0.5f;
[SerializeField] private float maxDistance = 3.0f;
private ARRaycastManager raycastManager;
void Awake()
{
raycastManager = GetComponent<ARRaycastManager>();
}
void Update()
{
if (Input.touchCount == 0) return;
var touch = Input.GetTouch(0);
if (touch.phase != TouchPhase.Began) return;
if (raycastManager.Raycast(touch.position, out var hits, TrackableType.PlaneWithinPolygon))
{
var pose = hits[0].pose;
float distance = Vector3.Distance(Camera.main.transform.position, pose.position);
if (distance < minDistance || distance > maxDistance) return;
var stump = Instantiate(stumpPrefab, pose.position, pose.rotation);
stump.AddComponent<ARAnchor>();
}
}
}We leverage Inkle’s Ink for a flexible, branching narrative:
- Script files live under
Assets/Dialogues/*.ink. - At runtime,
DialogueManager- Loads the chosen
.ink.jsonvia Unity’sTextAsset. - Advances text with
story.Continue(). - Presents choices as UI buttons.
- Handles player selections, feeding them back into
story.ChooseChoiceIndex(...).
- Loads the chosen
- Callbacks trigger in-game events (e.g. spawning objects, playing sounds).
Key excerpts from DialogueManager.cs
using Ink.Runtime;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class DialogueManager : MonoBehaviour
{
[SerializeField] private TextAsset inkJSON;
[SerializeField] private TextMeshProUGUI dialogueText;
[SerializeField] private GameObject choicesContainer;
[SerializeField] private Button choiceButtonPrefab;
private Story story;
public void StartDialogue()
{
story = new Story(inkJSON.text);
DisplayNextLine();
}
void DisplayNextLine()
{
if (!story.canContinue) { EndDialogue(); return; }
dialogueText.text = story.Continue();
CreateChoiceButtons();
}
void CreateChoiceButtons()
{
foreach (Transform t in choicesContainer.transform) Destroy(t.gameObject);
for (int i = 0; i < story.currentChoices.Count; i++)
{
var choice = story.currentChoices[i];
var btn = Instantiate(choiceButtonPrefab, choicesContainer.transform);
btn.GetComponentInChildren<TextMeshProUGUI>().text = choice.text;
int index = i;
btn.onClick.AddListener(() => OnChoiceSelected(index));
}
}
void OnChoiceSelected(int index)
{
story.ChooseChoiceIndex(index);
DisplayNextLine();
}
void EndDialogue() { /* Cleanup and signal end of dialogue */ }
}To detect and collect “wood” in the player’s environment, we integrate a custom semantic-segmentation model via Unity Barracuda:
- Camera feed frames are passed to the SegmentationRunner, which
- Preprocesses the image into a tensor.
- Runs inference on our trained “wood detector” model.
- Returns a binary mask highlighting wood-pixels.
- On touch input, we sample the mask at the tapped pixel.
- If that pixel belongs to wood, we spawn a “wood fragment” collectible at the corresponding world raycast hit.
- This pipeline ensures the user only collects actual wood surfaces, even in complex real-world scenes.
Pseudocode for segmentation in WoodCollector.cs
using Unity.Barracuda;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
public class WoodCollector : MonoBehaviour
{
[SerializeField] private NNModel segmentationModel;
[SerializeField] private GameObject woodPrefab;
private IWorker worker;
private ARCameraManager cameraManager;
void Start()
{
var model = ModelLoader.Load(segmentationModel);
worker = WorkerFactory.CreateWorker(model);
cameraManager = FindObjectOfType<ARCameraManager>();
}
void Update()
{
if (Input.touchCount == 0 || Input.GetTouch(0).phase != TouchPhase.Began) return;
cameraManager.TryAcquireLatestCpuImage(out var image);
var mask = RunSegmentation(image);
image.Dispose();
if (mask.IsWoodPixel(touchX, touchY))
{
Instantiate(woodPrefab, hitPose.position, Quaternion.identity);
}
}
private Tensor RunSegmentation(XRCpuImage image)
{
// Convert image to Tensor, dispatch to worker, retrieve mask tensor
}
void OnDestroy() => worker.Dispose();
}Next Steps:
- Tweak AR surface filters in
StumpPlaceto ignore overly small planes.- Extend Ink scripts to support inventory checks for collected wood.
- Retrain segmentation model with more wood-type samples for robustness.
Happy coding!
— SPI-Glass Team