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 Spawn; [Header(Constant.Header.Settings)] [SerializeField] private LayerMask layerMask; [SerializeField] private Material material; [SerializeField] private Optional bulletLerpSpeed; [SerializeField] private Optional 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 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 _newBulletQueue = new(); private readonly Queue _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(out var physicsInfo); var force = (raycastHit.point - (Vector3)bullet.Position).normalized * (physicsInfo?.AddForceMultiple ?? 64); if (raycastHit.collider.TryGetComponent(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() { 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 tags = new() { "BulletHit", }; if (raycastHit.collider.TryGetComponent(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().Spawn(location, tags.ToArray()); if (_rigidbody is not null && tags.Contains("Fixed") is false) { vfx.SetParentConstraint(_rigidbody.transform); } else { vfx.gameObject.RemoveComponent(); } hit = raycastHit; return true; } void IBulletService.Spawn(SpawnBullet bullet) => SpawnBullet(bullet); } }