240 lines
6.2 KiB
C#
240 lines
6.2 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using KitsuneCafe.Sys.Attributes;
|
|
using R3;
|
|
using UnityEngine;
|
|
using static KitsuneCafe.Extension.R3Extensions;
|
|
|
|
namespace KitsuneCafe.Sys
|
|
{
|
|
public class Raycaster : MonoBehaviour, INotifyPropertyChanged
|
|
{
|
|
private static readonly Lazy<IComparer<RaycastHit>> hitComparer = new(
|
|
() => FComparer<RaycastHit>.Create((a, b) => a.distance.CompareTo(b.distance))
|
|
);
|
|
|
|
[SerializeField]
|
|
private bool everyFrame = true;
|
|
|
|
[SerializeField, DrawIf(nameof(everyFrame), true)]
|
|
private PlayerLoopTiming updateTiming = PlayerLoopTiming.FixedUpdate;
|
|
|
|
[SerializeField]
|
|
private Vector3 direction = Vector3.forward;
|
|
public Vector3 Direction => DirectionFrom(directionSpace, direction, transform);
|
|
|
|
[SerializeField]
|
|
private Space directionSpace = Space.World;
|
|
|
|
[SerializeField]
|
|
private float maxDistance = 1f;
|
|
public float MaxDistance => maxDistance;
|
|
|
|
[SerializeField]
|
|
private LayerMask layerMask = default;
|
|
public LayerMask LayerMask => LayerMask;
|
|
|
|
[SerializeField]
|
|
private QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal;
|
|
public QueryTriggerInteraction QueryTriggerInteraction => queryTriggerInteraction;
|
|
|
|
[SerializeField]
|
|
private int bufferSize = 1;
|
|
public int BufferSize => bufferSize;
|
|
|
|
private int hitCount = 0;
|
|
public int HitCount
|
|
{
|
|
get => hitCount;
|
|
private set
|
|
{
|
|
if (hitCount != value)
|
|
{
|
|
var isCollidingChanged = value == 0 || hitCount == 0;
|
|
|
|
hitCount = value;
|
|
Notify();
|
|
|
|
if (isCollidingChanged)
|
|
{
|
|
Notify(nameof(IsColliding));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsColliding => hitCount > 0;
|
|
|
|
private RaycastHit[] hits;
|
|
private HashSet<int> ids = new();
|
|
public ReadOnlyMemory<RaycastHit> Collisions => new(hits, 0, hitCount);
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
private CancellationTokenSource disableCancellationTokenSource;
|
|
private CancellationToken disableCancellationToken => disableCancellationTokenSource.Token;
|
|
|
|
private void Reset()
|
|
{
|
|
layerMask = LayerMask.NameToLayer("Default");
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
disableCancellationTokenSource = new();
|
|
hits = new RaycastHit[bufferSize];
|
|
|
|
if (everyFrame)
|
|
{
|
|
var source = bufferSize > 1 ?
|
|
RaycastMany() :
|
|
RaycastSingle();
|
|
|
|
source.RegisterTo(disableCancellationToken);
|
|
}
|
|
}
|
|
|
|
private IDisposable RaycastMany() =>
|
|
Observable.EveryUpdate(updateTiming.GetFrameProvider())
|
|
.WithLatestFrom(DirectionStreamFrom(directionSpace, direction, transform), (_, dir) => dir)
|
|
.Subscribe(dir => RaycastAll(transform.position, dir));
|
|
|
|
private IDisposable RaycastSingle() =>
|
|
Observable.EveryUpdate(updateTiming.GetFrameProvider())
|
|
.WithLatestFrom(DirectionStreamFrom(directionSpace, direction, transform), (_, dir) => dir)
|
|
.Subscribe(dir => hitCount = Raycast(transform.position, dir, out hits[0]) ? 1 : 0);
|
|
|
|
public bool TryGetClosestHit(out RaycastHit hit)
|
|
{
|
|
if (hitCount > 0)
|
|
{
|
|
SortRaycastHits();
|
|
hit = bufferSize switch
|
|
{
|
|
> 1 => hits[0],
|
|
_ => default
|
|
};
|
|
|
|
return true;
|
|
}
|
|
|
|
hit = default;
|
|
return false;
|
|
}
|
|
|
|
private void SortRaycastHits()
|
|
{
|
|
Array.Sort(hits, hitComparer.Value);
|
|
}
|
|
|
|
private static Observable<Vector3> DirectionStreamFrom(Space space, Vector3 direction, Transform target) =>
|
|
space switch
|
|
{
|
|
Space.World => Observable.Return(direction),
|
|
Space.Self => Observable.EveryUpdate(UnityFrameProvider.FixedUpdate)
|
|
.Select(_ => target.TransformDirection(direction)),
|
|
_ => throw new ArgumentOutOfRangeException(space.ToString()),
|
|
};
|
|
|
|
private static Vector3 DirectionFrom(Space space, Vector3 direction, Transform target) =>
|
|
space switch
|
|
{
|
|
Space.World => direction,
|
|
Space.Self => target.TransformDirection(direction),
|
|
_ => throw new ArgumentOutOfRangeException(space.ToString()),
|
|
};
|
|
|
|
private void OnDisable()
|
|
{
|
|
disableCancellationTokenSource.Cancel();
|
|
}
|
|
|
|
public bool Raycast(out RaycastHit hit) => Raycast(
|
|
DirectionFrom(directionSpace, direction, transform),
|
|
out hit
|
|
);
|
|
|
|
public bool Raycast(Vector3 direction, out RaycastHit hit) => Raycast(
|
|
transform.position,
|
|
direction,
|
|
out hit
|
|
);
|
|
|
|
private bool Raycast(Vector3 position, Vector3 direction, out RaycastHit hit) =>
|
|
Physics.Raycast(
|
|
position,
|
|
direction,
|
|
out hit,
|
|
maxDistance,
|
|
layerMask,
|
|
queryTriggerInteraction
|
|
);
|
|
|
|
public bool RaycastAll(Vector3 direction) =>
|
|
RaycastAll(
|
|
transform.position,
|
|
direction
|
|
);
|
|
|
|
private bool RaycastAll(Vector3 position, Vector3 direction)
|
|
{
|
|
HitCount = RaycastAll(position, direction, hits);
|
|
|
|
NotifyChanges();
|
|
|
|
return hitCount > 0;
|
|
}
|
|
|
|
private void NotifyChanges()
|
|
{
|
|
var set = new HashSet<int>();
|
|
var notified = false;
|
|
foreach (var hit in Collisions.Span)
|
|
{
|
|
set.Add(hit.colliderInstanceID);
|
|
|
|
if (!notified && !ids.Contains(hit.colliderInstanceID))
|
|
{
|
|
notified = true;
|
|
Notify(nameof(Collisions));
|
|
}
|
|
}
|
|
|
|
ids.Clear();
|
|
ids.UnionWith(set);
|
|
}
|
|
|
|
private int RaycastAll(Vector3 position, Vector3 direction, RaycastHit[] hits) =>
|
|
Physics.RaycastNonAlloc(
|
|
position,
|
|
direction,
|
|
hits,
|
|
maxDistance,
|
|
layerMask,
|
|
queryTriggerInteraction
|
|
);
|
|
|
|
private void Notify([CallerMemberName] string name = default)
|
|
{
|
|
PropertyChanged?.Invoke(this, new(name));
|
|
}
|
|
|
|
private void OnDrawGizmos()
|
|
{
|
|
Gizmos.color = IsColliding ? Color.green : Color.yellow;
|
|
Gizmos.DrawRay(
|
|
transform.position,
|
|
DirectionFrom(
|
|
directionSpace,
|
|
direction,
|
|
transform
|
|
) * maxDistance
|
|
);
|
|
}
|
|
}
|
|
}
|