Example-Code / Utility / RaycasterHelper.cs
RaycasterHelper.cs
Raw
using CCG.Bigfoot.InputSystem;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

namespace CCG.Bigfoot.Utility
{
    /// <summary>
    /// Provides utility methods for raycasting GUI and Collider
    /// </summary>
    /// <remarks> Must call Initialize before use.
    /// Authors: CS
    /// Created: 2024-01-05
    /// </remarks>
    public static class RaycasterHelper
    {
        //static readonly string _logger = Log.GetClassLogger();

        static List<GameObject> _hitsWithTag = null;
        static List<GameObject> _hitsWithLayer = null;
        static Camera _camera;
        static bool _isInitialized;
        /// <summary>
        /// Initialize must be called before the system is used on scene-by-scene basis.
        /// </summary>
        /// <param name="raycastCam"></param>
        public static void Initialize(Camera raycastCam)
        {
            _isInitialized = false;

            _camera = raycastCam; //--Camera.main may return the wrong camera, which could result in bad raycast results.
            _hitsWithTag = new List<GameObject>(8);
            _hitsWithLayer = new List<GameObject>(8);

            if (_camera == null) Debug.LogWarning("[Initialize] - passed a null camera. Will default to Camera.main, which may result in bad results!");
            if (InputManager.Instance == null) Debug.LogError("[Initialize] - Failed to initialize. Raycasting won't work.");
            else _isInitialized = true;
        }
        public static void ClearAllMouseOverTag()
        {
            _hitsWithTag.Clear();
        }
        public static void ClearAllMouseOverLayerIndex()
        {
            _hitsWithLayer.Clear();
        }

        /// <summary>
        /// Returns true if pointer is over at least one GameObject with tag. Uses Physics.Raycast.
        /// Use if only one collider is in your target layer.
        /// </summary>
        /// <param name="tag"></param>
        /// <returns></returns>
        public static bool IsMouseOverTag(string tag, int layerIndex)
        {
            if(!_isInitialized) return false;

            int layermask = 1 << layerIndex;
            Vector3 pointerPos = InputManager.Instance.InputModule.point.action.ReadValue<Vector2>();

            if (_camera == null) _camera = Camera.main;
            Ray origin = _camera.ScreenPointToRay(pointerPos);

            try
            {
                //return Physics.Raycast(origin, out RaycastHit hit, Mathf.Infinity, layermask) && hit.transform.CompareTag(tag);
                bool result = Physics.Raycast(origin, out RaycastHit hit, Mathf.Infinity, layermask) && hit.transform.CompareTag(tag);
                if (result) Debug.Log("[IsMouseOverTag] - Raycast hit object " + hit.transform.name + " with tag " + hit.transform.gameObject.tag);
                return result;
            }
            catch (System.Exception e)
            {
                Debug.Log("[IsMouseOverTag] - caught " + e.Message);
                return false;
            }
        }

        /// <summary>
        /// Returns true if pointer is over at least one GameObject with tag. Uses Physics.RaycastNonAlloc.
        /// Use if more than one collider is in your targeted layer.
        /// </summary>
        /// <param name="tag"></param>
        /// <returns></returns>
        public static bool IsMouseOverTagNonAlloc(string tag, int layerIndex, int numMatchingTagsMouseBeOver = 1)
        {
            if (!_isInitialized) return false;

            int layermask = 1 << layerIndex;
            Vector3 pointerPos = InputManager.Instance.InputModule.point.action.ReadValue<Vector2>();

            if (_camera == null) _camera = Camera.main;

            Ray origin = _camera.ScreenPointToRay(pointerPos);
            RaycastHit[] hits = new RaycastHit[5];
            int numHits = Physics.RaycastNonAlloc(origin, hits, Mathf.Infinity, layermask);
            int numMatchingTagHits = 0;
            if (numHits < 1) return false;
            for (int i = 0; i < numHits; i++)
            {
                //Log.Debug(_logger, "[IsMouseOverTagNonAlloc] - RaycastNonAlloc hit object " + hits[i].transform.name + " with tag " + hits[i].transform.gameObject.tag);
                if (hits[i].transform.CompareTag(tag)) numMatchingTagHits++;
            }

            return numMatchingTagHits >= numMatchingTagsMouseBeOver;
        }

        /// <summary>
        /// Uses Physics.Raycast to return true if the pointer is over a collider in targetMask. 
        /// </summary>
        /// <param name="tag"></param>
        /// <returns></returns>
        public static bool IsMouseOverLayerMask(LayerMask targetMask, Vector2 mousePos)
        {
            if (!_isInitialized) return false;

            Ray ray = _camera.ScreenPointToRay(mousePos);

            return Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, targetMask);
        }

