using System.Collections.Generic; using UnityEngine; namespace _Project.Scripts.PlayerUtils { public class Projectile : MonoBehaviour { private Vector3 _direction; private float _speed; private float _damage; private float _maxDistance; private float _radius; private bool _pierce; private bool _firedByPlayer; private GameObject _owner; private LayerMask _hitMask; private GameObject _impactPrefab; private float _traveledDistance; private readonly HashSet _hitTargets = new HashSet(); // Фабрика: создаёт красивый светящийся снаряд со шлейфом прямо из кода! public static void Spawn( Vector3 position, Vector3 direction, BodyProfile profile, bool firedByPlayer, GameObject owner, LayerMask hitMask, GameObject impactPrefab = null) { var go = GameObject.CreatePrimitive(PrimitiveType.Sphere); go.name = firedByPlayer ? "PlayerProjectile" : "EnemyProjectile"; go.transform.position = position; go.transform.localScale = Vector3.one * profile.projectileSize; // Убираем стандартный коллайдер, так как проверяем полёт точным SphereCast var col = go.GetComponent(); if (col != null) Destroy(col); // Делаем снаряд ярко светящимся (хорошо видно в полёте для уворотов!) var rend = go.GetComponent(); if (rend != null) { var mat = new Material(Shader.Find("Sprites/Default")); mat.color = profile.projectileColor; rend.material = mat; rend.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } // Добавляем красивый шлейф (трассер) за снарядом var trail = go.AddComponent(); trail.time = 0.18f; trail.startWidth = profile.projectileSize * 0.85f; trail.endWidth = 0f; trail.material = new Material(Shader.Find("Sprites/Default")); trail.startColor = profile.projectileColor; trail.endColor = new Color(profile.projectileColor.r, profile.projectileColor.g, profile.projectileColor.b, 0f); trail.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; var proj = go.AddComponent(); proj._direction = direction.normalized; proj._speed = profile.projectileSpeed; proj._damage = firedByPlayer ? profile.damage : profile.damage * 0.5f; proj._maxDistance = profile.weaponRange; proj._radius = profile.projectileSize * 0.45f; proj._pierce = profile.pierceTargets; proj._firedByPlayer = firedByPlayer; proj._owner = owner; proj._hitMask = hitMask; proj._impactPrefab = impactPrefab; } private void Update() { // Снаряды летят в мировом времени (Time.deltaTime), поэтому в форме Духа // они замедляются, позволяя игроку красиво уворачиваться от них! float step = _speed * Time.deltaTime; if (step <= 0.0001f) return; if (_pierce) { // Пробивной снаряд Снайпера var hits = Physics.SphereCastAll(transform.position, _radius, _direction, step, _hitMask, QueryTriggerInteraction.Ignore); foreach (var hit in hits) { if (ShouldIgnore(hit.collider)) continue; var dmg = hit.collider.GetComponentInParent(); if (dmg != null) { if (_hitTargets.Add(dmg)) { dmg.TakeDamage(_damage); SpawnImpact(hit); } } else { // Упёрлись в стену/пол — уничтожаем снаряд SpawnImpact(hit); Destroy(gameObject); return; } } } else { // Обычный снаряд: до первого попадания if (Physics.SphereCast(transform.position, _radius, _direction, out RaycastHit hit, step, _hitMask, QueryTriggerInteraction.Ignore)) { if (!ShouldIgnore(hit.collider)) { var dmg = hit.collider.GetComponentInParent(); if (dmg != null) { dmg.TakeDamage(_damage); } else if (hit.rigidbody != null) { hit.rigidbody.AddForceAtPosition(_direction * (_damage * 8f), hit.point, ForceMode.Impulse); } SpawnImpact(hit); Destroy(gameObject); return; } } } transform.position += _direction * step; _traveledDistance += step; if (_traveledDistance >= _maxDistance) { Destroy(gameObject); } } private bool ShouldIgnore(Collider col) { if (_owner != null && (col.gameObject == _owner || col.transform.root.gameObject == _owner)) return true; // Чтобы враги случайно не расстреливали друг друга в спину, их снаряды бьют только игрока и стены if (!_firedByPlayer && col.GetComponentInParent() != null) return true; return false; } private void SpawnImpact(RaycastHit hit) { if (_impactPrefab != null) { var imp = Instantiate(_impactPrefab, hit.point + hit.normal * 0.01f, Quaternion.LookRotation(hit.normal)); Destroy(imp, 2.5f); } } } }