Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/Item/ManagedItemService.cs

333 lines
11 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 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<Rigidbody> _waitFreeze = new();
private readonly HashSet<Rigidbody> _isFreeze = new();
private readonly Quadtree _quadtree = new(default, new float2(2048, 2048));
private readonly HashSet<int> _inRendering=new();
private readonly Queue<int> _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<UnityItemNode>() is not {} itemNode)return;
var gameObject = entity.ServiceProvider.GetService<GameObject>();
_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<ScriptableItem>(itemNode.ItemPath);
var newItem = asset.CreateRuntimeItem();
newItem.Id = gameObject.GetInstanceID();
AddOrUpdateItem(newItem);
if (gameObject.transform.TryGetComponentAny<SpriteRenderer>(out var spriteRenderer))
{
_spriteRenderers.TryAdd(entity.Id, spriteRenderer);
}
}
public IReadOnlyDictionary<int, IRuntimeItem> Items => _items;
private static readonly ConcurrentDictionary<int,IRuntimeItem> _items = new();
private static readonly ConcurrentDictionary<int,GameObject> _itemsGO = new();
private static readonly ConcurrentDictionary<int, SpriteRenderer> _spriteRenderers = new();
public void AddOrUpdateItem(IRuntimeItem item)
{
_items.Set(item.Id,item);
OnItemAddedOrUpdated?.Invoke(item);
}
public async UniTask<object> InstanceItem(float3 position, quaternion rotation, IRuntimeItem item)
{
await _isBusy;
using var _ = _isBusy.GetHandle();
if(!_initialMaterial)
{
_initialMaterial =await ModService.LoadAsset<Material>("material_sprite_item");
};
var go = new GameObject
{
layer = LayerMask.NameToLayer("Loot")
};
var scriptableItem = await ModService.LoadAsset<ScriptableItem>(DictionaryReferenceScriptableObject.Dictionary[item.ScriptableId]);
if(!go)return null;
_items.TryRemove(item.Id);
item = item.Clone().As<IRuntimeItem>();
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<BoxCollider>();
if (boxCollider == null)
{
boxCollider = go.AddComponent<BoxCollider>();
}
boxCollider.gameObject.layer = LayerMask.NameToLayer("Loot");
if (scriptableItem.Model)
{
var newModel = Object.Instantiate(scriptableItem.Model, go.transform);
foreach (var collider in newModel.GetComponentsInChildren<Collider>())
{
Object.Destroy(collider);
}
// 获取所有可见的 MeshRenderer 组件
var meshRenderers = go.GetComponentsInChildren<MeshRenderer>();
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>();
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<IEntity>(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<Collider>(boxCollider);
entity.ServiceCollection.AddSingleton<IWorldInteractable>(new WorldInteractable()
{
Id = item.Id,
WorldObject = go
});
entity.ServiceCollection.AddSingleton<IRuntimeItem>(item);
_waitFreeze.Add( go.AddComponent<Rigidbody>());
_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<IRuntimeItem> OnItemAddedOrUpdated;
public static event Action<IRuntimeItem> OnItemDisposed;
event Action<IRuntimeItem> IManagedItemService.OnItemAddedOrUpdated
{
add => OnItemAddedOrUpdated += value;
remove => OnItemAddedOrUpdated -= value;
}
event Action<IRuntimeItem> IManagedItemService.OnItemDisposed
{
add => OnItemDisposed += value;
remove => OnItemDisposed -= value;
}
public void Dispose()
{
_asyncTicker.OnTickAsync -= OnTickAsync;
_ticker.Remove(OnTick);
_items.Clear();
_itemsGO.Clear();
}
}
}