using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using BITKit; using BITKit.Tween; using BITKit.UX; using Cysharp.Threading.Tasks; using Net.Project.B.Damage; using Project.B.Entities; using Unity.Mathematics; using UnityEngine; using UnityEngine.UIElements; namespace Net.Project.B.UX { public class UXIndicator : UIToolkitSubPanel , IDisposable, IUXIndicator where TPanel : IUXPanel { private struct IndicatorData { public int Initiator; public VisualElement VisualElement; public Vector3 Position; public CancellationTokenSource CancellationTokenSource; } private readonly IMainTicker _mainTicker; private readonly IDamageService _damageService; private readonly IPlayerFactory _playerFactory; private readonly ConcurrentDictionary _indicators = new(); [UXBindPath("indicator-container")] private VisualElement _indicatorContainer; private VisualTreeAsset _template; private readonly Transform _cameraTransform; public UXIndicator(IServiceProvider serviceProvider, IDamageService damageService, IPlayerFactory playerFactory, IMainTicker mainTicker) : base(serviceProvider) { _damageService = damageService; _playerFactory = playerFactory; _mainTicker = mainTicker; _damageService.OnDamaged += OnDamaged; _mainTicker.Add(OnMainTick); _cameraTransform = Camera.main!.transform; } private void OnMainTick(float obj) { foreach (var indicatorData in _indicators.Values) { UpdateIndicator(indicatorData); } } private async void OnDamaged(IDamageReport obj) { if (_playerFactory.Entities.ContainsKey(obj.Target) is false || obj.Target == obj.Initiator) return; if (_indicators.TryGetValue(obj.Initiator, out var indicatorData)) { indicatorData.Position = obj.ContactPosition; indicatorData.CancellationTokenSource.Cancel(); indicatorData.CancellationTokenSource = new CancellationTokenSource(); } else { indicatorData = new IndicatorData() { Initiator = obj.Initiator, VisualElement = _indicatorContainer.Create(_template), Position = obj.ContactPosition, CancellationTokenSource = new(), }; } _indicators[obj.Initiator] = indicatorData; try { await BITween.CreateSequence() .Append(UniTask.Delay(1000)) .Append(BITween.Lerp(indicatorData.VisualElement.SetOpacity, 1f, 0f, 1f, math.lerp, indicatorData.CancellationTokenSource.Token)) .Play() ; _indicators.TryRemove(obj.Initiator, out _); indicatorData.VisualElement.RemoveFromHierarchy(); } catch (OperationCanceledException) { } } private void UpdateIndicator(IndicatorData indicatorData){ var rhs = indicatorData.Position - _cameraTransform.position; var forward =Vector3.ProjectOnPlane( _cameraTransform.forward,Vector3.up); //Convert angle into screen space rhs.y = indicatorData.Position.y; rhs.Normalize(); //Get the angle between two positions. var angle = Vector3.Angle(rhs, forward); //Calculate the perpendicular of both vectors //More information about this calculation: https://unity3d.com/es/learn/tutorials/modules/beginner/scripting/vector-maths-dot-cross-products?playlist=17117 var perpendicular = Vector3.Cross(forward, rhs); //Calculate magnitude between two vectors var dot = -Vector3.Dot(perpendicular, Vector3.up); //get the horizontal angle in direction of target / sender. angle = AngleCircumference(dot, angle); //Apply the horizontal rotation to the indicator. indicatorData.VisualElement.transform.rotation = Quaternion.Euler(new(0,0,-angle)); return; float AngleCircumference(float dot, float angle) { const float circumference = 360f; var ac = angle - 10; if (dot < 0) { ac = circumference - angle; } return ac; } } protected override async UniTask OnInitiatedAsync() { await base.OnInitiatedAsync(); _template = _indicatorContainer.Q().templateSource; _indicatorContainer.Clear(); } public void Dispose() { foreach (var indicatorData in _indicators.Values) { indicatorData.CancellationTokenSource.Cancel(); } _mainTicker.Remove(OnMainTick); _damageService.OnDamaged -= OnDamaged; } } }