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.Pool; 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; private static readonly List Tokens = new(); [Header(Constant.Header.Settings)] [SerializeField] private LayerMask layerMask; [SerializeField] private Material material; [SerializeField] private Optional bulletScaleCurve; [Header(Constant.Header.Prefabs)] [SerializeField] private Mesh rectangleMesh; [SerializeField] private Mesh quadMesh; [Header(Constant.Header.Providers)] [SerializeReference, SubclassSelector] private IEntitiesService entitiesService; [SerializeReference, SubclassSelector] private INetClient netClient; [SerializeReference, SubclassSelector] private INetServer netServer; [SerializeReference, SubclassSelector] private IDamageService damageService; [Header(Constant.Header.InternalVariables)] private readonly List 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 INetProvider clientNetProvider => netClient.Source as INetProvider; private INetProvider serverNetProvider => netServer.Source as INetProvider; private new Camera camera=>_camera ? _camera : _camera=Camera.main; private Camera _camera; private void Start() { Spawn += SpawnBullet; serverNetProvider.AddCommandListener(RpcSpawnBullet); clientNetProvider.AddCommandListener(RpcSpawnBullet); destroyCancellationToken.Register(() => { Spawn -= SpawnBullet; }); } private void Update() { 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 rot = // 获取目标方向 queue.StartRotation; 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); } 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 (!IsReleaseHit(raycastHit, bullet)) 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)); } } else { bullet.ElapsedTime+=Time.fixedDeltaTime; bullet.Velocity+= Physics.gravity * (Time.fixedDeltaTime * bullet.ElapsedTime); var newVelocity =bullet.Velocity * Time.fixedDeltaTime; bullet.CurrentPos+=newVelocity; } } while (_removeBulletQueue.TryDequeue(out var removeBullet)) { instances.Remove(removeBullet); } while (_newBulletQueue.TryDequeue(out var newBullet)) { instances.Add(newBullet); } } private async void RpcSpawnBullet(SpawnBullet x) { if (x.Token is not 0) { if (Tokens.Contains(x.Token)) { return; } Tokens.Add(x.Token); } await UniTask.SwitchToMainThread(); InstanceBullet bullet = new() { Token = x.Token, 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, }; _newBulletQueue.Enqueue(bullet); } private void SpawnBullet(SpawnBullet x) { RpcSpawnBullet(x); if (netClient.IsConnected) { clientNetProvider.ServerCommand(x); clientNetProvider.AllClientCommand(x); } else if(netServer.IsRunningServer) { serverNetProvider.AllClientCommand(x); } } private bool IsReleaseHit(RaycastHit raycastHit, InstanceBullet bullet) { if (layerMask.Includes(raycastHit.collider.gameObject.layer) is false) return false; raycastHit.collider.TryGetComponentAny(out var physicsInfo); var returnTure = true; var force = (raycastHit.point - (Vector3)bullet.Position).normalized * (physicsInfo?.AddForceMultiple ?? 64); if (raycastHit.collider.TryGetComponent(out var damageComponent)) { if (damageComponent.UnityEntity?.Id == bullet.Initiator) return false; if (layerMask.Includes(raycastHit.collider.gameObject.layer) is false) return false; entitiesService.TryGetEntity(bullet.Initiator, out var initiator); if (damageComponent.Health is { HealthPoint: < 0 }) { returnTure = false; } var msg = new DamageMessage() { Initiator =initiator as Entity, Target = damageComponent.UnityEntity, Hit = damageComponent, Damage = bullet.InitialDamage, DamageType = new BulletDamageMessage(), Position = bullet.Position, Rotation = Quaternion.LookRotation(raycastHit.normal), RaycastHit =raycastHit, }; if (netClient.IsConnected) { } else { if (damageComponent is IEntityComponent) { damageService.Execute(msg); } else { damageComponent.GiveDamage(msg); } } } var _rigidbody = (raycastHit.rigidbody,damageComponent?.Rigidbody) switch { (null, null) => null, (null, not null) => damageComponent?.Rigidbody, (not null, null) => raycastHit.rigidbody, (not null, not null) => damageComponent?.Rigidbody, }; if (_rigidbody && _rigidbody.gameObject.layer is not 0) { _rigidbody.AddForceAtPositionAsync(force, raycastHit.point, ForceMode.Impulse).Forget(); } // List tags = new() // { // "BulletHit", // }; var tags = ListPool.Get(); tags.Add("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(); } tags.Clear(); ListPool.Release(tags); return returnTure; } void IBulletService.Spawn(SpawnBullet bullet) => SpawnBullet(bullet); } }