Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/Item/ManagedItemService.cs
2025-06-24 23:49:13 +08:00

333 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}
}