using UnityEngine;
namespace DestroyIt
{
public class Bullet : MonoBehaviour
{
[Tooltip("The bullet's speed in game units per second.")]
public float speed = 400f;
[Tooltip("How many seconds the bullet will live, regardless of distance traveled.")]
public float timeToLive = 0.5f;
public Renderer streak;
[Range(1,10)]
[Tooltip("How often the bullet streak is visibile. 1 = 10% of the time. 10 = 100% of the time.")]
public int streakVisibleFreq = 6;
[Range(1,50)]
[Tooltip("Once turned on or off, the bullet streak will remain stable (unchanged) for this many frames.")]
public int streakMinFramesStable = 3;
/// The position where the bullet started.
public Vector3 StartingPosition { get; set; }
/// How far in game units the bullet has traveled after being fired.
public float DistanceTraveled
{
get { return Vector3.Distance(StartingPosition, transform.position); }
}
private float spawnTime = 0f;
private bool hitSomething = false;
private bool isInitialized = false;
private int streakFramesStable = 0;
public void OnEnable()
{
spawnTime = Time.time;
hitSomething = false;
StartingPosition = transform.position;
if (streak != null)
streak.gameObject.SetActive(Random.Range(1, 11) <= streakVisibleFreq);
isInitialized = true;
}
public void Update()
{
if (!isInitialized) return;
// Check if the bullet needs to be destroyed.
if (Time.time > spawnTime + timeToLive || hitSomething)
{
ObjectPool.Instance.PoolObject(gameObject);
return;
}
if (streak != null)
{
if (streakFramesStable > streakMinFramesStable)
{
streak.gameObject.SetActive(Random.Range(1, 11) <= streakVisibleFreq);
streakFramesStable = 0;
}
else
streakFramesStable += 1;
}
Vector3 lineEndPoint = transform.position + (transform.forward * speed * Time.deltaTime);
Debug.DrawLine(transform.position, lineEndPoint, Color.red, 5f);
// Raycast in front of the bullet to see if it hit anything. Sort the hits from closest to farthest.
RaycastHit[] hits = Physics.RaycastAll(transform.position, transform.forward, speed * Time.deltaTime);
int hitIndex = -1; // index of the closest hit that is not a trigger collider
float closestHitDistance = float.MaxValue;
for (int i = 0; i < hits.Length; i++)
{
if (hits[i].collider.isTrigger) continue;
if (hits[i].distance < closestHitDistance)
{
hitIndex = i;
closestHitDistance = hits[i].distance;
}
}
if (hitIndex > -1)
{
ProcessBulletHit(hits[hitIndex], transform.forward);
hitSomething = true;
return;
}
// Move the bullet forward.
transform.position += transform.forward * speed * Time.deltaTime;
}
private static void ProcessBulletHit(RaycastHit hitInfo, Vector3 bulletDirection)
{
HitEffects hitEffects = hitInfo.collider.gameObject.GetComponentInParent();
if (hitEffects != null && hitEffects.effects.Count > 0)
hitEffects.PlayEffect(HitBy.Bullet, hitInfo.point, hitInfo.normal);
// Apply damage if object hit was Destructible
// Only do this for the first active and enabled Destructible script found in parent objects
// Special Note: Destructible scripts are turned off on terrain trees by default (to save resources), so we will make an exception for them and process the hit anyway
Destructible[] destObjs = hitInfo.collider.gameObject.GetComponentsInParent(false);
foreach (Destructible destObj in destObjs)
{
if (!destObj.isActiveAndEnabled && !destObj.isTerrainTree) continue;
ImpactDamage bulletDamage = new ImpactDamage { DamageAmount = InputManager.Instance.bulletDamage, AdditionalForce = InputManager.Instance.bulletForcePerSecond, AdditionalForcePosition = hitInfo.point, AdditionalForceRadius = .5f };
destObj.ApplyDamage(bulletDamage);
break;
}
Vector3 force = bulletDirection * (InputManager.Instance.bulletForcePerSecond / InputManager.Instance.bulletForceFrequency);
// Apply impact force to rigidbody hit
Rigidbody rbody = hitInfo.collider.attachedRigidbody;
if (rbody != null)
rbody.AddForceAtPosition(force, hitInfo.point, ForceMode.Impulse);
// Check for Chip-Away Debris
ChipAwayDebris chipAwayDebris = hitInfo.collider.gameObject.GetComponent();
if (chipAwayDebris != null)
chipAwayDebris.BreakOff(-1.5f * force, hitInfo.point);
}
}
}