using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using BITKit; using BITKit.Entities; using BITKit.SubSystems.Quest; using Cysharp.Threading.Tasks; using kcp2k; using Quadtree; using Quadtree.Items; using UnityEngine; using UnityEngine.AI; using YooAsset; using Random = UnityEngine.Random; namespace BITFALL.GameMode { public class NpcSpawnService : MonoBehaviour { private class MyInfo:IItem> { public int Id { get; } = BITApp.Count; public InfoNpcStart Info { get; set; } public Bounds GetBounds() => new(Info.Position, Vector3.one); public Node ParentNode { get; set; } public void QuadTree_Root_Initialized(IQuadtreeRoot> root) { } } private struct NpcInfo { public int Id; public Transform Transform; public Renderer Renderer; public string Name; public IHealth Health; public Entity Entity; } [SerializeReference, SubclassSelector] private ITicker ticker; [SerializeReference, SubclassSelector] private ITicker clearRepeatTick; [SerializeField] private Vector3 spawnSize = Vector3.one; [SerializeField, ReadOnly] private int spawnCount; [SerializeField, ReadOnly] private int tickCount; private readonly QuadtreeRoot> _quadtree = new(default,Vector3.one*2048); private bool _isBusy; private readonly ConcurrentDictionary _prefabs=new(); private readonly Dictionary actives=new(); private readonly List _removeList = new(); private readonly List _myInfos = new(); private readonly HashSet _usedNodes = new(); private void Start() { ticker.Add(OnTick); clearRepeatTick?.Add(ClearRepeat); NodeQuery.AddRegisterListener(typeof(InfoNpcStart), OnNpcInfoRegister); NodeQuery.AddUnregisterListener(typeof(InfoNpcStart), OnNpcInfoUnRegister); destroyCancellationToken.Register(Dispose); foreach (var x in NodeQuery.Query()) { OnNpcInfoRegister(x); } } private void Dispose() { clearRepeatTick?.Remove(ClearRepeat); NodeQuery.RemoveRegisterListener(typeof(InfoNpcStart), OnNpcInfoRegister); NodeQuery.RemoveUnregisterListener(typeof(InfoNpcStart), OnNpcInfoUnRegister); ticker.Remove(OnTick); } private void ClearRepeat(float obj) { _usedNodes.Clear(); } private void OnNpcInfoRegister(object obj) { _quadtree.Insert(new MyInfo{Info = (InfoNpcStart) obj}); } private void OnNpcInfoUnRegister(object obj) { _quadtree.Remove(new MyInfo{Info = (InfoNpcStart) obj}); } private async void OnTick(float obj) { try { if (_isBusy || enabled is false) return; tickCount++; _isBusy = true; var cameraPosition = Camera.main.transform.position; await UniTask.SwitchToThreadPool(); _myInfos.Clear(); float ByDistance(MyInfo y) { return Vector3.Distance(y.GetBounds().center, cameraPosition); } foreach (var x in _quadtree.Find(new Bounds(cameraPosition, spawnSize)).OrderBy(ByDistance)) { if (clearRepeatTick is not null) { if (_usedNodes.Add(x.Id) is false) continue; } var distance = Vector3.Distance(cameraPosition, x.Info.Position); if (8 > distance) continue; if( 32 < distance) continue; if( x.Info.Position.y - cameraPosition.y > 3) continue; _myInfos.Add(x); } await UniTask.SwitchToMainThread(); if (destroyCancellationToken.IsCancellationRequested) return; foreach (var (id, info) in actives) { if (!info.Transform || info.Health.IsAlive is false) { _removeList.Add(id); continue; } var infoPos = info.Transform.position; switch (Vector3.Distance(infoPos, cameraPosition), info.Renderer.isVisible) { case (> 64, _): case (> 32, false): //info.Health.HealthPoint = -1; _removeList.Add(id); break; default: if (Mathf.Abs(infoPos.y - cameraPosition.y) > 6) { _removeList.Add(id); } break; } } foreach (var x in _removeList) { var info = actives[x]; if (info.Transform) { if (info.Transform.TryGetComponent(out var pool)) { pool.EnabledHandle.RemoveElement(this); } } actives.Remove(x); } _removeList.Clear(); foreach (var info in _myInfos) { var prefab = _prefabs.GetOrAdd(info.Info.Name, OnAdd); var prefabName = prefab.name; if (destroyCancellationToken.IsCancellationRequested) return; if (PoolService.TryGet(prefab, out var x) is false) { _isBusy = false; return; } spawnCount++; x.TryGetComponent(out var health); var pos = info.Info.Position; pos.x += Random.Range(-1, 1); pos.z += Random.Range(-1, 1); health.HealthPoint = health.MaxHealthPoint; x.SetPositionAndRotation(pos, info.Info.Rotation); if (x.TryGetComponent(out var agent)) { if (agent.enabled) { agent.enabled = false; agent.enabled = true; agent.SetDestination(pos); agent.nextPosition = pos; } } var npcInfo = new NpcInfo { Id = spawnCount, Transform = x, Renderer = x.GetComponentInChildren(true), Entity = x.GetComponent(), Name = prefabName, Health = health, }; actives.Add(npcInfo.Id, npcInfo); health.OnSetAlive += OnSetAlive; if (x.TryGetComponent(out var pool)) { pool.EnabledHandle.AddElement(this); } continue; void OnSetAlive(bool alive) { if(alive)return; health.OnSetAlive -= OnSetAlive; actives.TryRemove(npcInfo.Id); if (x.TryGetComponent(out var o)) { o.EnabledHandle.RemoveElement(this); } } } _isBusy = false; } catch (Exception e) { BIT4Log.LogException(e); _isBusy = false; } } private Transform OnAdd(string arg) { var asset = YooAssets.LoadAssetAsync(arg); asset.WaitForAsyncComplete(); var prefab = asset.AssetObject.As().transform; _prefabs.TryAdd(arg, prefab); return prefab; } private void Reset() { foreach (var x in actives.Values.ToArray()) { x.Health.HealthPoint = -1; } } } }