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

248 lines
6.4 KiB
C#

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<MyInfo,Node<MyInfo>>
{
public int Id { get; } = BITApp.Count;
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;
[SerializeReference, SubclassSelector] private ITicker clearRepeatTick;
[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 readonly HashSet<int> _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<InfoNpcStart>())
{
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<IPoolObject>(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<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))
{
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<Renderer>(true),
Entity = x.GetComponent<Entity>(),
Name = prefabName,
Health = health,
};
actives.Add(npcInfo.Id, npcInfo);
health.OnSetAlive += OnSetAlive;
if (x.TryGetComponent<IPoolObject>(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<IPoolObject>(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<GameObject>(arg);
asset.WaitForAsyncComplete();
var prefab = asset.AssetObject.As<GameObject>().transform;
_prefabs.TryAdd(arg, prefab);
return prefab;
}
private void Reset()
{
foreach (var x in actives.Values.ToArray())
{
x.Health.HealthPoint = -1;
}
}
}
}