using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading; using BITFALL.Bullet; using UnityEngine; using BITKit; using BITKit.Entities; using BITKit.Pool; using Cysharp.Threading.Tasks; using Net.BITKit.VFX; using Net.Project.B.Bullet; using Net.Project.B.Damage; using Net.Project.B.Health; using Project.B.Item; using Unity.Mathematics; using UnityEditor; using UnityEngine.Animations; using UnityEngine.Pool; using UnityEngine.UIElements; using Object = UnityEngine.Object; namespace BITFALL { public record InstanceBullet : BulletData { public int Index; public Vector3 GraphicsPosition; public Vector3 Velocity; public Vector3 CurrentPos; public float CurrentSpeed = 64; public float ElapsedTime; } public class BulletService : IBulletService { private readonly IDamageService _damageService; private readonly IHealthService _healthService; private readonly IPoolService _poolService; private readonly VFXService _vfxService; private readonly ScriptableBulletServiceData _bulletServiceData; private readonly IEntitiesService _entitiesService; private readonly IMainTicker _mainTicker; private readonly ITicker _ticker; private readonly ConcurrentDictionary _instances = new(); private readonly RaycastHit[] _raycastHits = new RaycastHit[16]; private readonly Queue<(Vector3 Position,Vector3 Forward,Quaternion StartRotation,float ElaspedTime,Vector3 StartPosition)> _drawQueue=new(); private readonly Queue _newBulletQueue = new(); private readonly Queue _removeBulletQueue = new(); private Camera Camera=>_camera ? _camera : _camera=Camera.main; private Camera _camera; private int _count; private readonly CancellationTokenSource _cancellationTokenSource; public BulletService(IEntitiesService entitiesService, IMainTicker mainTicker, ITicker ticker, IPoolService poolService, VFXService vfxService, IHealthService healthService, CancellationTokenSource cancellationTokenSource, IDamageService damageService) { _entitiesService = entitiesService; _bulletServiceData = entitiesService.QueryComponents().ToArray().First(); _mainTicker = mainTicker; _ticker = ticker; _poolService = poolService; _vfxService = vfxService; _healthService = healthService; _cancellationTokenSource = cancellationTokenSource; _damageService = damageService; _mainTicker.Add(Update); ticker.Add(OnTick); _cancellationTokenSource.Token.Register(Dispose); } private CancellationToken CancellationToken => _cancellationTokenSource.Token; private void Update(float detla) { if (!Camera) return; var cameraTransform = _camera.transform; foreach (var bullet in _instances.Values) { if (bullet.ElapsedTime < 0.04f) continue; bullet.GraphicsPosition = Vector3.MoveTowards(bullet.GraphicsPosition, bullet.CurrentPos, bullet.StartSpeed*Time.deltaTime); _drawQueue.Enqueue((bullet.GraphicsPosition,bullet.Forward,bullet.Rotation,bullet.ElapsedTime,bullet.Position)); } var quadBulletRotation = cameraTransform.rotation * Quaternion.Euler(0, 180, 0); var cameraPosition = cameraTransform.position; while (_drawQueue.TryDequeue(out var queue)) { var rot = // 获取目标方向 queue.StartRotation; var mesh = _bulletServiceData.RectangleMesh; var toCamera = (cameraPosition - queue.Position).normalized; rot = Quaternion.LookRotation(queue.Forward, toCamera); //if (MathV.InFovRange(cameraTransform.position, cameraTransform.forward, bullet.CurrentPos,8)) if (Quaternion.Angle(Quaternion.LookRotation(cameraTransform.forward),Quaternion.LookRotation( queue.Forward)) < 8) { if (Vector3.Distance(queue.StartPosition, queue.Position) < 3.2f) continue; mesh = _bulletServiceData.QuadMesh; rot = quadBulletRotation; } var matrix = Matrix4x4.TRS( queue.Position, rot, Vector3.one * Mathf.Lerp( 0,1,Vector3.Distance(queue.StartPosition,queue.Position) ) + Vector3.one * ( _bulletServiceData.BulletScaleCurve.Allow ? _bulletServiceData.BulletScaleCurve.Value.Evaluate(queue.ElaspedTime) : 0) ); Graphics.DrawMesh(mesh, matrix, _bulletServiceData.Material, UnityEngine.LayerMask.NameToLayer("TransparentFX")); } } private void OnTick(float delta) { foreach (var bullet in _instances.Values) { if (CancellationToken.IsCancellationRequested) return; var size = Physics.RaycastNonAlloc(bullet.CurrentPos, bullet.Velocity, _raycastHits, Vector3.Distance(default,bullet.Velocity) * Time.fixedDeltaTime,_bulletServiceData.LayerMask); var validHit = false; RaycastHit hit=default; foreach (var raycastHit in _raycastHits.Take(size).OrderBy(KeySelector)) { if (raycastHit.collider.isTrigger) continue; if (!IsReleaseHit(raycastHit, bullet)) continue; validHit = true; break; } if (validHit || bullet.CurrentSpeed <= 0 || bullet.Velocity.sqrMagnitude <= 0.01f || bullet.ElapsedTime >= 8) { _removeBulletQueue.Enqueue(bullet.Index); if (validHit) { _drawQueue.Enqueue((hit.point - bullet.Velocity.normalized * 0.5f, bullet.Forward, bullet.Rotation, bullet.ElapsedTime,bullet.Position)); } else { _drawQueue.Enqueue((bullet.CurrentPos + bullet.Velocity * 0.5f, bullet.Forward, bullet.Rotation, bullet.ElapsedTime,bullet.Position)); } } else { bullet.ElapsedTime+=Time.fixedDeltaTime; bullet.Velocity+= Physics.gravity * (Time.fixedDeltaTime * bullet.ElapsedTime); var newVelocity =bullet.Velocity * Time.fixedDeltaTime; bullet.CurrentPos+=newVelocity; } continue; float KeySelector(RaycastHit x) => Vector3.Distance(bullet.CurrentPos, x.point); } while (_removeBulletQueue.TryDequeue(out var removeBullet)) { _instances.TryRemove(removeBullet); } while (_newBulletQueue.TryDequeue(out var newBullet)) { _instances.TryAdd(newBullet.Index,newBullet); } } private bool IsReleaseHit(RaycastHit raycastHit, InstanceBullet bullet) { //if (_bulletServiceData.LayerMask.Includes(raycastHit.collider.gameObject.layer) is false) return false; #region Damage if( raycastHit.transform.TryGetComponentAny(out var entity)) { }else if (_entitiesService.TryGetEntity(raycastHit.transform.gameObject.GetInstanceID(), out entity)) { } else { } if (entity is not null) { if (entity.Id == bullet.Initiator) return false; foreach (var func in OnHit.CastAsFunc()) { if(func.Invoke(bullet.Initiator,entity.Id) is false) { return false; } } if (_healthService.Healths.ContainsKey(entity.Id)) { _damageService.CreateDamageAsync(bullet.Initiator,entity.Id,bullet.InitialDamage,new ScriptableItemDamage() { ScriptableId = bullet.ScriptableId },bullet.Position).Forget(); } //_healthService.AddHealth(entity.Id, -bullet.InitialDamage, this).Forget(); } #endregion #region Physics var force = (raycastHit.point - (Vector3)bullet.Position).normalized * bullet.InitialForce; var rigidbody = (raycastHit.rigidbody); if (rigidbody && rigidbody.gameObject.layer is not 0) { rigidbody.AddForceAtPosition(force,raycastHit.point,ForceMode.VelocityChange); rigidbody.AddForceAtPosition(force, raycastHit.point, ForceMode.Impulse); } #endregion #region VFX var tags = ListPool.Get(); tags.Add("bullet_hit"); if (raycastHit.collider.TryGetComponent(out var tag)) { tags.AddRange(tag.Tags); } SpawnVFX(); #endregion #if TEST Location location = new() { position = raycastHit.point, forward = tags.Contains("IgnoreRotation") ? bullet.Forward : Quaternion.LookRotation(raycastHit.normal) * Vector3.forward, }; var vfx = DI.Get().Spawn(location, tags.ToArray()); if (rigidbody is not null && tags.Contains("Fixed") is false) { vfx.SetParentConstraint(rigidbody.transform); } else { vfx.gameObject.RemoveComponent(); } AudioSensorService.MakeNoise(raycastHit.point, null, 6, new GunShootNoise()); #endif tags.Clear(); ListPool.Release(tags); return true; async void SpawnVFX() { var vfx =_vfxService.GetPrefab(tags).gameObject; vfx =await _poolService.Spawn(vfx.name, vfx); if(_cancellationTokenSource.IsCancellationRequested)return; vfx.transform.position = raycastHit.point; vfx.transform.rotation = Quaternion.LookRotation(raycastHit.normal); if (raycastHit.rigidbody) { vfx.transform.SetParentConstraint(raycastHit.transform); } else if (vfx.transform.TryGetComponent(out var constraint)) { Object.Destroy(constraint); vfx.transform.position = raycastHit.point; vfx.transform.rotation = Quaternion.LookRotation(raycastHit.normal); } } } public int LayerMask { get => _bulletServiceData.LayerMask; set => throw new NotImplementedException(); } public void Spawn(BulletData bulletData) { var newBullet = new InstanceBullet() { Index = _count++, Position = bulletData.Position, Forward = bulletData.Forward, Rotation = bulletData.Rotation, CurrentPos = bulletData.Position, GraphicsPosition = bulletData.Position, CurrentSpeed = bulletData.StartSpeed, StartSpeed = bulletData.StartSpeed, ElapsedTime = 0, Velocity = bulletData.Forward * bulletData.StartSpeed, InitialDamage = math.abs(bulletData.InitialDamage), MaxDamage = math.abs(bulletData.MaxDamage), Initiator = bulletData.Initiator, ScriptableId = bulletData.ScriptableId, InitialForce = bulletData.InitialForce }; _newBulletQueue.Enqueue(newBullet); } public Func OnHit { get; set; } public void Dispose() { _cancellationTokenSource?.Dispose(); _ticker.Remove(OnTick); _mainTicker.Remove(Update); } } }