Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/Bullet/BulletService.cs
2025-06-24 23:49:13 +08:00

349 lines
13 KiB
C#

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<int,InstanceBullet> _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<InstanceBullet> _newBulletQueue = new();
private readonly Queue<int> _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<ScriptableBulletServiceData>().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<IEntity>(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<string>.Get();
tags.Add("bullet_hit");
if (raycastHit.collider.TryGetComponent<ITag>(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<VFXService>().Spawn(location, tags.ToArray());
if (rigidbody is not null && tags.Contains("Fixed") is false)
{
vfx.SetParentConstraint(rigidbody.transform);
}
else
{
vfx.gameObject.RemoveComponent<ParentConstraint>();
}
AudioSensorService.MakeNoise(raycastHit.point, null, 6, new GunShootNoise());
#endif
tags.Clear();
ListPool<string>.Release(tags);
return true;
async void SpawnVFX()
{
var vfx =_vfxService.GetPrefab(tags).gameObject;
vfx =await _poolService.Spawn<GameObject>(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<ParentConstraint>(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<int, int, bool> OnHit { get; set; }
public void Dispose()
{
_cancellationTokenSource?.Dispose();
_ticker.Remove(OnTick);
_mainTicker.Remove(Update);
}
}
}