Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/Entities/NpcFactory.cs
2025-06-24 23:49:13 +08:00

249 lines
9.4 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Animancer;
using BITKit;
using BITKit.Entities;
using BITKit.Mod;
using BITKit.Pool;
using BITKit.StateMachine;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Net.Project.B.AI;
using Net.Project.B.Dialogue;
using Net.Project.B.Health;
using Net.Project.B.Interaction;
using NodeCanvas.DialogueTrees;
using NodeCanvas.Framework;
using Project.B.Animation;
using Project.B.CharacterController;
using UnityEngine;
using UnityEngine.AI;
using Object = UnityEngine.Object;
namespace Project.B.Entities
{
public class NpcFactory:INpcFactory
{
public IReadOnlyDictionary<int, IEntity> Entities { get; set; }
private static readonly ConcurrentDictionary<int, IEntity> _entities=new();
private readonly IServiceProvider _serviceProvider;
private readonly IServiceCollection _serviceCollection;
private readonly IPoolService _poolService;
private readonly IHealthService _healthService;
private readonly IEntitiesService _entitiesService;
private readonly IReadOnlyCollection<IHumanoidAnimationFactory> _humanoidAnimationFactories;
private readonly IReadOnlyCollection<IHumanoidTransitionAnimationFactory> _humanoidTransitionAnimationFactories;
public NpcFactory(IServiceProvider serviceProvider, IServiceCollection serviceCollection, IEntitiesService entitiesService, IHealthService healthService, IPoolService poolService)
{
_serviceProvider = serviceProvider;
_serviceCollection = serviceCollection;
_entitiesService = entitiesService;
_healthService = healthService;
_poolService = poolService;
_humanoidAnimationFactories = _entitiesService.QueryComponents<IHumanoidAnimationFactory>().ToArray();
_humanoidTransitionAnimationFactories = _entitiesService.QueryComponents<IHumanoidTransitionAnimationFactory>().ToArray();
}
public async UniTask<IEntity> CreateAsync(string addressablePath, object obj)
{
var entity = new Entity();
if (obj is not GameObject model || !model)
{
var op = Object.InstantiateAsync(await ModService.LoadAsset<GameObject>(string.IsNullOrEmpty(addressablePath) ? "npc_base" : addressablePath));
await op;
model = op.Result[0];
}
if (_entities.TryGetValue(model.GetInstanceID(), out var currentEntity))
{
return currentEntity;
}
if (model && _entitiesService.Entities.TryGetValue(model.GetInstanceID(), out currentEntity) )
{
_entitiesService.UnRegister(currentEntity);
await UniTask.NextFrame();
if (currentEntity is IDisposable disposable)
{
disposable.Dispose();
}
await UniTask.NextFrame();
entity.Id = model.GetInstanceID();
entity.ServiceCollection.Add(currentEntity.ServiceCollection);
if (currentEntity.ServiceProvider.QueryComponents(out IHealthComponent _) is false)
{
entity.ServiceCollection.AddSingleton<IHealthComponent,HealthComponent > ();
}
}
var idComponent = new IdComponent() { Id = model.GetInstanceID(), Name = "Npc" };
if (model && model.TryGetComponent<IDialogueActor>(out var dialogueActor))
{
idComponent.Name = dialogueActor.name;
}
entity.ServiceCollection.AddSingleton(idComponent);
var ct = model.GetCancellationTokenOnDestroy();
var cts = new CancellationTokenSource();
ct.Register(entity.Dispose);
ct.Register(cts.Cancel);
entity.CancellationToken = ct;
entity.ServiceCollection.Add(_serviceCollection);
entity.ServiceCollection.AddSingleton(_serviceProvider.GetRequiredService<IEntitiesService>());
entity.ServiceCollection.AddSingleton(cts);
entity.ServiceCollection.AddSingleton(model.GetComponent<Transform>());
entity.ServiceCollection.AddSingleton(model.GetComponent<Animator>());
entity.ServiceCollection.AddSingleton(model.GetComponent<AnimancerComponent>());
entity.ServiceCollection.AddSingleton(model.GetComponent<NavMeshAgent>());
entity.ServiceCollection.AddSingleton(model.GetComponent<IBlackboard>());
entity.ServiceCollection.AddSingleton(_healthService);
entity.ServiceCollection.AddSingleton(_serviceProvider.GetRequiredService<IKnockedService>());
entity.ServiceCollection.AddSingleton<NpcAnimancerController>();
entity.ServiceCollection.AddSingleton(_serviceProvider.GetRequiredService<IDialogueService>());
entity.ServiceCollection.AddSingleton<NpcCharacterController>();
entity.ServiceCollection.AddSingleton<ICharacterController>(x =>
x.GetRequiredService<NpcCharacterController>());
entity.ServiceCollection.AddSingleton<ICharacterStateIdle, NpcCharacterStateIdle>();
entity.ServiceCollection.AddSingleton<ICharacterStateWalk, NpcCharacterStateWalk>();
entity.ServiceCollection.AddSingleton<ICharacterStateAnimation, NpcCharacterStateAnimation>();
entity.ServiceCollection.AddSingleton<AIBlackBoard>();
entity.ServiceCollection.AddSingleton<DynamicInteractable>();
entity.ServiceCollection.AddSingleton<IWorldInteractable>(x => x.GetRequiredService<DynamicInteractable>());
if (model.TryGetComponent<ITag>(out var tag))
{
var tags = tag.Tags;
foreach (var animationFactory in _humanoidAnimationFactories)
{
if(animationFactory.Tags.Count is 0)continue;
if (tags.Intersect(animationFactory.Tags).Any())
{
entity.ServiceCollection.AddSingleton(animationFactory);
break;
}
}
foreach (var animationFactory in _humanoidTransitionAnimationFactories)
{
if(animationFactory.Tags.Count is 0)continue;
if (tags.Intersect(animationFactory.Tags).Any())
{
entity.ServiceCollection.AddSingleton(animationFactory);
break;
}
}
}
await OnEntityCreate.UniTaskFunc(addressablePath, entity);
var serviceProvider = entity.ServiceProvider;
await OnEntityCreated.UniTaskFunc(addressablePath, entity);
_entities[entity.Id] = entity;
ct.Register(WaitEntityDispose);
_entitiesService.Register(entity);
WaitEntityDispose();
return entity;
async void OnHealthChanged(int arg2, int arg3)
{
if(ct.IsCancellationRequested)return;
if (arg3 < 0)
{
var myRenderer = model.GetComponentsInChildren<Renderer>().ToDictionary(x => x.name, x=>x);
var ragdoll =await _poolService.Spawn<GameObject>("player_ragdoll");
ragdoll.transform.SetPositionAndRotation(model.transform.position,
model.transform.rotation);
var ragdollRenderer = ragdoll.GetComponentsInChildren<Renderer>().ToDictionary(x => x.name, x=>x);
foreach (var (name,myMeshRenderer) in myRenderer)
{
if (ragdollRenderer.TryGetValue(name, out var ragdollMeshRenderer))
{
ragdollMeshRenderer.material = myMeshRenderer.material;
}
}
model.SetActive(false);
}
else
{
model.SetActive(true);
}
}
async void WaitEntityDispose()
{
using var characterController = serviceProvider.GetRequiredService<NpcCharacterController>();
serviceProvider.GetRequiredService<ICharacterController>();
using var npcAnimancer = serviceProvider.GetRequiredService<NpcAnimancerController>();
characterController.TransitionState<ICharacterStateIdle>();
var healthComponent = serviceProvider.GetRequiredService<IHealthComponent>();
healthComponent.OnHealthChanged += OnHealthChanged;
await ct.WaitUntilCanceled();
_entitiesService.UnRegister(entity);
_entities.TryRemove(entity.Id, out _);
}
}
public event Func<string, IEntity,UniTask> OnEntityCreate;
public event Func<string, IEntity,UniTask> OnEntityCreated;
public void Dispose()
{
}
}
}