Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/Interaction/PlayerInteractionController.cs

350 lines
13 KiB
C#
Raw Normal View History

2025-06-24 23:49:13 +08:00
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BITKit;
using BITKit.Entities;
using BITKit.UX.Hotkey;
using Lightbug.CharacterControllerPro.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Net.Project.B.Health;
using Net.Project.B.Mark;
using NodeCanvas.Framework;
using Project.B.CharacterController;
using Project.B.Player;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Interactions;
using Object = UnityEngine.Object;
namespace Net.Project.B.Interaction
{
public class PlayerInteractionController:IDisposable,IHotkeyCollection
{
private Transform CameraTransform => _cameraTransform ? _cameraTransform : _cameraTransform = Camera.main!.transform;
private Transform _cameraTransform;
private readonly Transform _transform;
private readonly IMarkService _markService;
private readonly IEntitiesService _entitiesService;
private readonly IKnockedService _knockedService;
private readonly IEntity _entity;
private readonly ILogger<PlayerInteractionController> _logger;
private readonly IWorldInteractionService _interactionService;
private readonly InputActionGroup _inputActionGroup;
private readonly IBlackboard _blackBoard;
private readonly IMainTicker _ticker;
private readonly IFixedTicker _fixedTicker;
private readonly ICharacterController _characterController;
private readonly CharacterActor _characterActor;
private readonly LayerMask _interactionLayer;
private IWorldInteractable _currentInteractable;
private IWorldInteractable _triggerInteractable;
private readonly ConcurrentDictionary<int,IWorldInteractable> _interactions = new();
private readonly ConcurrentDictionary<int, IHotkeyProvider> _hotkeyProviders = new();
public IEnumerable<IHotkeyProvider> Hotkeys => _hotkeyProviders.Values;
private bool AllowInteraction => _inputActionGroup.allowInput.Allow &&
_characterController.CurrentState is not null or ICharacterSeating;
private bool _markNextFrame;
private readonly GameObject _myMark;
private readonly IEntity _markEntity;
public PlayerInteractionController(IBlackboard blackBoard, IMainTicker ticker, IWorldInteractionService interactionService, InputActionGroup inputActionGroup, IPlayerKeyMap<InputAction> playerKeyMap, ICharacterController characterController, CharacterActor characterActor, ILogger<PlayerInteractionController> logger, IEntity entity, IKnockedService knockedService, IFixedTicker fixedTicker, IEntitiesService entitiesService, IMarkService markService, Transform transform)
{
_blackBoard = blackBoard;
_ticker = ticker;
_interactionService = interactionService;
_inputActionGroup = inputActionGroup;
_characterController = characterController;
_characterActor = characterActor;
_logger = logger;
_entity = entity;
_knockedService = knockedService;
_fixedTicker = fixedTicker;
_entitiesService = entitiesService;
_markService = markService;
_transform = transform;
_fixedTicker.Add(OnTick);
_interactionLayer = blackBoard.GetVariable<LayerMask>("layer_interaction").GetValue();
inputActionGroup.RegisterCallback(playerKeyMap.InteractiveKey, OnInteraction);
inputActionGroup.RegisterCallback(playerKeyMap.MarkKey, OnMark);
_myMark = new GameObject()
{
hideFlags = HideFlags.HideAndDontSave
};
_markEntity = new Entity();
_markEntity.ServiceCollection.AddSingleton(_myMark.transform);
_markEntity.ServiceCollection.AddSingleton(_myMark);
_markEntity.ServiceCollection.AddSingleton<IMarkComponent,PositionMark>();
_markEntity.ServiceCollection.AddSingleton<PositionMark>();
_markEntity.ServiceCollection.AddSingleton<OwnedByLocalPlayer>();
_entitiesService.Register(_markEntity);
}
private void OnMark(InputAction.CallbackContext obj)
{
if(BITAppForUnity.AllowCursor.Allow)return;
if (obj.JustPressed())
{
_markNextFrame = true;
}
}
private void OnInteraction(InputAction.CallbackContext obj)
{
var interactable = _currentInteractable?? _triggerInteractable;
if(interactable is null)return;
switch (obj)
{
case { interaction: PressInteraction, performed: true }:
{
_interactionService.Interaction(_entity, interactable);
}
break;
case { interaction: PressInteraction, canceled: true }:
{
_interactionService.Interaction(_entity, interactable,WorldInteractionProcess.Cancel);
}
break;
}
}
private void OnTick(float obj)
{
try
{
if (_markService.InMarking.Contains(_markEntity.Id))
{
if (Vector3.Distance(_transform.position, _myMark.transform.position) < 1)
{
_markService.CancelMark(_markEntity.Id);
}
}
var releaseTrigger = true;
if (AllowInteraction)
{
foreach (var trigger in _characterActor.Triggers)
{
if (!trigger.collider3D)
{
continue;
}
var id = trigger.collider3D.gameObject.GetInstanceID();
if (_entitiesService.TryGetEntity(id, out var triggerEntity) is false) continue;
if (triggerEntity.ServiceProvider.QueryComponents(
out IWorldInteractable worldInteractable) is false) continue;
worldInteractable.WorldObject = trigger.gameObject;
worldInteractable.Id = id;
if (_currentInteractable == worldInteractable)
{
releaseTrigger = false;
continue;
}
_triggerInteractable = worldInteractable;
_interactionService.Interaction(_entity, _triggerInteractable, WorldInteractionProcess.Hover);
return;
}
}
if (releaseTrigger || AllowInteraction is false)
{
if (_triggerInteractable is not null)
{
_interactionService.Interaction(_entity, _triggerInteractable, WorldInteractionProcess.None);
_triggerInteractable = null;
}
}
if (AllowInteraction is false)
{
DisposeCurrentInteractable();
return;
}
if (Physics.Raycast(CameraTransform.position, CameraTransform.forward, out var hit, 512,
_interactionLayer) is false)
{
DisposeCurrentInteractable();
return;
}
if (_markNextFrame && hit.transform)
{
var id = hit.transform.gameObject.GetInstanceID();
var colliderId = hit.collider.gameObject.GetInstanceID();
if (_entitiesService.Entities.ContainsKey(id))
{
}else if (_entitiesService.Entities.ContainsKey(colliderId))
{
id = colliderId;
}else if (hit.collider.GetComponentInParent<IEntity>() is {} parentEntity)
{
id = parentEntity.Id;
}
else
{
_myMark.transform.position = hit.point;
if (hit.rigidbody)
{
_myMark.transform.SetParentConstraint(hit.transform);
}
else if(_myMark.transform.TryGetComponent<ParentConstraint>(out var parentConstraint))
{
Object.Destroy(parentConstraint);
_myMark.transform.position = hit.point;
}
id = _markEntity.Id;
if (_markService.InMarking.Contains(id))
{
id = -9999;
}
}
if (id != _characterActor.gameObject.GetInstanceID())
{
if (_markService.InMarking.Contains(id))
{
_markService.CancelMark(id);
}
else
{
_markService.Mark(id);
}
}
}
if (_knockedService.Knocked.Contains(_entity.Id))
{
DisposeCurrentInteractable();
return;
}
if (Vector3.Distance(_characterController.Center, hit.point) > 2)
{
DisposeCurrentInteractable();
return;
}
IWorldInteractable newInteractable = null;
try
{
if (_entitiesService.TryGetEntity(hit.transform.gameObject.GetInstanceID(), out var hitEntity))
{
Check(hitEntity);
}
if (_entitiesService.TryGetEntity(hit.collider.gameObject.GetInstanceID(), out hitEntity))
{
Check(hitEntity);
}
if (hit.transform.GetComponentInParent<IEntity>() is { } e)
{
Check(e);
}
DisposeCurrentInteractable();
return;
void Check(IEntity entity)
{
if (!entity.ServiceProvider.QueryComponents(out IWorldInteractable interactable,
out GameObject gameObject)) return;
if(interactable.Enabled is false)return;
newInteractable = interactable;
newInteractable.WorldObject = gameObject;
newInteractable.Id = hit.transform.gameObject.GetInstanceID();
throw new OperationCanceledException();
}
}
catch (OperationCanceledException)
{
}
if (_currentInteractable == newInteractable)
{
return;
}
DisposeCurrentInteractable();
_interactionService.Interaction(_entity, newInteractable, WorldInteractionProcess.Hover);
_currentInteractable = newInteractable;
}
finally
{
_markNextFrame = false;
}
}
public bool IsInteractable(Transform transform, out IWorldInteractable interactable)
{
interactable = null;
return false;
}
public void Dispose()
{
_fixedTicker.Remove(OnTick);
_entitiesService.UnRegister(_markEntity);
}
public void DisposeCurrentInteractable()
{
if (_currentInteractable is not null)
{
_hotkeyProviders.TryRemove(_currentInteractable.Id);
_interactions.TryRemove(_currentInteractable.Id);
_interactionService.Interaction(_entity, _currentInteractable, WorldInteractionProcess.None);
_currentInteractable = null;
}
}
public void Register(IHotkeyProvider hotkey)
{
throw new NotImplementedException();
}
public void UnRegister(IHotkeyProvider hotkey)
{
throw new NotImplementedException();
}
}
}