Files
Studio/Assets/Scripts/XRLib/GenericController/Raycaster.cs

391 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Studio;
namespace Studio.Manage
{
[DefaultExecutionOrder(int.MinValue)]
public class Raycaster : MonoBehaviour
{
public enum EventType
{
FirstEnter,
FirstClick,
FirstExit,
Enter,
Click,
Exit,
Stay,
RightClick,
FirstRightClick,
FirstStay,
FirstLeftClickOnly,
FirstRightClickOnly,
}
PointerEventData pointerEvent = new(EventSystem.current);
List<RaycastResult> uiRaycastResults = new();
RaycastHit[] hitInfo = new RaycastHit[100];
RaycastHit[] tempInfo;
bool onLeftClick;
bool onRightClick;
Dictionary<Type, RaycastHit> firstHit = new();
Dictionary<Type, RaycastHit> tempFirstHit = new();
Dictionary<Transform, RaycastHit> fth = new();
int hitCount;
Camera cam;
public RaycastHit hit => hitInfo[0];
HashSet<Transform> hitTransform = new();
HashSet<Transform> tempHit = new();
Dictionary<Transform, RaycastHit> transformToHitinfo = new();
List<(Action<RaycastHit, Component>, RaycastHit, Component)> eventList = new();
public float uiHoverTime;
float uiHoverTimer;
#pragma warning disable IDE0044 // 읽기 전용 한정자 추가
#pragma warning disable CS0649 // 'Raycaster.onUIHoverEvent' 필드에는 할당되지 않으므로 항상 null 기본값을 사용합니다.
public Action<RaycastResult> onUIHoverEvent;
public Action onUIHoverExitEvent;
#pragma warning restore CS0649
#pragma warning restore IDE0044
Vector2 prevMousePos;
public override void AfterAwake()
{
cam = Camera.main;
}
void Update()
{
onLeftClick = Input.GetMouseButtonDown(0);
onRightClick = Input.GetMouseButtonDown(1);
if (!UIRaycast())
{
PhysicsRaycast();
}
EventInvoking();
}
void EventInvoking()
{
foreach(var e in eventList)
{
e.Item1?.Invoke(e.Item2, e.Item3);
}
eventList.Clear();
}
bool UIRaycast()
{
Vector2 currentMousePos = Input.mousePosition;
pointerEvent.position = currentMousePos;
EventSystem.current.RaycastAll(pointerEvent, uiRaycastResults);
uiRaycastResults = uiRaycastResults.Where(r => r.gameObject.GetComponent<Graphic>() != null).ToList();
if (uiRaycastResults.Count == 0)
{
ResetHoverTime();
return false;
}
if (onLeftClick)
{
}
if (prevMousePos != currentMousePos)
{
ResetHoverTime();
prevMousePos = currentMousePos;
}
if (uiHoverTimer >= uiHoverTime)
{
onUIHoverEvent?.Invoke(uiRaycastResults[0]);
}
else
{
uiHoverTimer += Time.deltaTime;
}
return true;
}
void PhysicsRaycast()
{
Rayfire();
SingleCasting();
MultiCasting();
}
void Rayfire()
{
var ray = cam.ScreenPointToRay(Input.mousePosition);
//Physics.Raycast(ray, out singleHit, Mathf.Infinity);
tempInfo = new RaycastHit[100];
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 eventTable.Keys)
{
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);
Invoking(EventType.FirstStay, tl, hitInfo, value);
}
else
{
//Debug.Log($"OnExitFirst : {prev.transform.name}");
if (prev.transform.TryGetComponent(tl, out var prevValue))
{
//EventInvoke(onExitFirst, tl, prev, prevValue);
Invoking(EventType.FirstExit, tl, hitInfo, value);
}
//Debug.Log($"OnEnterFirst : {hitInfo.transform.name}");
//EventInvoke(onEnterFirst, tl, hitInfo, value);
Invoking(EventType.FirstEnter, tl, hitInfo, value);
fth.Remove(prev.transform);
}
}
else
{
//Debug.Log($"OnEnterFirst : {hitInfo.transform.name}");
//EventInvoke(onEnterFirst, tl, hitInfo, value);
Invoking(EventType.FirstEnter, 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();
FirstClickOnlyEvent();
}
void FirstClickEvent()
{
foreach (var p in tempFirstHit)
{
firstHit.Add(p.Key, p.Value);
if (onLeftClick)
{
Invoking(EventType.FirstClick, p.Key, p.Value, p.Value.transform.GetComponent(p.Key));
}
if (onRightClick)
{
Invoking(EventType.FirstRightClick, p.Key, p.Value, p.Value.transform.GetComponent(p.Key));
}
}
}
void FirstClickOnlyEvent()
{
foreach (var p in tempFirstHit)
{
if (onLeftClick)
{
Invoking(EventType.FirstLeftClickOnly, p.Key, p.Value, p.Value.transform.GetComponent(p.Key));
//EventInvoke(onLeftClickOnlyFirst, p.Key, p.Value, p.Value.transform.GetComponent(p.Key));
return;
}
if (onRightClick)
{
Invoking(EventType.FirstRightClickOnly, p.Key, p.Value, p.Value.transform.GetComponent(p.Key));
return;
}
}
}
void FirstExitEvent()
{
foreach (var f in firstHit)
{
//Debug.Log($"OnExitFirst :{f.Value.transform.name}");
if (f.Value.transform == null)
continue;
Invoking(EventType.FirstExit, 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 MultiCasting()
{
tempHit.Clear();
for (int i = 0; i < hitCount; ++i)
{
var ht = hitInfo[i].transform;
tempHit.Add(ht);
transformToHitinfo.TryAdd(ht, hitInfo[i]);
bool isStay = hitTransform.Remove(ht);
foreach (var tl in eventTable.Keys)
{
if (!ht.TryGetComponent(tl, out var value))
continue;
if (onLeftClick)
{
Invoking(EventType.Click, tl, hitInfo[i], value);
//Debug.Log($"OnClick {tl} {value}");
}
if(onRightClick)
{
Invoking(EventType.RightClick, tl, hitInfo[i], value);
}
if (!isStay)
{
//Debug.Log($"OnEnter {tl} {value}");
Invoking(EventType.Enter, tl, hitInfo[i], value);
}
else
{
//EventInvoke(onStayEvent_TypeLayer, tl, hitInfo[i], value);
Invoking(EventType.Stay, tl, hitInfo[i], value);
//Debug.Log($"OnStay {tl} {value}");
}
}
}
foreach (var h in hitTransform)
{
if (h == null)
continue;
foreach (var tl in eventTable.Keys)
{
if (!h.TryGetComponent(tl, out var value))
continue;
Invoking(EventType.Exit, tl, transformToHitinfo[h], value);
//EventInvoke(onExitEvent_TypeLayer, tl, transformToHitinfo[h], value);
//Debug.Log($"OnExit {tl} {value}");
}
transformToHitinfo.Remove(h);
}
hitTransform.Clear();
foreach (var p in tempHit)
{
hitTransform.Add(p);
}
}
void Invoking(EventType et, Type t, in RaycastHit hitInfo, Component value)
{
if(eventTable.TryGetValue(t, out var actionTable))
{
if(actionTable.TryGetValue(et, out var action))
{
eventList.Add((action, hitInfo, value));
}
}
}
public bool IsFirstHit(Transform target)
{
if (hitInfo.Length == 0)
return false;
return hitInfo[0].transform == target;
}
Dictionary<Type, Dictionary<EventType, Action<RaycastHit, Component>>> eventTable = new();
public void AddEvent(EventType et, Type layer, Action<RaycastHit, Component> action)
{
if (!eventTable.ContainsKey(layer))
eventTable.Add(layer, new Dictionary<EventType, Action<RaycastHit, Component>>());
if (!eventTable[layer].ContainsKey(et))
eventTable[layer].Add(et, null);
eventTable[layer][et] += action;
}
public void RemoveEvent(EventType et, Type layer, Action<RaycastHit, Component> action)
{
eventTable[layer][et]-=action;
}
void ResetHoverTime()
{
onUIHoverExitEvent?.Invoke();
uiHoverTimer = 0f;
}
}
}