Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/WorldNodeService/UnityDoorService.cs

379 lines
13 KiB
C#
Raw Normal View History

2025-06-24 23:49:13 +08:00
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);
}
}
}