using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using BITKit; using BITKit.Entities; using BITKit.Mod; using BITKit.WorldNode; using Cysharp.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Net.BITKit.Quadtree; using Net.Project.B.Interaction; using Net.Project.B.WorldNode; using Project.B.Item; using Unity.Mathematics; using UnityEngine; using Object = UnityEngine.Object; namespace Net.Project.B.Item { public class ManagedItemService:IManagedItemService,IDisposable { private readonly IEntitiesService _entitiesService; private readonly ValidHandle _isBusy = new(); private readonly IFixedTicker _ticker; private readonly IAsyncTicker _asyncTicker; private Material _initialMaterial; private readonly HashSet _waitFreeze = new(); private readonly HashSet _isFreeze = new(); private readonly Quadtree _quadtree = new(default, new float2(2048, 2048)); private readonly HashSet _inRendering=new(); private readonly Queue _inactiveQueue = new(); public ManagedItemService( IEntitiesService entitiesService, IFixedTicker ticker, IAsyncTicker asyncTicker) { _entitiesService = entitiesService; _ticker = ticker; _asyncTicker = asyncTicker; _entitiesService.OnAdd += OnNodeRegistered; _ticker.Add(OnTick); _asyncTicker.OnTickAsync += OnTickAsync; } private UniTask OnTickAsync(float arg) { var camera = Camera.main; if(!camera)return UniTask.CompletedTask; var pos = (float3)camera.transform.position; foreach (var id in _inRendering) { if (_itemsGO.TryGetValue(id, out var go) is false) { _inactiveQueue.Enqueue(id); continue; } var distance = Vector3.Distance(pos,go.transform.position); if (!(distance > 32)) continue; go.SetActive(false); _inactiveQueue.Enqueue(id); } while (_inactiveQueue.TryDequeue(out var id)) { _inRendering.Remove(id); } foreach (var id in _quadtree.Query(pos.xz,16)) { if (_itemsGO.TryGetValue(id,out var go) is false || _inRendering.Add(id) is false)continue; go.SetActive(true); } return UniTask.CompletedTask; } private void OnTick(float obj) { foreach (var rigidbody in _waitFreeze) { if (!rigidbody || rigidbody.IsSleeping() || rigidbody.velocity.GetLength() is 0) { _isFreeze.Add(rigidbody); } } var camera = Camera.main; if (camera) { var rot = camera.transform.rotation * Vector3.back; foreach (var (id,spriteRenderer) in _spriteRenderers) { if (!spriteRenderer) { _spriteRenderers.TryRemove(id); break; } spriteRenderer.transform.rotation = Quaternion.LookRotation(rot); } } foreach (var rigidbody in _isFreeze) { _waitFreeze.Remove(rigidbody); } } private async void OnNodeRegistered(IEntity entity) { if(entity.ServiceProvider.GetService() is not {} itemNode)return; var gameObject = entity.ServiceProvider.GetService(); _itemsGO.TryAdd(gameObject.GetInstanceID(), gameObject); _quadtree.Insert(gameObject.GetInstanceID(),((float3)gameObject.transform.position).xz); await _isBusy; using var _ = _isBusy.GetHandle(); var asset =await ModService.LoadAsset(itemNode.ItemPath); var newItem = asset.CreateRuntimeItem(); newItem.Id = gameObject.GetInstanceID(); AddOrUpdateItem(newItem); if (gameObject.transform.TryGetComponentAny(out var spriteRenderer)) { _spriteRenderers.TryAdd(entity.Id, spriteRenderer); } } public IReadOnlyDictionary Items => _items; private static readonly ConcurrentDictionary _items = new(); private static readonly ConcurrentDictionary _itemsGO = new(); private static readonly ConcurrentDictionary _spriteRenderers = new(); public void AddOrUpdateItem(IRuntimeItem item) { _items.Set(item.Id,item); OnItemAddedOrUpdated?.Invoke(item); } public async UniTask InstanceItem(float3 position, quaternion rotation, IRuntimeItem item) { await _isBusy; using var _ = _isBusy.GetHandle(); if(!_initialMaterial) { _initialMaterial =await ModService.LoadAsset("material_sprite_item"); }; var go = new GameObject { layer = LayerMask.NameToLayer("Loot") }; var scriptableItem = await ModService.LoadAsset(DictionaryReferenceScriptableObject.Dictionary[item.ScriptableId]); if(!go)return null; _items.TryRemove(item.Id); item = item.Clone().As(); item.Id = go.GetInstanceID(); _items.TryAdd(item.Id,item); _itemsGO.TryAdd(item.Id,go); go.transform.position = (Vector3)position+Vector3.up; go.transform.rotation = rotation; // 获取或创建 BoxCollider 组件 var boxCollider = go.GetComponent(); if (boxCollider == null) { boxCollider = go.AddComponent(); } boxCollider.gameObject.layer = LayerMask.NameToLayer("Loot"); if (scriptableItem.Model) { var newModel = Object.Instantiate(scriptableItem.Model, go.transform); foreach (var collider in newModel.GetComponentsInChildren()) { Object.Destroy(collider); } // 获取所有可见的 MeshRenderer 组件 var meshRenderers = go.GetComponentsInChildren(); if (meshRenderers.Length == 0) { Debug.LogWarning("No MeshRenderer components found in children."); } // 初始化包围盒,以第一个 MeshRenderer 的包围盒为基准 Bounds bounds = new Bounds(go.transform.InverseTransformPoint(meshRenderers[0].bounds.center), go.transform.InverseTransformVector(meshRenderers[0].bounds.size)); // 遍历所有 MeshRenderer,合并包围盒 for (int i = 1; i < meshRenderers.Length; i++) { Bounds localBounds = meshRenderers[i].bounds; Vector3 localCenter = go.transform.InverseTransformPoint(localBounds.center); Vector3 localSize = go.transform.InverseTransformVector(localBounds.size); bounds.Encapsulate(new Bounds(localCenter, localSize)); } // 设置 BoxCollider 的中心和大小 boxCollider.center = bounds.center; boxCollider.size = math.abs(bounds.size); } else { var spriteObject = new GameObject { transform = { parent = go.transform, localPosition = Vector3.zero } }; var spriteRenderer = spriteObject.AddComponent(); spriteRenderer.sprite = scriptableItem.Icon; spriteRenderer.receiveShadows = false; spriteRenderer.drawMode = SpriteDrawMode.Sliced; spriteRenderer.size = new Vector2(0.32f, 0.32f); spriteRenderer.material = _initialMaterial; _spriteRenderers.TryAdd(item.Id, spriteRenderer); boxCollider.size = new Vector3(0.2f, 0.2f, 0.2f); boxCollider.center = new Vector3(0, 0.1f, 0); spriteRenderer.transform.localPosition = new Vector3(0, 0.16f, 0); } await UniTask.NextFrame(); if (go.TryGetComponent(out var entity) is false) { entity = new Entity() { Id = go.GetInstanceID(), }; } entity.ServiceCollection.AddSingleton(new WorldInfoNode() { Name = scriptableItem.Name, Description = scriptableItem.Description }); entity.ServiceCollection.AddSingleton(go); entity.ServiceCollection.AddSingleton(go.transform); entity.ServiceCollection.AddSingleton(boxCollider); entity.ServiceCollection.AddSingleton(new WorldInteractable() { Id = item.Id, WorldObject = go }); entity.ServiceCollection.AddSingleton(item); _waitFreeze.Add( go.AddComponent()); _entitiesService.Register(entity); _inRendering.Add(item.Id); _quadtree.Insert(item.Id,position.xz); return go; } public void DisposeItem(IRuntimeItem item) { _items.TryRemove(item.Id); OnItemDisposed?.Invoke(item); DisposeWorldObject(item); } public void DisposeWorldObject(IRuntimeItem item) { _quadtree.Remove(item.Id); _inRendering.Remove(item.Id); if (_itemsGO.TryRemove(item.Id, out var go)) { Object.Destroy(go); } } public UniTask ReloadAsync() { throw new System.NotImplementedException(); } public static event Action OnItemAddedOrUpdated; public static event Action OnItemDisposed; event Action IManagedItemService.OnItemAddedOrUpdated { add => OnItemAddedOrUpdated += value; remove => OnItemAddedOrUpdated -= value; } event Action IManagedItemService.OnItemDisposed { add => OnItemDisposed += value; remove => OnItemDisposed -= value; } public void Dispose() { _asyncTicker.OnTickAsync -= OnTickAsync; _ticker.Remove(OnTick); _items.Clear(); _itemsGO.Clear(); } } }