You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
441 lines
17 KiB
441 lines
17 KiB
5 months ago
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.EventSystems;
|
||
|
|
||
|
namespace HighlightPlus {
|
||
|
|
||
|
public enum TriggerMode {
|
||
|
ColliderEventsOnlyOnThisObject = 0,
|
||
|
RaycastOnThisObjectAndChildren = 1,
|
||
|
Volume = 2
|
||
|
}
|
||
|
|
||
|
public enum RayCastSource {
|
||
|
MousePosition = 0,
|
||
|
CameraDirection = 1
|
||
|
}
|
||
|
|
||
|
|
||
|
[RequireComponent(typeof(HighlightEffect))]
|
||
|
[ExecuteInEditMode]
|
||
|
[HelpURL("https://kronnect.com/guides/highlight-plus-introduction/")]
|
||
|
public class HighlightTrigger : MonoBehaviour {
|
||
|
|
||
|
[Tooltip("Enables highlight when pointer is over this object.")]
|
||
|
public bool highlightOnHover = true;
|
||
|
[Tooltip("Used to trigger automatic highlighting including children objects.")]
|
||
|
#if ENABLE_INPUT_SYSTEM
|
||
|
public TriggerMode triggerMode = TriggerMode.RaycastOnThisObjectAndChildren;
|
||
|
#else
|
||
|
public TriggerMode triggerMode = TriggerMode.ColliderEventsOnlyOnThisObject;
|
||
|
#endif
|
||
|
public Camera raycastCamera;
|
||
|
public RayCastSource raycastSource = RayCastSource.MousePosition;
|
||
|
public LayerMask raycastLayerMask = -1;
|
||
|
[Tooltip("Minimum distance for target.")]
|
||
|
public float minDistance;
|
||
|
[Tooltip("Maximum distance for target. 0 = infinity")]
|
||
|
public float maxDistance;
|
||
|
[Tooltip("Blocks interaction if pointer is over an UI element")]
|
||
|
public bool respectUI = true;
|
||
|
public LayerMask volumeLayerMask;
|
||
|
|
||
|
const int MAX_RAYCAST_HITS = 100;
|
||
|
|
||
|
|
||
|
[Tooltip("If the object will be selected by clicking with mouse or tapping on it.")]
|
||
|
public bool selectOnClick;
|
||
|
[Tooltip("Profile to use when object is selected by clicking on it.")]
|
||
|
public HighlightProfile selectedProfile;
|
||
|
[Tooltip("Profile to use whtn object is selected and highlighted.")]
|
||
|
public HighlightProfile selectedAndHighlightedProfile;
|
||
|
[Tooltip("Automatically deselects any other selected object prior selecting this one")]
|
||
|
public bool singleSelection;
|
||
|
[Tooltip("Toggles selection on/off when clicking object")]
|
||
|
public bool toggle;
|
||
|
[Tooltip("Keeps current selection when clicking outside of any selectable object")]
|
||
|
public bool keepSelection = true;
|
||
|
|
||
|
[NonSerialized] public Collider[] colliders;
|
||
|
[NonSerialized] public Collider2D[] colliders2D;
|
||
|
|
||
|
public bool hasColliders => (colliders != null && colliders.Length > 0);
|
||
|
public bool hasColliders2D => (colliders2D != null && colliders2D.Length > 0);
|
||
|
|
||
|
UnityEngine.Object currentCollider;
|
||
|
static RaycastHit[] hits;
|
||
|
static RaycastHit2D[] hits2D;
|
||
|
HighlightEffect hb;
|
||
|
|
||
|
public HighlightEffect highlightEffect { get { return hb; } }
|
||
|
|
||
|
public event OnObjectSelectionEvent OnObjectSelected;
|
||
|
public event OnObjectSelectionEvent OnObjectUnSelected;
|
||
|
public event OnObjectHighlightEvent OnObjectHighlightStart;
|
||
|
public event OnObjectHighlightEvent OnObjectHighlightEnd;
|
||
|
|
||
|
TriggerMode currentTriggerMode;
|
||
|
|
||
|
|
||
|
[RuntimeInitializeOnLoadMethod]
|
||
|
static void DomainReloadDisabledSupport() {
|
||
|
HighlightManager.selectedObjects.Clear();
|
||
|
}
|
||
|
|
||
|
void OnEnable() {
|
||
|
Init();
|
||
|
}
|
||
|
|
||
|
private void OnValidate() {
|
||
|
if (currentTriggerMode != triggerMode) {
|
||
|
UpdateTriggers();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void UpdateTriggers() {
|
||
|
currentTriggerMode = triggerMode;
|
||
|
if (currentTriggerMode == TriggerMode.RaycastOnThisObjectAndChildren) {
|
||
|
colliders = GetComponentsInChildren<Collider>();
|
||
|
colliders2D = GetComponentsInChildren<Collider2D>();
|
||
|
if (hits == null || hits.Length != MAX_RAYCAST_HITS) {
|
||
|
hits = new RaycastHit[MAX_RAYCAST_HITS];
|
||
|
}
|
||
|
if (hits2D == null || hits2D.Length != MAX_RAYCAST_HITS) {
|
||
|
hits2D = new RaycastHit2D[MAX_RAYCAST_HITS];
|
||
|
}
|
||
|
if (Application.isPlaying) {
|
||
|
StopAllCoroutines();
|
||
|
if (gameObject.activeInHierarchy) {
|
||
|
StartCoroutine(DoRayCast());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public void Init() {
|
||
|
if (raycastCamera == null) {
|
||
|
raycastCamera = HighlightManager.GetCamera();
|
||
|
}
|
||
|
UpdateTriggers();
|
||
|
if (hb == null) {
|
||
|
hb = GetComponent<HighlightEffect>();
|
||
|
}
|
||
|
InputProxy.Init();
|
||
|
}
|
||
|
|
||
|
void Start() {
|
||
|
UpdateTriggers();
|
||
|
if (triggerMode == TriggerMode.RaycastOnThisObjectAndChildren) {
|
||
|
if (raycastCamera == null) {
|
||
|
raycastCamera = HighlightManager.GetCamera();
|
||
|
if (raycastCamera == null) {
|
||
|
Debug.LogError("Highlight Trigger on " + gameObject.name + ": no camera found!");
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
Collider collider = GetComponent<Collider>();
|
||
|
if (collider == null) {
|
||
|
if (GetComponent<MeshFilter>() != null) {
|
||
|
gameObject.AddComponent<MeshCollider>();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
IEnumerator DoRayCast() {
|
||
|
yield return null;
|
||
|
WaitForEndOfFrame w = new WaitForEndOfFrame();
|
||
|
while (triggerMode == TriggerMode.RaycastOnThisObjectAndChildren) {
|
||
|
if (raycastCamera == null) {
|
||
|
yield return null;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
int hitCount;
|
||
|
bool hit = false;
|
||
|
|
||
|
#if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
|
||
|
|
||
|
if (respectUI) {
|
||
|
EventSystem es = EventSystem.current;
|
||
|
if (es == null) {
|
||
|
es = CreateEventSystem();
|
||
|
}
|
||
|
List<RaycastResult> raycastResults = new List<RaycastResult>();
|
||
|
PointerEventData eventData = new PointerEventData(es);
|
||
|
Vector3 cameraPos = raycastCamera.transform.position;
|
||
|
if (raycastSource == RayCastSource.MousePosition) {
|
||
|
eventData.position = InputProxy.mousePosition;
|
||
|
} else {
|
||
|
eventData.position = new Vector2(raycastCamera.pixelWidth * 0.5f, raycastCamera.pixelHeight * 0.5f);
|
||
|
}
|
||
|
es.RaycastAll(eventData, raycastResults);
|
||
|
hitCount = raycastResults.Count;
|
||
|
// check UI blocker
|
||
|
bool blocked = false;
|
||
|
for (int k = 0; k < hitCount; k++) {
|
||
|
RaycastResult rr = raycastResults[k];
|
||
|
if (rr.module is UnityEngine.UI.GraphicRaycaster) {
|
||
|
blocked = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (blocked) {
|
||
|
yield return null;
|
||
|
continue;
|
||
|
}
|
||
|
// look for our gameobject
|
||
|
for (int k = 0; k < hitCount; k++) {
|
||
|
RaycastResult rr = raycastResults[k];
|
||
|
float distance = Vector3.Distance(rr.worldPosition, cameraPos);
|
||
|
if (distance < minDistance || (maxDistance > 0 && distance > maxDistance)) continue;
|
||
|
|
||
|
GameObject theGameObject = rr.gameObject;
|
||
|
for (int c = 0; c < colliders.Length; c++) {
|
||
|
if (colliders[c].gameObject == theGameObject) {
|
||
|
Collider theCollider = colliders[c];
|
||
|
hit = true;
|
||
|
if (selectOnClick && InputProxy.GetMouseButtonDown(0)) {
|
||
|
ToggleSelection();
|
||
|
break;
|
||
|
} else if (theCollider != currentCollider) {
|
||
|
SwitchCollider(theCollider);
|
||
|
k = hitCount;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// if not blocked by UI and no hit found, fallback to raycast (required if no PhysicsRaycaster is present on the camera)
|
||
|
|
||
|
#endif
|
||
|
Ray ray;
|
||
|
if (raycastSource == RayCastSource.MousePosition) {
|
||
|
#if !ENABLE_INPUT_SYSTEM
|
||
|
|
||
|
if (!CanInteract()) {
|
||
|
yield return null;
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
ray = raycastCamera.ScreenPointToRay(InputProxy.mousePosition);
|
||
|
} else {
|
||
|
ray = new Ray(raycastCamera.transform.position, raycastCamera.transform.forward);
|
||
|
}
|
||
|
bool isMouseButonDown = InputProxy.GetMouseButtonDown(0);
|
||
|
if (hasColliders2D) {
|
||
|
if (maxDistance > 0) {
|
||
|
hitCount = Physics2D.GetRayIntersectionNonAlloc(ray, hits2D, maxDistance, raycastLayerMask);
|
||
|
} else {
|
||
|
hitCount = Physics2D.GetRayIntersectionNonAlloc(ray, hits2D, float.MaxValue, raycastLayerMask);
|
||
|
}
|
||
|
for (int k = 0; k < hitCount; k++) {
|
||
|
if (Vector3.Distance(hits2D[k].point, ray.origin) < minDistance) continue;
|
||
|
Collider2D theCollider = hits2D[k].collider;
|
||
|
int colliders2DCount = colliders2D.Length;
|
||
|
for (int c = 0; c < colliders2DCount; c++) {
|
||
|
if (colliders2D[c] == theCollider) {
|
||
|
hit = true;
|
||
|
if (selectOnClick && isMouseButonDown) {
|
||
|
ToggleSelection();
|
||
|
break;
|
||
|
} else if (theCollider != currentCollider) {
|
||
|
SwitchCollider(theCollider);
|
||
|
k = hitCount;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (hasColliders) {
|
||
|
if (maxDistance > 0) {
|
||
|
hitCount = Physics.RaycastNonAlloc(ray, hits, maxDistance, raycastLayerMask);
|
||
|
} else {
|
||
|
hitCount = Physics.RaycastNonAlloc(ray, hits, float.MaxValue, raycastLayerMask);
|
||
|
}
|
||
|
for (int k = 0; k < hitCount; k++) {
|
||
|
if (Vector3.Distance(hits[k].point, ray.origin) < minDistance) continue;
|
||
|
Collider theCollider = hits[k].collider;
|
||
|
int collidersCount = colliders.Length;
|
||
|
for (int c = 0; c < collidersCount; c++) {
|
||
|
if (colliders[c] == theCollider) {
|
||
|
hit = true;
|
||
|
if (selectOnClick && isMouseButonDown) {
|
||
|
ToggleSelection();
|
||
|
break;
|
||
|
} else if (theCollider != currentCollider) {
|
||
|
SwitchCollider(theCollider);
|
||
|
k = hitCount;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (!hit && currentCollider != null) {
|
||
|
SwitchCollider(null);
|
||
|
}
|
||
|
|
||
|
if (selectOnClick && isMouseButonDown && !keepSelection && !hit) {
|
||
|
yield return w; // wait for other potential triggers to act
|
||
|
if (HighlightManager.lastTriggerFrame < Time.frameCount) {
|
||
|
HighlightManager.DeselectAll();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
yield return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
|
||
|
EventSystem CreateEventSystem() {
|
||
|
GameObject eo = new GameObject("Event System created by Highlight Plus", typeof(EventSystem), typeof(UnityEngine.InputSystem.UI.InputSystemUIInputModule));
|
||
|
return eo.GetComponent<EventSystem>();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
void SwitchCollider(UnityEngine.Object newCollider) {
|
||
|
if (!highlightOnHover && !hb.isSelected) return;
|
||
|
|
||
|
currentCollider = newCollider;
|
||
|
if (currentCollider != null) {
|
||
|
Highlight(true);
|
||
|
} else {
|
||
|
Highlight(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CanInteract() {
|
||
|
if (!respectUI) return true;
|
||
|
EventSystem es = EventSystem.current;
|
||
|
if (es == null) return true;
|
||
|
if (Application.isMobilePlatform && InputProxy.touchCount > 0 && es.IsPointerOverGameObject(InputProxy.GetFingerIdFromTouch(0))) {
|
||
|
return false;
|
||
|
} else if (es.IsPointerOverGameObject(-1))
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
void OnMouseDown() {
|
||
|
if (isActiveAndEnabled && triggerMode == TriggerMode.ColliderEventsOnlyOnThisObject) {
|
||
|
if (!CanInteract()) return;
|
||
|
if (selectOnClick && InputProxy.GetMouseButtonDown(0)) {
|
||
|
ToggleSelection();
|
||
|
return;
|
||
|
}
|
||
|
Highlight(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnMouseEnter() {
|
||
|
if (isActiveAndEnabled && triggerMode == TriggerMode.ColliderEventsOnlyOnThisObject) {
|
||
|
if (!CanInteract()) return;
|
||
|
Highlight(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnMouseExit() {
|
||
|
if (isActiveAndEnabled && triggerMode == TriggerMode.ColliderEventsOnlyOnThisObject) {
|
||
|
if (!CanInteract()) return;
|
||
|
Highlight(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Highlight(bool state) {
|
||
|
if (state) {
|
||
|
if (!hb.highlighted) {
|
||
|
if (OnObjectHighlightStart != null && hb.target != null) {
|
||
|
if (!OnObjectHighlightStart(hb.target.gameObject)) return;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if (hb.highlighted) {
|
||
|
if (OnObjectHighlightEnd != null && hb.target != null) {
|
||
|
OnObjectHighlightEnd(hb.target.gameObject);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (selectOnClick || hb.isSelected) {
|
||
|
if (hb.isSelected) {
|
||
|
if (state && selectedAndHighlightedProfile != null) {
|
||
|
selectedAndHighlightedProfile.Load(hb);
|
||
|
} else if (selectedProfile != null) {
|
||
|
selectedProfile.Load(hb);
|
||
|
} else {
|
||
|
hb.previousSettings.Load(hb);
|
||
|
}
|
||
|
if (hb.highlighted) {
|
||
|
hb.UpdateMaterialProperties();
|
||
|
} else {
|
||
|
hb.SetHighlighted(true);
|
||
|
}
|
||
|
return;
|
||
|
} else if (!highlightOnHover) {
|
||
|
hb.SetHighlighted(false);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
hb.SetHighlighted(state);
|
||
|
}
|
||
|
|
||
|
|
||
|
void ToggleSelection() {
|
||
|
|
||
|
HighlightManager.lastTriggerFrame = Time.frameCount;
|
||
|
|
||
|
bool newState = toggle ? !hb.isSelected : true;
|
||
|
if (newState) {
|
||
|
if (OnObjectSelected != null && !OnObjectSelected(gameObject)) return;
|
||
|
} else {
|
||
|
if (OnObjectUnSelected != null && !OnObjectUnSelected(gameObject)) return;
|
||
|
}
|
||
|
|
||
|
if (singleSelection && newState) {
|
||
|
HighlightManager.DeselectAll();
|
||
|
}
|
||
|
hb.isSelected = newState;
|
||
|
if (newState && !HighlightManager.selectedObjects.Contains(hb)) {
|
||
|
HighlightManager.selectedObjects.Add(hb);
|
||
|
} else if (!newState && HighlightManager.selectedObjects.Contains(hb)) {
|
||
|
HighlightManager.selectedObjects.Remove(hb);
|
||
|
}
|
||
|
|
||
|
if (hb.isSelected) {
|
||
|
if (hb.previousSettings == null) {
|
||
|
hb.previousSettings = ScriptableObject.CreateInstance<HighlightProfile>();
|
||
|
}
|
||
|
hb.previousSettings.Save(hb);
|
||
|
} else {
|
||
|
hb.RestorePreviousHighlightEffectSettings();
|
||
|
}
|
||
|
|
||
|
Highlight(true);
|
||
|
}
|
||
|
|
||
|
public void OnTriggerEnter(Collider other) {
|
||
|
if (triggerMode == TriggerMode.Volume) {
|
||
|
if ((volumeLayerMask & (1 << other.gameObject.layer)) != 0) {
|
||
|
Highlight(true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void OnTriggerExit(Collider other) {
|
||
|
if (triggerMode == TriggerMode.Volume) {
|
||
|
if ((volumeLayerMask & (1 << other.gameObject.layer)) != 0) {
|
||
|
Highlight(false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|