248 lines
6.4 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|