349 lines
13 KiB
C#
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);
|
||
|
}
|
||
|
}
|
||
|
}
|