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 _doorNodes = new(); private readonly ConcurrentDictionary _hingeJoints = new(); private readonly ConcurrentDictionary _stopAnimationCancellation = new(); private readonly Quadtree _quadtree = new(default, new float2(2048, 2048)); private readonly Queue _removeQueue = new(); private readonly ITicker _ticker; private readonly ConcurrentDictionary _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 = _doorNodes[id].DoorTransform ? _doorNodes[id].DoorTransform : transform; var collisionController = transform.gameObject.AddComponent(); _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() 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().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() 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()); Object.Destroy(currentHingeJoint.transform.parent.GetComponent()); } 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(); 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.GetComponent().mass = 64; _hingeJoints.TryAdd(doorNode.Id, hingeJoint); } if (gameObject.transform.parent.TryGetComponent(out var rigidbody) is false) { rigidbody = gameObject.transform.parent.gameObject.AddComponent(); 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() is not { } doorNode) return; doorNode.Id = entity.Id; _doorNodes.Add(entity.Id, doorNode); var transform = entity.ServiceProvider.GetRequiredService(); 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); } } }