ARFoundation入门教程10-平面检测和放置

示例源代码:

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 提供程序目前仅支持 PlaneEstimatedPlaneWithinBoundsPlaneWithinPolygonFeaturePointImage 和 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

猜你喜欢

转载自blog.csdn.net/weixin_40239288/article/details/125874906
今日推荐