BITFALL/Assets/Artists/Scripts/BulletService/BulletService.cs

317 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BITFALL.Bullet;
using UnityEngine;
using BITKit;
using BITKit.Entities;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine.Animations;
using UnityEngine.UIElements;
namespace BITFALL
{
[Serializable]
public record InstanceBullet : SpawnBullet
{
public Vector3 GraphicsPosition;
public Vector3 Velocity;
public Vector3 CurrentPos;
public float CurrentSpeed = 64;
public float ElapsedTime;
}
[Serializable]
public class BulletServiceSingleton : IBulletService
{
public void Spawn(SpawnBullet bullet)=>BulletService.Spawn?.Invoke(bullet);
}
public class BulletService : MonoBehaviour, IBulletService
{
public static Action<SpawnBullet> Spawn;
[Header(Constant.Header.Settings)]
[SerializeField] private LayerMask layerMask;
[SerializeField] private Material material;
[SerializeField] private Optional<float> bulletLerpSpeed;
[SerializeField] private Optional<AnimationCurve> bulletScaleCurve;
[Header(Constant.Header.Prefabs)]
[SerializeField] private Mesh rectangleMesh;
[SerializeField] private Mesh quadMesh;
[Header(Constant.Header.Providers)]
[SerializeField, SerializeReference, SubclassSelector]
private IEntitiesService entitiesService;
[SerializeField, SerializeReference, SubclassSelector]
private INetProvider netProvider;
[SerializeField, SerializeReference, SubclassSelector]
private IDamageService damageService;
[Header(Constant.Header.InternalVariables)]
private readonly List<InstanceBullet> instances = new();
private readonly RaycastHit[] _raycastHits = new RaycastHit[16];
private const float dragCoefficient = 0.045f; // 子弹的阻力系数
private const float bulletMass = 0.015f; // 子弹的质量,单位:千克
private readonly Queue<(Vector3 Position,Vector3 Forward,Quaternion StartRotation,float ElaspedTime,Vector3 StartPosition)> DrawQueue=new();
private readonly Queue<InstanceBullet> _newBulletQueue = new();
private readonly Queue<InstanceBullet> _removeBulletQueue = new();
private void Start()
{
Spawn += SpawnBullet;
}
private void OnDestroy()
{
Spawn -= SpawnBullet;
}
private void Update()
{
var camera = Camera.main;
if (!camera) return;
var cameraTransform = camera.transform;
//var rot = camera.transform.rotation * Quaternion.Euler(0, 180, 0);
foreach (var bullet in instances.ToArray())
{
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));
}
while (DrawQueue.TryDequeue(out var queue))
{
var direction = camera.transform.position - queue.Position;
var rot = Quaternion.LookRotation(queue.Position + queue.Forward, direction);
// 获取目标方向
var mesh = rectangleMesh;
//if (MathV.InFovRange(cameraTransform.position, cameraTransform.forward, bullet.CurrentPos,8))
if (Vector3.Angle(cameraTransform.forward, queue.Forward) < 8)
{
if (Vector3.Distance(queue.StartPosition, queue.Position) < 3.2f)
continue;
mesh = quadMesh;
rot = cameraTransform.rotation * Quaternion.Euler(0, 180, 0);
}
const int count = 1;
var matrix = Matrix4x4.TRS(
queue.Position,
rot,
Vector3.one *
Mathf.Lerp(
0,1,Vector3.Distance(queue.StartPosition,queue.Position)
)
+ Vector3.one *
(bulletScaleCurve.Allow ? bulletScaleCurve.Value.Evaluate(queue.ElaspedTime) : 0)
);
Graphics.DrawMesh(mesh, matrix, material,
LayerMask.NameToLayer("TransparentFX"));
}
}
private void FixedUpdate()
{
foreach (var bullet in instances)
{
if (destroyCancellationToken.IsCancellationRequested) return;
var size = Physics.RaycastNonAlloc(bullet.CurrentPos, bullet.Velocity, _raycastHits, Vector3.Distance(default,bullet.Velocity) * Time.fixedDeltaTime, layerMask);
var validHit = false;
RaycastHit hit=default;
foreach (var raycastHit in _raycastHits.Take(size).OrderBy(x => Vector3.Distance(bullet.Position, x.point)))
{
if (!IsValidHit(raycastHit, bullet,out hit))
continue;
validHit = true;
break;
}
if (validHit || bullet.CurrentSpeed <= 0 || bullet.Velocity.sqrMagnitude <= 0.01f ||
bullet.ElapsedTime >= 8)
{
_removeBulletQueue.Enqueue(bullet);
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));
}
//pool.Return(bullet.model);
}
else
{
// // 计算子弹的下坠距离
// float distance =Mathf.Clamp(
// 0.5f * Physics.gravity.y * bullet.ElapsedTime * bullet.ElapsedTime,
// Physics.gravity.y
// ,0
// ) ;
//
// bullet.currentSpeed -= bullet.startSpeed * Time.fixedDeltaTime;
// bullet.currentPos += (Vector3)bullet.forward * (bullet.currentSpeed * Time.fixedDeltaTime);
// bullet.currentPos += Vector3.up * distance;
// bullet.model.transform.position = bullet.currentPos;
//
// bullet.ElapsedTime += Time.fixedDeltaTime;
// 计算重力下坠
bullet.ElapsedTime+=Time.fixedDeltaTime;
bullet.Velocity+= Physics.gravity * (Time.fixedDeltaTime * bullet.ElapsedTime);
// // 计算空气阻力
// float airDragForce = 0.5f * dragCoefficient * 1.225f * bullet.Velocity.magnitude * bullet.Velocity.magnitude;
//
// if (float.IsNaN(airDragForce))
// {
// bullet.Velocity=Vector3.zero;
// continue;
// }
//
// Vector3 airDragDirection = -bullet.Velocity.normalized;
// Vector3 airDrag = airDragDirection * airDragForce / bulletMass;
var newVelocity =bullet.Velocity * Time.fixedDeltaTime;
bullet.CurrentPos+=newVelocity;
float CalculateElevationAngle(float distance)
{
float g = Mathf.Abs(Physics.gravity.y);
float angle = 0.5f * Mathf.Asin(distance * g / (bullet.StartSpeed * bullet.StartSpeed));
return angle * Mathf.Rad2Deg;
}
}
}
while (_removeBulletQueue.TryDequeue(out var removeBullet))
{
instances.Remove(removeBullet);
}
while (_newBulletQueue.TryDequeue(out var newBullet))
{
instances.Add(newBullet);
}
}
private async void RpcSpawnBullet(SpawnBullet x)
{
await UniTask.SwitchToMainThread();
InstanceBullet bullet = new()
{
Initiator = x.Initiator,
CurrentPos = x.Position,
StartSpeed = x.StartSpeed,
Position = x.Position,
GraphicsPosition=x.Position,
Rotation = x.Rotation,
Forward = x.Forward,
InitialDamage = x.InitialDamage,
InitialForce = x.InitialForce,
Velocity =x.Forward * x.StartSpeed,
Token = x.Token
};
// InstanceBullet bullet = x with
// {
//
// };
_newBulletQueue.Enqueue(bullet);
//instances.Add(bullet);
}
private void SpawnBullet(SpawnBullet x)
{
RpcSpawnBullet(x);
}
private bool IsValidHit(RaycastHit raycastHit, InstanceBullet bullet,out RaycastHit hit)
{
hit = default;
if (layerMask.Includes(raycastHit.collider.gameObject.layer) is false) return false;
raycastHit.collider.TryGetComponentAny<IPhysicsInfo>(out var physicsInfo);
var force = (raycastHit.point - (Vector3)bullet.Position).normalized * (physicsInfo?.AddForceMultiple ?? 64);
if (raycastHit.collider.TryGetComponent<IDamagable>(out var damagable))
{
if (damagable.UnityEntity?.Id == bullet.Initiator) return false;
if (layerMask.Includes(raycastHit.collider.gameObject.layer) is false) return false;
var msg = new DamageMessage()
{
Initiator = entitiesService.Get(bullet.Initiator) as Entity,
Target = damagable.UnityEntity,
Hit = damagable,
Damage = bullet.InitialDamage,
};
if (damagable is IEntityComponent)
{
damageService.Execute(msg);
}
else
{
damagable.GiveDamage(msg);
}
}
var _rigidbody = (raycastHit.rigidbody,damagable?.Rigidbody) switch
{
(null, null) => null,
(null, not null) => damagable?.Rigidbody,
(not null, null) => raycastHit.rigidbody,
(not null, not null) => damagable?.Rigidbody,
};
if (_rigidbody && _rigidbody.gameObject.layer is not 0)
{
_rigidbody.AddForceAtPositionAsync(force, raycastHit.point, ForceMode.Impulse).Forget();
}
List<string> tags = new()
{
"BulletHit",
};
if (raycastHit.collider.TryGetComponent<ITag>(out var _tag))
{
tags.AddRange(_tag.GetTags());
}
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>();
}
hit = raycastHit;
return true;
}
void IBulletService.Spawn(SpawnBullet bullet) => SpawnBullet(bullet);
}
}