示例源代码:
https://github.com/sueleeyu/ar-plane
从《ARFoundation从零开始3-arfoundation项目》复制项目,继续:
一、添加组件
1.添加AR RayCaset Manager 和AR Plane Manager:
选择左侧Hierarchy-AR Session Origin,Inspector下点击Add Component,依次输入并添加AR RayCaset Manager 和AR Plane Manager

2.创建平面prefabs:Hierarchy-‘+’-XR-AR Default Plane,Assets下新建Prefabs目录,将创建的对象拖动到Prefabs目录,删除Hieraychy下的对象。


3.将创建的plane预制件拖动到Plane Manager组件:

4.创建Button组件,命名BtnPlane,用于显示/隐藏平面:

二、编写代码
1.编写cs代码PlaceManager.cs,用于放置预制件。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
namespace FrameworkDesign.Example
{
??
??? public class PlaceManager : MonoBehaviour
??? {
??????? [Header("AR Foundation")]?
??????? /// <summary>
??????? /// The active ARRaycastManager used in the example.
??????? /// </summary>
??????? public ARRaycastManager m_RaycastManager;
??????? [Header("UI")]
??????? [SerializeField]
??????? [Tooltip("Instantiates this prefab on a plane at the touch location.")]
??????? GameObject m_PlacedPrefab;//要放置的预制件
??????? /// <summary>
??????? /// The prefab to instantiate on touch.
??????? /// </summary>
??????? public GameObject placedPrefab
??????? {
??????????? get { return m_PlacedPrefab; }
??????????? set { m_PlacedPrefab = value; }
??????? }
??????? [HideInInspector]
??????? static List<ARRaycastHit> s_Hits = new List<ARRaycastHit>();//存放检测到的碰撞点
??????? /// <summary>
??????? /// The object instantiated as a result of a successful raycast intersection with a plane.
??????? /// </summary>
??????? public GameObject spawnedObject { get; private set; }
??????? void Awake()
??????? {
??????????? // m_RaycastManager = GetComponent<ARRaycastManager>();//也可以通过GetComponent获取到ARRaycastManager
??????? }
??????? bool TryGetTouchPosition(out Vector2 touchPosition)
??????? {
??????????? if (Input.touchCount > 0)
??????????? {
??????????????? touchPosition = Input.GetTouch(0).position;
??????? ????????return true;
??????????? }
??????????? touchPosition = default;
??????????? return false;
??????? }
??????? void Update()
??????? {
??????????? if (!TryGetTouchPosition(out Vector2 touchPosition))
??????????????? return;
??????????? var touch = Input.GetTouch(0);
??????????? const TrackableType trackableTypes =
??????????? TrackableType.FeaturePoint |
??????????? TrackableType.PlaneWithinPolygon;
??????????? if (Input.touchCount == 1 && touch.phase == TouchPhase.Moved)//移动已放置的对象
?????? ?????{
??????????????? if (m_RaycastManager.Raycast(touchPosition, s_Hits, trackableTypes))
??????????????? {
??????????????????? // Raycast hits are sorted by distance, so the first one
??????????????????? // will be the closest hit.
??????????????????? var hitPose = s_Hits[0].pose;
??????????????????? if (spawnedObject != null)
??????????????????? {
??????????????????????? spawnedObject.transform.position = hitPose.position;
??????????????????? }???????????????????
??????????????? }
??????????? }
????? ??????if (Input.touchCount == 1 && touch.phase == TouchPhase.Began)//检测touch begin,在touch begin中做射线碰撞检测
??????????? {
??????????????? //---判断是否touch到UI组件----
??????????????? //#if IPHONE || ANDROID
??????????????? if (EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId))
??????????????? //#else
??????????????? // if (EventSystem.current.IsPointerOverGameObject())
??????????????? //#endif
??????????????? //Debug.Log("当前触摸在UI上");
??????????????? {
??????????????????? Logger.Log($"当前触摸在UI上"+ touch.phase);
??????????????????? return;
??????????????? }
??????????????? else
??????????????? {
??????????????????? //Debug.Log("当前没有触摸在UI上");
??????????????????? Logger.Log($"当前没有触摸在UI上"+ touch.phase);
??????????????? }
??????????????? if (m_RaycastManager.Raycast(touchPosition, s_Hits, trackableTypes))
??????????????? {
??????????????????? // Raycast hits are sorted by distance, so the first one
??????????????????? // will be the closest hit.
??????????????????? var hitPose = s_Hits[0].pose;
?????? ?????????????if (spawnedObject == null)
??????????????????? {
??????????????????????? spawnedObject = Instantiate(m_PlacedPrefab, hitPose.position, hitPose.rotation);//实例化预制件对象
??????????????????? }
??????????????????? else
??????????????????? {
????????? ??????????????spawnedObject.transform.position = hitPose.position;//更新对象状态
??????????????????? }
??????????????? }
??????????? }??????????????
??????? }??????
??? }
}
2.Hierarchy下Game下Create Empty,命名GameScene,将PlaneManager.cs挂载到其下:

选择ARScene,将AR Session Origin 和做好的预制件拖放到PlaceManager.cs的参数列:

