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