using System; using System.Collections.Generic; using System.Linq; #if UNITY_EDITOR using UnityEditor.Presets; #endif using UnityEngine; using UnityEngine.EventSystems; namespace WI { [DefaultExecutionOrder(int.MinValue)] public class Raycaster : MonoBehaviour, ISingle { PointerEventData pointerEvent = new(EventSystem.current); List uiRaycastResults = new(); RaycastHit[] hitInfo = new RaycastHit[16]; RaycastHit[] tempInfo; HashSet typeLayers = new(); Dictionary> onExitEvent = new(); Dictionary> onStayEvent = new(); Dictionary> onEnterEvent = new(); Dictionary> onLeftClickEvent = new(); Dictionary> onRightClickEvent = new(); Dictionary> onLeftClickFirst = new(); Dictionary> onRightClickFirst = new(); Dictionary> onEnterFirst = new(); Dictionary> onStayFirst = new(); Dictionary> onExitFirst = new(); bool onLeftClick; bool onRightClick; Dictionary firstHit = new(); Dictionary tempFirstHit = new(); Dictionary fth = new(); int hitCount; Camera cam; public RaycastHit hit => hitInfo[0]; HashSet hitTransform = new(); HashSet tempHit = new(); Dictionary transformToHitinfo = new(); List<(Action, RaycastHit, Component)> eventList = new(); public float uiHoverTime; float uiHoverTimer; #pragma warning disable IDE0044 // 읽기 전용 한정자 추가 #pragma warning disable CS0649 // 'Raycaster.onUIHoverEvent' 필드에는 할당되지 않으므로 항상 null 기본값을 사용합니다. Action onUIHoverEvent; #pragma warning restore CS0649 #pragma warning restore IDE0044 GameObject prevOnUI; void Awake() { cam = Camera.main; } void Update() { UIRaycast(); PhysicsRaycast(); EventInvoking(); } void EventInvoking() { foreach(var e in eventList) { e.Item1.Invoke(e.Item2, e.Item3); } eventList.Clear(); } void UIRaycast() { pointerEvent.position = Input.mousePosition; EventSystem.current.RaycastAll(pointerEvent, uiRaycastResults); if(uiRaycastResults.Count != 0) { if (uiRaycastResults[0].gameObject == null) { return; } if (prevOnUI != uiRaycastResults[0].gameObject) { uiHoverTimer = 0f; prevOnUI = uiRaycastResults[0].gameObject; } if (uiHoverTimer >= uiHoverTime) { onUIHoverEvent?.Invoke(uiRaycastResults[0]); } else { uiHoverTimer += Time.deltaTime; } } else { prevOnUI = null; } } void PhysicsRaycast() { onLeftClick = Input.GetMouseButtonDown(0); onRightClick = Input.GetMouseButtonDown(1); Rayfire(); SingleCasting(); MultiCasting(); } void Rayfire() { var ray = cam.ScreenPointToRay(Input.mousePosition); //Physics.Raycast(ray, out singleHit, Mathf.Infinity); tempInfo = new RaycastHit[16]; hitCount = Physics.RaycastNonAlloc(ray, tempInfo, Mathf.Infinity); hitInfo = SortingHitInfos(tempInfo); } public bool Casting(LayerMask layer, out RaycastHit hit) { hit = new RaycastHit(); if (hitCount == 0) { return false; } foreach (var h in hitInfo) { if (h.transform == null) continue; if ((layer.value >> h.transform.gameObject.layer) == 1) { hit = h; return true; } } return false; } void SingleCasting(in RaycastHit hitInfo) { fth.TryAdd(hitInfo.transform, hitInfo); foreach (var tl in typeLayers) { SingleCasting(hitInfo, tl); } } void SingleCasting(in RaycastHit hitInfo, Type tl) { if (tempFirstHit.ContainsKey(tl)) { return; } if (!hitInfo.transform.TryGetComponent(tl, out var value)) return; if (firstHit.Remove(tl, out var prev)) { if (prev.transform == hitInfo.transform) { //Debug.Log($"OnStayFirst : {prev.transform.name}"); EventInvoke(onStayFirst, tl, hitInfo, value); } else { //Debug.Log($"OnExitFirst : {prev.transform.name}"); EventInvoke(onExitFirst, tl, prev, prev.transform.GetComponent(tl)); //Debug.Log($"OnEnterFirst : {hitInfo.transform.name}"); EventInvoke(onEnterFirst, tl, hitInfo, value); fth.Remove(prev.transform); } } else { //Debug.Log($"OnEnterFirst : {hitInfo.transform.name}"); EventInvoke(onEnterFirst, tl, hitInfo, value); } fth[hitInfo.transform] = hitInfo; tempFirstHit.Add(tl, hitInfo); } void SingleCasting() { //fth.Clear(); tempFirstHit.Clear(); for (int i = 0; i < hitCount; ++i) { SingleCasting(hitInfo[i]); } FirstExitEvent(); FirstClickEvent(); } void FirstClickEvent() { foreach (var p in tempFirstHit) { firstHit.Add(p.Key, p.Value); if (onLeftClick) { EventInvoke(onLeftClickFirst, p.Key, p.Value, p.Value.transform.GetComponent(p.Key)); } if (onRightClick) { EventInvoke(onRightClickFirst, p.Key, p.Value, p.Value.transform.GetComponent(p.Key)); } } } void FirstExitEvent() { foreach (var f in firstHit) { //Debug.Log($"OnExitFirst :{f.Value.transform.name}"); if (f.Value.transform == null) continue; EventInvoke(onExitFirst, f.Key, f.Value, f.Value.transform.GetComponent(f.Key)); fth.Remove(f.Value.transform); //tempFirstHit.Remove(f.Key); } firstHit.Clear(); } RaycastHit[] SortingHitInfos(in RaycastHit[] hitInfo) { if (hitInfo[0].transform == null) return null; var sortHitInfo = hitInfo.Where(hi => hi.transform != null).OrderBy(hi => hi.distance).ToArray(); //var sortHitInfo = hitInfo.OrderBy(hi => hi.distance).ToArray(); return sortHitInfo; } void HitCheck(int hitIndex) { var ht = hitInfo[hitIndex].transform; tempHit.Add(ht); transformToHitinfo.TryAdd(ht, hitInfo[hitIndex]); bool isStay = hitTransform.Remove(ht); foreach (var tl in typeLayers) { if (!ht.TryGetComponent(tl, out var value)) continue; if (onLeftClick) { EventInvoke(onLeftClickEvent, tl, hitInfo[hitIndex], value); //Debug.Log($"OnClick {tl} {value}"); } if (onRightClick) { EventInvoke(onRightClickEvent, tl, hitInfo[hitIndex], value); } if (!isStay) { //Debug.Log($"OnEnter {tl} {value}"); EventInvoke(onEnterEvent, tl, hitInfo[hitIndex], value); } else { EventInvoke(onStayEvent, tl, hitInfo[hitIndex], value); //Debug.Log($"OnStay {tl} {value}"); } } } void MultiCasting() { tempHit.Clear(); for (int i = 0; i < hitCount; ++i) { HitCheck(i); } foreach (var h in hitTransform) { if (h == null) continue; foreach (var tl in typeLayers) { if (!h.TryGetComponent(tl, out var value)) continue; EventInvoke(onExitEvent, tl, transformToHitinfo[h], value); } transformToHitinfo.Remove(h); } hitTransform.Clear(); foreach (var p in tempHit) { hitTransform.Add(p); } } void EventInvoke(Dictionary> eventTable, Type layer, in RaycastHit hitInfo, Component value) { if (eventTable.TryGetValue(layer, out var action)) { eventList.Add((action,hitInfo, value)); //action?.Invoke(hitInfo, value); } } public bool IsFirstHit(Transform target) { if (hitInfo.Length == 0) return false; return hitInfo[0].transform == target; } public void AddTypeLayer(Type t) { typeLayers.Add(t); typeLayers.RemoveWhere(t => t == null); } public void RemoveTypeLayer() { typeLayers.Remove(typeof(T)); } public void AddEvent_FirstEnter(Type layer, Action action) { onEnterFirst.TryAdd(layer, null); onEnterFirst[layer] += action; } public void AddEvent_FirstExit(Type layer, Action action) { onExitFirst.TryAdd(layer, null); onExitFirst[layer] += action; } public void AddEvent_FirstStay(Type layer, Action action) { onStayFirst.TryAdd(layer, null); onStayFirst[layer] += action; } public void AddEvent_FirstLeftClick(Type layer, Action action) { onLeftClickFirst.TryAdd(layer, null); onLeftClickFirst[layer] += action; } public void AddEvent_FirstRightClick(Type layer, Action action) { onRightClickFirst.TryAdd(layer, null); onRightClickFirst[layer] += action; } public void AddEvent_RightClick(Type layer, Action action) { onRightClickEvent.TryAdd(layer, null); onRightClickEvent[layer] += action; } public void AddEvent_Enter(Type layer, Action action) { onEnterEvent.TryAdd(layer, null); onEnterEvent[layer] += action; } public void AddEvent_Exit(Type layer, Action action) { onExitEvent.TryAdd(layer, null); onExitEvent[layer] += action; } public void AddEvent_Stay(Type layer, Action action) { onStayEvent.TryAdd(layer, null); onStayEvent[layer] += action; } public void AddEvent_LeftClick(Type layer, Action action) { onLeftClickEvent.TryAdd(layer, null); onLeftClickEvent[layer] += action; } } }