Souljack/Assets/_Project/Scripts/Utilities/Projectile.cs

161 lines
6.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<IDamageable> _hitTargets = new HashSet<IDamageable>();
// Фабрика: создаёт красивый светящийся снаряд со шлейфом прямо из кода!
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<Collider>();
if (col != null) Destroy(col);
// Делаем снаряд ярко светящимся (хорошо видно в полёте для уворотов!)
var rend = go.GetComponent<Renderer>();
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<TrailRenderer>();
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<Projectile>();
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<IDamageable>();
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<IDamageable>();
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<EnemyAI>() != 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);
}
}
}
}