        /// <summary>
        /// Uses Physics.Raycast to return true if the pointer is over a collider in targetMask. 
        /// Attempts to retrieve the Type passed in using reflection first looking at self then upwards.
        /// Outs back the component if it was found.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="targetMask"> The LayerMask you're sending a ray through.</param>
        /// <param name="mousePos">The screen space coordinates representing the mouse position.</param>
        /// <param name="targetComponent">The component you are expecting to grab.</param>
        /// <returns></returns>
        public static bool IsMouseOverLayerMask<T>(LayerMask targetMask, Vector2 mousePos, out T targetComponent) where T : Component
        {
            targetComponent = null;

            if (!_isInitialized) return false;

            Ray ray = _camera.ScreenPointToRay(mousePos);

            if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, targetMask))
            {
                T foundComponent = hit.collider.GetComponentInParent<T>();
                if (foundComponent != null)
                {
                    targetComponent = foundComponent;
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Returns true if pointer is over at least one GameObject with tag.
        /// </summary>
        /// <param name="tag"></param>
        /// <returns></returns>
        public static bool IsMouseOverTag(string tag)
        {
            if (!_isInitialized) return false;

            List<RaycastResult> results = RaycastAll();

            if(results == null) return false;

            foreach (RaycastResult hit in results)
            {
                if (hit.isValid && hit.gameObject.CompareTag(tag)) return true;
            }
            return false;
        }

        /// <summary>
        /// Returns true if pointer is over at least one GameObject with tag and out's it back.
        /// </summary>
        /// <param name="tag"></param>
        /// <param name="hitGo"></param>
        /// <returns></returns>
        public static bool GetFirstIsMouseOverTag(string tag, out GameObject hitGo)
        {
            if (!_isInitialized) { hitGo = null; return false; }

            List<RaycastResult> results = RaycastAll();

            if (results == null) { hitGo = null; return false; }

            foreach (RaycastResult hit in results)
            {
                if (hit.isValid && hit.gameObject.CompareTag(tag))
                {
                    hitGo = hit.gameObject;
                    return true;
                }
            }
            hitGo = null;
            return false;
        }

        /// <summary>
        /// Returns true if pointer is over at least 1 Gameobject with the tag and out's a supplied list of all GameObjects with tag hit.
        /// Callers must call the ClearAllMouseOverTag() after results are operated on.
        /// </summary>
        /// <param name="tag"></param>
        /// <param name="hits"></param>
        /// <returns></returns>
        public static bool GetAllMouseOverTag(string tag, out List<GameObject> hits)
        {
            if (!_isInitialized) { hits = null; return false; }

            List<RaycastResult> results = RaycastAll();
            hits = _hitsWithTag;//--use reserved memory address for this method so don't have to create new

            if(results == null)return false;

            foreach (RaycastResult hit in results)
            {
                if (hit.isValid && hit.gameObject.CompareTag(tag))
                {
                    hits.Add(hit.gameObject);
                }
            }
            return hits.Count > 0;
        }

        /// <summary>
        /// Returns true if pointer is over at least 1 Gameobject with the inspector layer index and out's a supplied list of all GameObjects with layer hit.
        /// Callers must call the ClearAllMouseOverLayer() after results are operated on.
        /// </summary>
        /// <param name="layerIndex"></param>
        /// <param name="hits"></param>
        /// <returns></returns>
        public static bool GetAllMouseOverLayerIndex(int layerIndex, out List<GameObject> hits)
        {
            if (!_isInitialized) { hits = null; return false; }

            List<RaycastResult> results = RaycastAll();
            hits = _hitsWithLayer;//--use reserved memory address for this method so don't have to create new

            if (results == null) return false;

            foreach (RaycastResult hit in results)
            {
                if (hit.isValid && layerIndex == hit.gameObject.layer)
                {
                    hits.Add(hit.gameObject);
                }
            }
            return hits.Count > 0;
        }

        /// <summary>
        /// Returns true if pointer is over at least 1 Gameobject with the LayerMask and out's a supplied list of all GameObjects with layer hit.
        /// Callers must call the ClearAllMouseOverLayerMask() after results are operated on.
        /// </summary>
        /// <param name="layerMask"></param>
        /// <param name="hits"></param>
        /// <returns></returns>
        public static bool GetAllMouseOverLayerMask(LayerMask layerMask, out List<GameObject> hits)
        {
            if (!_isInitialized) { hits = null; return false; }

            List<RaycastResult> results = RaycastAll(); 
            hits = _hitsWithLayer;//--use reserved memory address for this method so don't have to create new

            if (results == null) return false;

            foreach (RaycastResult hit in results)
            {
                //--Convert layer to bitfield for comparison
                int hitLayerMask = (1 << hit.gameObject.layer);

                if (hit.isValid && (layerMask & hitLayerMask) > 0)
                {
                    hits.Add(hit.gameObject);
                }
            }
            return hits.Count > 0;
        }





        /// <summary>
        /// Performs a RaycastAll with the current EventSystem to tell you the UI that was underneath the pointer.
        /// </summary>
        /// <returns></returns>
        public static List<RaycastResult> RaycastAll()
        {
            if (!_isInitialized || InputManager.Instance == null)
            {
                Debug.LogError("[RaycastAll] - ERROR");
                return null;
            }

            PointerEventData pointerData = new(EventSystem.current)
            {
                pointerId = -1,
            };

            pointerData.position = InputManager.Instance.InputModule.point.action.ReadValue<Vector2>();

            List<RaycastResult> results = new();
            EventSystem.current.RaycastAll(pointerData, results);

            return results;
        }
    }

}