3.编写ARManager.cs,用于显示/隐藏平面信息:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
public class ARManager : MonoBehaviour
{
??? [Header("AR Foundation")]
??? /// <summary>
??? /// The active ARRaycastManager used in the example.
??? /// </summary>
??? public ARPlaneManager m_ARPlaneManager;
??? [HideInInspector]
??? /// <summary>
??? /// 当前识别出的平面
??? /// </summary>
??? List<ARPlane> detectPlanes = new List<ARPlane>();
??? /// <summary>
??? /// 当前是否要显示平面
??? /// </summary>
??? bool isShowPlane = true;
??? #region MonoBehaviour CallBacks
??? private void Awake()
??? {?????
??????? m_ARPlaneManager = FindObjectOfType<ARPlaneManager>();
??? }
??? void Start()
??? {
??????? CheckDevice();
??????? m_ARPlaneManager.planesChanged += OnPlaneChanged;
??? }
??? private void Update()
??? {
??????? SaveElePolicy();
??? }
??? void OnDisable()
??? {
??????? m_ARPlaneManager.planesChanged -= OnPlaneChanged;
??? }
??? #endregion
??? // 启用与禁用平面检测
??? // 程序默认启用,启用时一直不停地检测平面。关闭时则不会再检测新平面了。
??? public void DetectionPlane(bool value)
??? {
??????? m_ARPlaneManager.enabled = value;
??????? if (m_ARPlaneManager.enabled)
??????? {
??????????? print("已启用平面检测");
??????? }
??????? else
??????? {
??????????? print("已禁用平面检测");
??????? }
??? }
??? // 显示与隐藏检测到的平面?
??? public void SwitchPlane()
??? {
??????? isShowPlane = !isShowPlane;
??????? for (int i = detectPlanes.Count - 1; i >= 0; i--)
??????? {
??????????? if (detectPlanes[i] == null || detectPlanes[i].gameObject == null)
??????????????? detectPlanes.Remove(detectPlanes[i]);
??????????? else
??????????????? detectPlanes[i].gameObject.SetActive(isShowPlane);
??????? }
??? }
??? /// <summary>
??? /// 得到当前AR会话是否正在运行,并被跟踪(即,该设备能够确定其在世界上的位置和方向)。
??? /// </summary>
??? public bool Skode_IsTracking()
??? {
??????? bool isTracking = false;
??????? if (ARSession.state == ARSessionState.SessionTracking)
??????? {
??????????? isTracking = true;
??????? }
??????? return isTracking;
??? }
??? //在ARFoundation新发现平面时,将平面添加进列表里,便于我们控制这些平面
??? void OnPlaneChanged(ARPlanesChangedEventArgs arg)
??? {
?????? ?for (int i = 0; i < arg.added.Count; i++)
??????? {
??????????? detectPlanes.Add(arg.added[i]);
??????????? arg.added[i].gameObject.SetActive(isShowPlane);
??????? }
??? }
??? //检查设备运行环境
??? void CheckDevice()
??? {
??????? if (ARSession.state == ARSessionState.NeedsInstall)
??????? {
??????????? ShowAndroidToastMessage("AR is supported, but requires an additional install. .");
??????????? Invoke("Quit", 1);
??????? }
??????? else if (ARSession.state == ARSessionState.Ready)
??????? {
??????????? Debug.Log("AR is supported and ready.");
??????? }
??????? else if (ARSession.state == ARSessionState.Unsupported)
??????? {
??????????? ShowAndroidToastMessage("AR is not supported on the current device.");
??????????? Invoke("Quit", 1);
??????? }
??? }
??? void ShowAndroidToastMessage(string message)
??? {
??????? AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
??????? AndroidJavaObject unityActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
??????? if (unityActivity != null)
??????? {
??????????? AndroidJavaClass toastClass = new AndroidJavaClass("android.widget.Toast");
??????????? unityActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
??????????? {
??????????????? AndroidJavaObject toastObject = toastClass.CallStatic<AndroidJavaObject>("makeText", unityActivity, message, 0);
??????????????? toastObject.Call("show");
??????????? }));
??????? }
??? }
??? void Quit()
??? {
??????? Application.Quit();
??? }
??? /// <summary>
??? /// 一种省电设置,当设备没找到识别目标,允许屏幕在最后激活一段时间后变暗
??? /// </summary>
??? void SaveElePolicy()
??? {
??????? if (ARSession.state != ARSessionState.SessionTracking)
??????? {
??????????? const int lostTrackingSleepTimeout = 15;
??????????? Screen.sleepTimeout = lostTrackingSleepTimeout;
??????? }
??????? else
??????? {
??????????? Screen.sleepTimeout = SleepTimeout.NeverSleep;
??????? }
??? }
}
4.添加ARManager.cs到Hierarchy-Game下,挂载PlaneManager组件:

5.添加Button的click事件。选择BtnPlane组件,添加Onclick事件,拖动Game,选择Function函数:


二、unity知识点
1.射线检测ARRaycastManager:
ARRaycastManager.Raycast(Vector2, List<ARRaycastHit>, TrackableType)?。
API:Class ARRaycastManager | AR Foundation | 4.2.3

注意:并非所有?TrackableType ?都受 ARCore 和 ARKit 提供程序支持。ARCore 提供程序目前仅支持?PlaneEstimated 、PlaneWithinBounds 、PlaneWithinPolygon 、FeaturePoint 、Image ?和?Depth 。
三、android打包运行
如未配置,参看《ARFoundation从零开始3-arfoundation项目》。
1.安装运行


四、常见问题
五、参考资料
1. Unity api:
Class ARRaycastManager | AR Foundation | 4.2.3
2.ARFoundation示例:
GitHub - Unity-Technologies/arfoundation-samples: Example content for Unity projects based on AR Foundation
3.ARCore文档:
在 Unity (AR Foundation) 应用中执行光线投射 ?|? ARCore ?|? Google Developers
4.本项目示例源代码:
https://github.com/sueleeyu/ar-plane
|