# 5. Worked Example: Place on a Plane Part of [[AR Foundation]]. Prev: [[4. Touch Input and Session State]]. Next: [[6. AR Foundation Pitfalls]]. Scene: official **AR Session** + **XR Origin (Mobile AR)**, **AR Plane Manager**, **AR Raycast Manager**. One prefab (a cube or a gray-box prop). Script **not compiled** and **not device-tested**. `TrackableType` in the sample uses `PlaneWithinPolygon` — confirm that flag in **6.5.1 IntelliSense** (and the single-raycasts page). If your resolved enum uses a different official plane flag, use that and do not keep a name IntelliSense rejects. ```csharp using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.XR.ARFoundation; using UnityEngine.XR.ARSubsystems; /// <summary> /// Places a prefab at the closest plane hit under a pointer. /// NOT compiled in Unity; not play-tested on a device. /// </summary> public sealed class PlaceOnPlane : MonoBehaviour { [SerializeField] ARRaycastManager raycastManager; [SerializeField] GameObject prefab; [SerializeField] InputActionReference point; // a Vector2 screen-position action, or read Touchscreen / Pointer [SerializeField] InputActionReference press; // a button / press action readonly List<ARRaycastHit> _hits = new List<ARRaycastHit>(); GameObject _instance; void OnEnable() { press.action.performed += OnPress; press.action.Enable(); point.action.Enable(); } void OnDisable() { press.action.performed -= OnPress; } void OnPress(InputAction.CallbackContext ctx) { Vector2 screen = point.action.ReadValue<Vector2>(); if (!raycastManager.Raycast(screen, _hits, TrackableType.PlaneWithinPolygon)) return; ARRaycastHit hit = _hits[0]; // official: closest first Pose pose = hit.pose; if (_instance == null) _instance = Instantiate(prefab, pose.position, pose.rotation); else _instance.transform.SetPositionAndRotation(pose.position, pose.rotation); if (hit.trackable is ARPlane plane) Debug.Log(quot;Placed on plane alignment={plane.alignment}"); } } ``` Wire `point` and `press` from your existing Input System asset (a Touch / Pointer position and a press). If `PlayerInput` already owns the asset, follow [[2. Control Schemes Devices and Consumption Patterns]] and do not construct a second asset. This example **moves one instance** rather than spawning a pile. Instantiating every tap is a valid variant; then you own those instances and must destroy them on session end. ## 5.1 Playtest checklist (device) 1. Grant camera / AR permissions. `ARSession.state` reaches a tracking state on the official table. 2. A plane visualization appears (if the Plane Manager draws one). 3. A tap on the plane places (or moves) the prefab at `hit.pose`; a tap on empty air does nothing. 4. A tap on a UI button does not place (if you have UI). 5. Only one XR Origin, one Session, one Main Camera. 6. You tested on **hardware**, not only the Editor. ## 5.2 Exercise Build an Android or iOS profile ([[1. Build Profiles]]), sideload, and record a short clip of a placement. If `PlaneWithinPolygon` is rejected, swap to the official 6.5.1 flag and note it in the writeup. ## Sources - https://docs.unity3d.com/Packages/[email protected]/api/UnityEngine.XR.ARFoundation.ARRaycastManager.html - [[5. Worked Example Remappable Player]] / [[Mobile Input and Touch]]