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 Quadtree; using Quadtree.Items; using UnityEngine; using UnityEngine.AI; using YooAsset; using Random = UnityEngine.Random; namespace BITFALL.GameMode { public class NpcSpawnService : MonoBehaviour { private struct MyInfo:IItem> { 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; [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 void Start() { ticker.Add(OnTick); NodeQuery.AddRegisterListener(typeof(InfoNpcStart), OnNpcInfoRegister); NodeQuery.AddUnregisterListener(typeof(InfoNpcStart), OnNpcInfoUnRegister); destroyCancellationToken.Register(Dispose); foreach (var x in NodeQuery.Query()) { OnNpcInfoRegister(x); } } 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(); foreach (var x in _quadtree.Find(new Bounds(cameraPosition, spawnSize))) { if (Vector3.Distance(cameraPosition, x.Info.Position) < 8) continue; _myInfos.Add(x); } await UniTask.SwitchToMainThread(); if (destroyCancellationToken.IsCancellationRequested) return; foreach (var (id, info) in actives) { if (!info.Transform) { _removeList.Add(id); continue; } switch (Vector3.Distance(info.Transform.position, cameraPosition), info.Renderer.isVisible) { case (> 64, _): case (> 32, false): //info.Health.HealthPoint = -1; _removeList.Add(id); break; } } foreach (var x in _removeList) { var info = actives[x]; if(!info.Transform)continue; info.Health.HealthPoint = -1; } _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)) { agent.enabled = false; agent.enabled = true; } var npcInfo = new NpcInfo { Id = spawnCount, Transform = x, Renderer = x.GetComponentInChildren(), Entity = x.GetComponent(), Name = prefabName, Health = health }; health.OnSetAlive += OnSetAlive; actives.Add(npcInfo.Id, npcInfo); continue; async void OnSetAlive(bool alive) { if (alive) return; health.OnSetAlive -= OnSetAlive; actives.TryRemove(npcInfo.Id); if (destroyCancellationToken.IsCancellationRequested) return; if (npcInfo.Entity.destroyCancellationToken.IsCancellationRequested) return; await UniTask.Delay(3000); PoolService.Release(npcInfo.Name, npcInfo.Transform); } } _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 Dispose() { NodeQuery.RemoveRegisterListener(typeof(InfoNpcStart), OnNpcInfoRegister); NodeQuery.RemoveUnregisterListener(typeof(InfoNpcStart), OnNpcInfoUnRegister); ticker.Remove(OnTick); } private void Reset() { foreach (var x in actives.Values.ToArray()) { x.Health.HealthPoint = -1; } } } }