333 lines
11 KiB
C#
333 lines
11 KiB
C#
![]() |
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();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|