379 lines
13 KiB
C#
379 lines
13 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using BITKit;
|
|
using BITKit.Entities;
|
|
using BITKit.Physics;
|
|
using BITKit.Tween;
|
|
using BITKit.WorldNode;
|
|
using Cysharp.Threading.Tasks;
|
|
using Cysharp.Threading.Tasks.Triggers;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Net.BITKit.Quadtree;
|
|
using Net.Project.B.Health;
|
|
using Net.Project.B.Interaction;
|
|
using Project.B.CharacterController;
|
|
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace Net.Project.B.WorldNode
|
|
{
|
|
public class UnityDoorService:IDisposable
|
|
{
|
|
private readonly IEntitiesService _entitiesService;
|
|
private readonly IWorldInteractionService _interactionService;
|
|
private readonly Dictionary<int, UnityDoorNode> _doorNodes = new();
|
|
private readonly ConcurrentDictionary<int, HingeJoint> _hingeJoints = new();
|
|
|
|
private readonly ConcurrentDictionary<int, CancellationTokenSource> _stopAnimationCancellation = new();
|
|
|
|
private readonly Quadtree _quadtree = new(default, new float2(2048, 2048));
|
|
|
|
private readonly Queue<int> _removeQueue = new();
|
|
|
|
private readonly ITicker _ticker;
|
|
|
|
private readonly ConcurrentDictionary<int, UnityCollisionController> _collisionControllers = new();
|
|
|
|
public UnityDoorService(IWorldInteractionService interactionService, IEntitiesService entitiesService, ITicker ticker)
|
|
{
|
|
_interactionService = interactionService;
|
|
_entitiesService = entitiesService;
|
|
_ticker = ticker;
|
|
|
|
interactionService.OnInteraction += OnInteraction;
|
|
|
|
_entitiesService.OnAdd += OnAdd;
|
|
_entitiesService.OnRemove += OnRemove;
|
|
|
|
_ticker.Add(OnTick);
|
|
}
|
|
|
|
private void OnTick(float obj)
|
|
{
|
|
var camera = Camera.main;
|
|
if (!camera) return;
|
|
|
|
float3 position = camera.transform.position;
|
|
|
|
var result = _quadtree.Query(position.xz, 16);
|
|
|
|
foreach (var id in result)
|
|
{
|
|
_collisionControllers.GetOrAdd(id, Create);
|
|
|
|
continue;
|
|
|
|
UnityCollisionController Create(int _)
|
|
{
|
|
var transform = _entitiesService.Entities[id].ServiceProvider.GetRequiredService<Transform>();
|
|
transform = _doorNodes[id].DoorTransform ? _doorNodes[id].DoorTransform : transform;
|
|
|
|
var collisionController = transform.gameObject.AddComponent<UnityCollisionController>();
|
|
|
|
_collisionControllers.TryAdd(id, collisionController);
|
|
|
|
collisionController.OnUnityCollisionEnter += OnUnityCollisionEnter;
|
|
|
|
return collisionController;
|
|
}
|
|
|
|
|
|
void OnUnityCollisionEnter(Collision collision)
|
|
{
|
|
if (_doorNodes[id].State is not (UnityDoorState.Closed or UnityDoorState.NoState)) return;
|
|
|
|
if (_entitiesService.TryGetEntity(collision.transform.gameObject.GetInstanceID(), out var entity) is
|
|
false) return;
|
|
if (entity.ServiceProvider.GetService<ICharacterController>() is not
|
|
{ } characterController) return;
|
|
switch (characterController.CurrentState)
|
|
{
|
|
case ICharacterStateRun:
|
|
case ICharacterSprint:
|
|
case ICharacterSliding:
|
|
_doorNodes[id].State = UnityDoorState.Closed;
|
|
InteractionDoor(_doorNodes[id], -1).Forget();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
foreach (var i in _collisionControllers.Keys)
|
|
{
|
|
_collisionControllers.TryRemove(i, out var current);
|
|
Object.Destroy(current);
|
|
}
|
|
|
|
foreach (var id in _collisionControllers.Keys)
|
|
{
|
|
var remove = true;
|
|
foreach (var queryId in result)
|
|
{
|
|
if (queryId != id) continue;
|
|
remove = false;
|
|
break;
|
|
}
|
|
|
|
if (remove)
|
|
{
|
|
_removeQueue.Enqueue(id);
|
|
}
|
|
}
|
|
|
|
while (_removeQueue.TryDequeue(out var removeId))
|
|
{
|
|
if (_collisionControllers.TryRemove(removeId, out var current))
|
|
{
|
|
Object.Destroy(current);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void OnRemove(IEntity obj)
|
|
{
|
|
_quadtree.Remove(obj.Id);
|
|
_doorNodes.TryRemove(obj.Id);
|
|
}
|
|
|
|
private async void OnInteraction(object arg1, IWorldInteractable arg2, WorldInteractionProcess arg3,
|
|
object arg4)
|
|
{
|
|
var id = arg2.WorldObject.As<GameObject>().GetInstanceID();
|
|
;
|
|
if (_doorNodes.TryGetValue(id, out var doorNode) is false) return;
|
|
|
|
if (doorNode.InteractionType is WorldInteractionProcess.None &&
|
|
arg3 is not WorldInteractionProcess.Performed) return;
|
|
|
|
if (doorNode.InteractionType is not WorldInteractionProcess.None &&
|
|
arg3 != doorNode.InteractionType) return;
|
|
|
|
if (_entitiesService.Entities[id].ServiceProvider.GetRequiredService<GameObject>() is not
|
|
{ } gameObject) return;
|
|
|
|
if (arg1 is IEntity entity)
|
|
{
|
|
if (entity.ServiceProvider.QueryComponents(out ICharacterController characterController))
|
|
{
|
|
await InteractionDoor(doorNode, startPosition: characterController.ViewPosition);
|
|
return;
|
|
}
|
|
|
|
if (entity.ServiceProvider.QueryComponents(out Transform transform))
|
|
{
|
|
await InteractionDoor(doorNode, startPosition: transform.position);
|
|
return;
|
|
}
|
|
}
|
|
|
|
await InteractionDoor(doorNode);
|
|
}
|
|
|
|
private async UniTask InteractionDoor(UnityDoorNode doorNode,float delta = 361,Vector3 startPosition=default)
|
|
{
|
|
var id = doorNode.Id;
|
|
|
|
var gameObject = doorNode.DoorTransform.gameObject;
|
|
|
|
Vector3 newPosition = default;
|
|
Quaternion newEuler = default;
|
|
|
|
if (_hingeJoints.TryRemove(doorNode.Id, out var currentHingeJoint))
|
|
{
|
|
Object.Destroy(currentHingeJoint);
|
|
Object.Destroy(currentHingeJoint.GetComponent<Rigidbody>());
|
|
Object.Destroy(currentHingeJoint.transform.parent.GetComponent<Rigidbody>());
|
|
}
|
|
|
|
switch (doorNode.State)
|
|
{
|
|
case UnityDoorState.NoState:
|
|
{
|
|
var angle = Quaternion.Angle(gameObject.transform.localRotation,
|
|
Quaternion.Euler(doorNode.CloseEulerAngles));
|
|
|
|
if (angle < 5)
|
|
{
|
|
goto case UnityDoorState.Closed;
|
|
}
|
|
{
|
|
goto case UnityDoorState.Opened;
|
|
}
|
|
}
|
|
case UnityDoorState.Closed:
|
|
|
|
|
|
if (startPosition.sqrMagnitude is 0)
|
|
{
|
|
startPosition = Camera.main!.transform.position;
|
|
}
|
|
|
|
newPosition = doorNode.OpenPosition;
|
|
|
|
newEuler = Quaternion.Euler(doorNode.OpenEulerAngles);
|
|
|
|
if (doorNode.IsDoubleSwing)
|
|
{
|
|
Vector3 toCamera = (startPosition - gameObject.transform.position).normalized; // 获取玩家相对门的位置
|
|
Vector3 doorForward = gameObject.transform.forward; // 门的正面方向
|
|
|
|
// 计算玩家是否在门的"外侧"(正方向)
|
|
bool isPlayerOutside = Vector3.Dot(doorForward, toCamera) < 0;
|
|
|
|
if (isPlayerOutside)
|
|
{
|
|
newEuler = Quaternion.Euler(-doorNode.OpenEulerAngles); // 向内开
|
|
}
|
|
else
|
|
{
|
|
newEuler = Quaternion.Euler(doorNode.OpenEulerAngles); // 向外开
|
|
}
|
|
}
|
|
|
|
doorNode.State = UnityDoorState.Opened;
|
|
break;
|
|
case UnityDoorState.Opened:
|
|
newPosition = doorNode.ClosePosition;
|
|
newEuler = Quaternion.Euler(doorNode.CloseEulerAngles);
|
|
|
|
doorNode.State = UnityDoorState.Closed;
|
|
break;
|
|
}
|
|
|
|
_doorNodes[id] = doorNode;
|
|
|
|
if (_stopAnimationCancellation.TryRemove(doorNode.Id, out var cts))
|
|
{
|
|
cts.Cancel();
|
|
}
|
|
|
|
cts = new CancellationTokenSource();
|
|
|
|
_stopAnimationCancellation.TryAdd(doorNode.Id, cts);
|
|
|
|
UnityCollisionController physics = null;
|
|
|
|
if (doorNode.IsDoubleSwing)
|
|
{
|
|
physics = gameObject.AddComponent<UnityCollisionController>();
|
|
|
|
physics.OnUnityCollisionEnter += Cancel;
|
|
}
|
|
|
|
gameObject.GetCancellationTokenOnDestroy().Register(cts.Cancel);
|
|
|
|
try
|
|
{
|
|
if (delta > 0)
|
|
{
|
|
var posTask = BITween.MoveToForward(x => gameObject.transform.localPosition = x,
|
|
gameObject.transform.localPosition, newPosition, 1, Vector3.MoveTowards, cts.Token);
|
|
var rotTask = BITween.MoveToForward(x => gameObject.transform.localRotation = x,
|
|
gameObject.transform.localRotation, newEuler, delta, Quaternion.RotateTowards, cts.Token);
|
|
await UniTask.WhenAll(posTask, rotTask).AttachExternalCancellation(cts.Token);
|
|
}
|
|
else
|
|
{
|
|
gameObject.transform.localPosition = newPosition;
|
|
gameObject.transform.localRotation = newEuler;
|
|
}
|
|
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
doorNode.State = UnityDoorState.NoState;
|
|
}
|
|
|
|
if (physics)
|
|
{
|
|
physics.OnUnityCollisionEnter -= Cancel;
|
|
|
|
Object.Destroy(physics);
|
|
}
|
|
|
|
return;
|
|
|
|
void Cancel(Collision collision)
|
|
{
|
|
if (collision.gameObject.layer == LayerMask.NameToLayer("Default") ||
|
|
collision.gameObject.isStatic) return;
|
|
cts.Cancel();
|
|
|
|
if (_hingeJoints.TryGetValue(doorNode.Id, out var hingeJoint) is false)
|
|
{
|
|
hingeJoint = gameObject.AddComponent<HingeJoint>();
|
|
hingeJoint.GetComponent<Rigidbody>().mass = 64;
|
|
_hingeJoints.TryAdd(doorNode.Id, hingeJoint);
|
|
}
|
|
|
|
if (gameObject.transform.parent.TryGetComponent<Rigidbody>(out var rigidbody) is false)
|
|
{
|
|
rigidbody = gameObject.transform.parent.gameObject.AddComponent<Rigidbody>();
|
|
rigidbody.isKinematic = true;
|
|
}
|
|
|
|
hingeJoint.connectedBody = rigidbody;
|
|
hingeJoint.axis = doorNode.OpenEulerAngles.normalized;
|
|
|
|
var angle = doorNode.OpenEulerAngles.GetLength();
|
|
|
|
hingeJoint.limits = new JointLimits
|
|
{
|
|
min = doorNode.IsDoubleSwing ? -angle : 0,
|
|
max = angle
|
|
};
|
|
|
|
hingeJoint.useLimits = true;
|
|
}
|
|
}
|
|
|
|
private void OnAdd(IEntity entity)
|
|
{
|
|
if (entity.ServiceProvider.GetService<UnityDoorNode>() is not { } doorNode) return;
|
|
doorNode.Id = entity.Id;
|
|
_doorNodes.Add(entity.Id, doorNode);
|
|
|
|
var transform = entity.ServiceProvider.GetRequiredService<Transform>();
|
|
|
|
transform = doorNode.DoorTransform ? doorNode.DoorTransform : transform;
|
|
|
|
if (!doorNode.DoorTransform)
|
|
{
|
|
doorNode.DoorTransform = transform;
|
|
}
|
|
|
|
float3 pos = transform.position;
|
|
|
|
_quadtree.Insert(entity.Id,pos.xz);
|
|
|
|
var angle = (Quaternion.Angle(transform.localRotation,
|
|
Quaternion.Euler(doorNode.CloseEulerAngles)));
|
|
|
|
doorNode.State = (doorNode.State) switch
|
|
{
|
|
UnityDoorState.NoState=>angle<5 ? UnityDoorState.Closed : UnityDoorState.Opened,
|
|
_=>doorNode.State,
|
|
};
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_interactionService.OnInteraction -= OnInteraction;
|
|
|
|
_entitiesService.OnAdd -= OnAdd;
|
|
_entitiesService.OnRemove -= OnRemove;
|
|
|
|
_ticker.Remove(OnTick);
|
|
}
|
|
}
|
|
|
|
}
|