BITFALL/Assets/Artists/Scripts/GameMode/NpcSpawnService.cs

191 lines
5.1 KiB
C#
Raw Normal View History

2024-04-13 01:10:30 +08:00
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
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<MyInfo,Node<MyInfo>>
{
public InfoNpcStart Info { get; set; }
public Bounds GetBounds() => new(Info.Position, Vector3.one);
public Node<MyInfo> ParentNode { get; set; }
public void QuadTree_Root_Initialized(IQuadtreeRoot<MyInfo, Node<MyInfo>> 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<MyInfo, Node<MyInfo>> _quadtree = new(default,Vector3.one*2048);
private bool _isBusy;
private readonly ConcurrentDictionary<string,Transform> _prefabs=new();
private readonly Dictionary<int,NpcInfo> actives=new();
private readonly List<int> _removeList = new();
private readonly List<MyInfo> _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<InfoNpcStart>())
{
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) 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<IHealth>(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<NavMeshAgent>(out var agent))
{
agent.enabled = false;
agent.enabled = true;
}
var npcInfo = new NpcInfo
{
Id = spawnCount,
Transform = x,
Renderer = x.GetComponentInChildren<Renderer>(),
Entity = x.GetComponent<Entity>(),
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<GameObject>(arg);
asset.WaitForAsyncComplete();
var prefab = asset.AssetObject.As<GameObject>().transform;
_prefabs.TryAdd(arg, prefab);
return prefab;
}
private void Dispose()
{
NodeQuery.RemoveRegisterListener(typeof(InfoNpcStart), OnNpcInfoRegister);
NodeQuery.RemoveUnregisterListener(typeof(InfoNpcStart), OnNpcInfoUnRegister);
ticker.Remove(OnTick);
}
}
}