Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/Npc/NpcAnimancerController.cs

295 lines
11 KiB
C#
Raw Normal View History

2025-06-24 23:49:13 +08:00
using System;
using System.Threading;
using Animancer;
using BITKit;
using BITKit.Entities;
using BITKit.Mod;
using Cysharp.Threading.Tasks;
using Net.Project.B.Dialogue;
using Net.Project.B.Emoji;
using Net.Project.B.Health;
using NodeCanvas.Framework;
using Project.B.Animation;
using Project.B.CharacterController;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.AI;
using Random = UnityEngine.Random;
namespace Net.Project.B.AI
{
public enum NpcAnimationState
{
}
public class NpcAnimancerController:IDisposable
{
private readonly IHealthComponent _healthComponent;
private readonly IFixedTicker _ticker;
private readonly IBlackboard _blackboard;
private readonly IHealthService _healthService;
private readonly Transform _transform;
private readonly NavMeshAgent _navMeshAgent;
private readonly ICharacterController _characterController;
private readonly IHumanoidAnimationFactory _humanoidAnimationFactory;
private readonly IHumanoidTransitionAnimationFactory _transitionFactory;
private readonly IDialogueService _dialogueService;
private readonly AnimancerComponent _animancerComponent;
private readonly IEntity _entity;
private readonly IWrapper<float2> _idleWrapper;
private readonly IWrapper<float2> _walkWrapper;
private readonly IWrapper<float2> _idleToWalkWrapper;
private readonly IWrapper<float2> _walkToIdleWrapper;
private readonly IWrapper<float2> _idleTurnWrapper;
private readonly IWrapper<float2> _hitAnimation;
public CancellationTokenSource StateChangeCancellationTokenSource { get;private set; }
private Vector3 TargetPosition => _blackboard.GetVariableValue<Vector3>("target_position");
private bool _isDisposed;
public NpcAnimancerController(AnimancerComponent animancerComponent, IDialogueService dialogueService, ICharacterController characterController, IEntity entity, IHumanoidTransitionAnimationFactory transitionFactory, NavMeshAgent navMeshAgent, Transform transform, IFixedTicker ticker, IBlackboard blackboard, IHumanoidAnimationFactory humanoidAnimationFactory, IHealthService healthService, IHealthComponent healthComponent)
{
_animancerComponent = animancerComponent;
_dialogueService = dialogueService;
_characterController = characterController;
_entity = entity;
_transitionFactory = transitionFactory;
_navMeshAgent = navMeshAgent;
_transform = transform;
_ticker = ticker;
_blackboard = blackboard;
_humanoidAnimationFactory = humanoidAnimationFactory;
_healthService = healthService;
_healthComponent = healthComponent;
_dialogueService.OnDialogueStart += OnDialogueStartAsync;
_dialogueService.OnDynamicDialogueStart += OnDialogueStart;
_characterController.OnStateChanged += OnStateChanged;
_idleWrapper = _humanoidAnimationFactory.CreateIdleAnimation();
_walkWrapper = _humanoidAnimationFactory.CreateWalkAnimation();
_idleToWalkWrapper = _transitionFactory.CreateIdleToWalk();
_walkToIdleWrapper = _transitionFactory.CreateWalkToIdle();
_idleTurnWrapper = _transitionFactory.CreateIdleTurn();
_hitAnimation = _humanoidAnimationFactory.CreateHitAnimation();
_ticker.Add(OnTick);
_healthComponent.OnHealthChanged += OnHealthChanged;
}
private CancellationTokenSource _hitCancellationTokenSource;
private async void OnHealthChanged(int prevHp, int newHp)
{
_hitCancellationTokenSource?.Cancel();
_hitCancellationTokenSource = new CancellationTokenSource();
if(newHp>=0)return;
var layer = _animancerComponent.Layers[2];
layer.IsAdditive = true;
try
{
var task = layer.Play(_hitAnimation.Obj as AnimancerState);
task.Time = 0;
_hitAnimation.Value = Random.insideUnitCircle;
await task.WithCancellation(_hitCancellationTokenSource.Token);
if (_animancerComponent)
layer.Stop();
_hitCancellationTokenSource?.Cancel();
}
catch (OperationCanceledException)
{
}
}
private void OnTick(float obj)
{
if(!_transform.gameObject.activeInHierarchy)return;
if (StateChangeCancellationTokenSource is not { IsCancellationRequested: true }) return;
switch (_characterController.CurrentState)
{
case ICharacterStateWalk:
{
if (_navMeshAgent.remainingDistance > _navMeshAgent.stoppingDistance)
{
var corner = _navMeshAgent.path.corners;
if(corner.Length < 2)break;
var position = corner[1];
var dir = _transform.InverseTransformPoint(position)*8;
if (Vector3.Distance(position, _transform.position) > 1)
{
_walkWrapper.Value = new(dir.x, dir.z);
}
else
{
_walkWrapper.Value = new(0, 1);
}
}
}
break;
}
}
private async void OnStateChanged(ICharacterState arg1, ICharacterState arg2)
{
StateChangeCancellationTokenSource?.Cancel();
if(_isDisposed)return;
if(!_animancerComponent)return;
StateChangeCancellationTokenSource = new();
switch (arg1,arg2)
{
case (ICharacterStateWalk, ICharacterStateIdle):
{
try
{
_characterController.AllowMovement.AddDisableElements(this);
if (_walkToIdleWrapper is not null)
{
await _animancerComponent.Play(_walkToIdleWrapper.Obj as AnimancerState, 0.2f).WithCancellation(StateChangeCancellationTokenSource.Token);
}
if (_idleTurnWrapper is not null)
{
var dir = _transform.InverseTransformPoint(TargetPosition).normalized;
_idleTurnWrapper.Value = new (dir.x, dir.z);
await _animancerComponent.Play(_idleTurnWrapper.Obj as AnimancerState, 0.2f).WithCancellation(StateChangeCancellationTokenSource.Token);
}
}
catch (OperationCanceledException)
{
_characterController.AllowMovement.RemoveDisableElements(this);
return;
}
_characterController.AllowMovement.RemoveDisableElements(this);
_animancerComponent.Play(_idleWrapper.Obj as AnimancerState, 0.2f);
}
break;
case (ICharacterStateIdle, ICharacterStateWalk):
{
var corners = _navMeshAgent.path.corners;
if (corners.Length < 2)
{
_walkWrapper.Value = new(0, 1);
_animancerComponent.Play(_walkWrapper.Obj as AnimancerState, 0.2f);
break;
}
var direction = corners[1];
direction = Vector3.ProjectOnPlane(_transform.InverseTransformPoint(direction), Vector3.up).normalized*8
;
_idleToWalkWrapper.Value = new(direction.x, direction.z);
try
{
var walkState = _animancerComponent.Play(_idleToWalkWrapper.Obj as AnimancerState, 0.2f);
walkState.Time = 0.2f;
await walkState.WithCancellation(StateChangeCancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
return;
}
_walkWrapper.Value = new(0, 1);
_animancerComponent.Play(_walkWrapper.Obj as AnimancerState, 0.2f);
}
break;
case (_,ICharacterStateIdle):
{
_animancerComponent.Play(_idleWrapper.Obj as AnimancerState, 0.2f);
}
break;
case (_,ICharacterStateWalk):
{
_animancerComponent.Play(_walkWrapper.Obj as AnimancerState, 0.2f);
_walkWrapper.Value = new(0, 1);
}
break;
}
StateChangeCancellationTokenSource?.Cancel();
}
private void OnDialogueStart(IDialogueData arg)
{
OnDialogueStartAsync(arg).Forget();
}
private async UniTask OnDialogueStartAsync(IDialogueData arg)
{
if (arg.ActorIdentity != _entity.Id) return;
foreach (var scriptableEmoji in await ModService.LoadAssets<ScriptableEmojiCollection>("dialogue"))
{
_characterController.TransitionState<ICharacterStateAnimation>();
var emoji = scriptableEmoji.GetEmojis.Random();
var layer = _animancerComponent.Layers[_animancerComponent.Layers.Count + 1];
await layer.Play(emoji.Clip,0.2f);
if (_animancerComponent)
{
layer.StartFade(0);
_characterController.TransitionState<ICharacterStateIdle>();
}
return;
}
}
public void Dispose()
{
_isDisposed = true;
StateChangeCancellationTokenSource.Cancel();
_ticker.Remove(OnTick);
_dialogueService.OnDialogueStart -= OnDialogueStartAsync;
_dialogueService.OnDynamicDialogueStart -= OnDialogueStart;
_characterController.OnStateChanged -= OnStateChanged;
}
}
}