# 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]]