295 lines
11 KiB
C#
295 lines
11 KiB
C#
![]() |
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|