ThrowBall/Assets/Plugins/RayFire/Scripts/Components/RayfireBomb.cs

741 lines
29 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using Random = UnityEngine.Random;
namespace RayFire
{
[AddComponentMenu ("RayFire/Rayfire Bomb")]
[HelpURL ("https://rayfirestudios.com/unity-online-help/components/unity-bomb-component/")]
public class RayfireBomb : MonoBehaviour
{
/// <summary>
/// Rayfire Bomb explosion range type.
/// </summary>
public enum RangeType
{
Spherical = 0,
Directional = 3
}
/// <summary>
/// Rayfire Bomb explosion fade type.
/// </summary>
public enum BombFadeType
{
Linear = 0,
Exponential = 1,
ByCurve = 3,
None = 2
}
/// <summary>
/// Rayfire Bomb projectile class.
/// </summary>
[Serializable]
public class Projectile
{
public Vector3 positionPivot;
public Vector3 positionClosest;
public Vector3 expPos;
public float fade;
public Rigidbody rb;
public RayfireRigid rigid;
public Quaternion rotation;
public RFShard shard;
public RayfireRigidRoot rigidRoot;
public SimType simType;
}
// UI
public bool showGizmo;
public RangeType rangeType;
public float range = 5f;
public Vector2 boxSize = new Vector2 (6, 2);
public int deletion;
public BombFadeType fadeType;
public float strength = 1f;
public int variation = 50;
public int chaos = 30;
public bool forceByMass = true;
public bool affectInactive;
public bool affectKinematic;
public float heightOffset;
public float delay;
public bool atStart;
public bool destroy;
public bool obst_enable;
public bool obst_static;
public bool obst_kinematik;
[FormerlySerializedAs ("obstacleCollidersList")] public List<Collider> obst_list;
public bool applyDamage;
public float damageValue = 100f;
public bool play;
public float volume = 1f;
public AudioClip clip;
public int mask = -1;
public string tagFilter = "Untagged";
public AnimationCurve curve = new AnimationCurve (
new Keyframe (0, 1, -1, 0), new Keyframe (0.5f, 1, 0, 0),
new Keyframe (0.7f, 0, -1, 0), new Keyframe (1, 0, 0, -1));
// Event
public RFExplosionEvent explosionEvent = new RFExplosionEvent();
int maxObstacleHits = 10;
string untagged = "Untagged";
// Non Serialized
[NonSerialized] Vector3 bombPosition;
[NonSerialized] Vector3 bombDirection;
[NonSerialized] Vector3 explPosition;
[NonSerialized] Collider[] explColliders;
[NonSerialized] HashSet<Collider> obst_hash;
[NonSerialized] List<Rigidbody> rigidbodies = new List<Rigidbody>();
[NonSerialized] List<Projectile> projectiles = new List<Projectile>();
[NonSerialized] List<Projectile> deletionProjectiles = new List<Projectile>();
/// /////////////////////////////////////////////////////////
/// Common
/// /////////////////////////////////////////////////////////
// Awake
void Awake()
{
// Clear
ClearLists();
}
// Auto explode
void Start()
{
if (Application.isPlaying == true)
if (atStart == true)
Explode (delay);
}
// Copy properties from another Rigs
public void CopyFrom (RayfireBomb scr)
{
rangeType = scr.rangeType;
fadeType = scr.fadeType;
range = scr.range;
deletion = scr.deletion;
strength = scr.strength;
variation = scr.variation;
chaos = scr.chaos;
forceByMass = scr.forceByMass;
affectKinematic = scr.affectKinematic;
heightOffset = scr.heightOffset;
delay = scr.delay;
applyDamage = scr.applyDamage;
damageValue = scr.damageValue;
clip = scr.clip;
volume = scr.volume;
}
/// /////////////////////////////////////////////////////////
/// Explode
/// /////////////////////////////////////////////////////////
// Explode bomb
public void Explode (float delayLoc)
{
if (delayLoc == 0)
Explode();
else if (delayLoc > 0)
StartCoroutine (ExplodeCor());
}
// Init delay before explode
IEnumerator ExplodeCor()
{
// Wait delay time
yield return new WaitForSeconds (delay);
// Explode
Explode();
}
// Explode bomb
void Explode()
{
// Set bomb and explosion positions
SetPositions();
// Setup collider, projectiles and rigidbodies
if (Setup() == false)
return;
// Recollect projectiles if damage with demolition.
if (SetRigidDamage() == true)
if (Setup() == false)
return;
// Deletion
Deletion();
// Activate inactive and kinematic objects
Activate();
// Apply explosion force
SetForce();
// Event
RFExplosionEvent.ExplosionEvent (this);
// Explosion Sound
PlayAudio();
// Clear lists in runtime
if (Application.isEditor == false)
ClearLists();
// Destroy
if (destroy == true)
Destroy (gameObject, 1f);
}
// Explosion Sound
void PlayAudio()
{
if (play == true && clip != null)
{
// Fix volume
if (volume < 0)
volume = 1f;
// TODO Set volume bu range
// Play clip
AudioSource.PlayClipAtPoint (clip, transform.position, volume);
}
}
// Setup collider, projectiles and rigidbodies
bool Setup()
{
// Clear all lists
ClearLists();
// Set colliders by range type
SetColliders();
// Get obstacle colliders from all colliders in explosion range
SetObstacleHash();
// Filter exploded colliders by obstacles
CollidersFilter();
// Set rigidbodies by colliders
SetProjectiles();
// Nothing to explode
if (projectiles.Count == 0)
return false;
return true;
}
// Reset all lists
void ClearLists()
{
explColliders = null;
rigidbodies.Clear();
projectiles.Clear();
}
/// /////////////////////////////////////////////////////////
/// Restore
/// /////////////////////////////////////////////////////////
// Restore exploded objects transformation
public void Restore()
{
RestoreProjectiles (projectiles);
RestoreProjectiles (deletionProjectiles);
}
// Restore projectiles
static void RestoreProjectiles (List<Projectile> prj)
{
for (int i = 0; i < prj.Count; i++)
if (prj[i].rigid != null)
prj[i].rigid.ResetRigid();
else if (prj[i].rb != null)
{
prj[i].rb.velocity = Vector3.zero;
prj[i].rb.angularVelocity = Vector3.zero;
prj[i].rb.transform.SetPositionAndRotation (prj[i].positionPivot, prj[i].rotation);
}
}
/// /////////////////////////////////////////////////////////
/// Setups
/// /////////////////////////////////////////////////////////
// Set bomb and explosion positions
void SetPositions()
{
// Set initial bomb and explosion positions
bombPosition = transform.position;
bombDirection = transform.forward;
explPosition = bombPosition;
// Consider height offset
if (heightOffset != 0)
explPosition = bombPosition + transform.TransformDirection (0f, heightOffset, 0f);
}
// Set colliders by range type
void SetColliders()
{
// Set mask to everything because of obstacles
int finalMask = mask;
if (obst_enable == true)
if (obst_static == true || obst_kinematik == true)
finalMask = -1;
if (rangeType == RangeType.Spherical)
explColliders = Physics.OverlapSphere (explPosition, range, finalMask);
else if (rangeType == RangeType.Directional)
{
Vector3 center = bombPosition + (range / 2f) * bombDirection;
Vector3 halfExtent = new Vector3(boxSize.x / 2f, boxSize.y / 2f, range / 2f);
explColliders = Physics.OverlapBox (center, halfExtent, transform.rotation, finalMask);
}
}
// Get obstacle colliders hash from all colliders in explosion range
void SetObstacleHash()
{
if (obst_enable == true)
{
// Final obstacle colliders
List<Collider> obstColliders = new List<Collider>();
if (obst_list != null)
for (int i = 0; i < obst_list.Count; i++)
if (obst_list[i] != null)
obstColliders.Add (obst_list[i]);
// Collect obstacles colliders
for (int j = 0; j < explColliders.Length; j++)
{
// Obstacles colliders without rigidbody
if (obst_static == true && explColliders[j].attachedRigidbody == null)
{
obstColliders.Add (explColliders[j]);
explColliders[j] = null;
continue;
}
// Obstacles colliders without kinematik rigidbody
if (obst_kinematik == true &&
explColliders[j].attachedRigidbody != null &&
explColliders[j].attachedRigidbody.isKinematic == true)
{
obstColliders.Add (explColliders[j]);
explColliders[j] = null;
}
}
// Obstacles colliders as hash
if (obstColliders.Count > 0)
obst_hash = new HashSet<Collider> (obstColliders);
}
}
// Filter exploded colliders by obstacles and other properties
void CollidersFilter()
{
// TODO check cluster explosion. sum expl force for each collider issue
// Filter by obstacles
if (obst_enable == true)
{
// Ray intersections -> obstacles -> projectiles
if (obst_hash != null && obst_hash.Count > 0)
{
// Vars
int num = 0;
Vector3 bombToFrag = Vector3.zero;
RaycastHit[] hits = new RaycastHit[maxObstacleHits];
// Check all
for (int c = 0; c < explColliders.Length; c++)
{
// Null check
if (explColliders[c] == null)
continue;
// Vector from bomb to frag collider
if (rangeType == RangeType.Spherical)
{
bombToFrag = explColliders[c].bounds.center - explPosition;
num = Physics.RaycastNonAlloc (explPosition, bombToFrag.normalized, hits, bombToFrag.magnitude);
}
// 90 Degree Vector from bomb plane to frag collider
else if (rangeType == RangeType.Directional)
{
Vector3 expPos = ClosestOnPlane (explColliders[c].bounds.center, bombPosition, bombDirection);
bombToFrag = explColliders[c].bounds.center - expPos;
num = Physics.RaycastNonAlloc (expPos, bombDirection, hits, bombToFrag.magnitude);
}
// Raycast
if (num > 0)
for (int h = 0; h < hits.Length; h++)
// Check if collider in obstacle colliders hash
if (obst_hash.Contains (hits[h].collider) == true)
// Exclude collider if obstacle closer than frag
if (hits[h].distance < bombToFrag.magnitude)
explColliders[c] = null;
}
}
}
// Filter by tag
if (tagFilter != untagged)
{
for (int i = 0; i < explColliders.Length; i++)
if (explColliders[i] != null)
if (explColliders[i].gameObject.CompareTag (tagFilter) == false)
explColliders[i] = null;
}
// Filter by layer if obstacle enabled and added its layers to finalmask
if (mask != -1 && obst_enable == true)
{
for (int i = 0; i < explColliders.Length; i++)
if (explColliders[i] != null)
if (mask == (mask | 1 << explColliders[i].gameObject.layer) == false )
explColliders[i] = null;
}
}
// Set projectiles by colliders
void SetProjectiles()
{
// Collect all rigid bodies in range
projectiles.Clear();
foreach (Collider col in explColliders)
{
// Null check
if (col == null)
continue;
// Get attached rigid body
Rigidbody rb = col.attachedRigidbody;
// No rb
if (rb == null)
continue;
// Create projectile if rigid body new. Could be several colliders on one object. TODO change to hash
if (rigidbodies.Contains (rb) == false)
{
Projectile projectile = new Projectile
{
rb = rb,
positionPivot = rb.transform.position,
rotation = rb.transform.rotation,
expPos = explPosition
};
// For directional bomb explosion point is different for every fragment
if (rangeType == RangeType.Directional)
projectile.expPos = ClosestOnPlane (projectile.positionPivot, bombPosition, bombDirection);
// Get position of closest point to explosion position
projectile.positionClosest = col.bounds.ClosestPoint (projectile.expPos);
// Get fade multiplier by range and distance
projectile.fade = Fade (projectile.expPos, projectile.positionClosest);
// Skip fragments out of range
if (projectile.fade <= 0)
continue;
// Check for Rigid script
projectile.rigid = projectile.rb.GetComponent<RayfireRigid>();
// TODO optional targets, for quick search
// Set RigidRoot amd Shard
if (projectile.rigid == null)
{
projectile.rigidRoot = projectile.rb.GetComponentInParent<RayfireRigidRoot>();
if (projectile.rigidRoot != null)
{
if (projectile.rigidRoot.collidersHash == null)
{
List<Collider> collidersTemp = new List<Collider>(projectile.rigidRoot.inactiveShards.Count);
for (int s = 0; s < projectile.rigidRoot.inactiveShards.Count; s++)
collidersTemp.Add (projectile.rigidRoot.inactiveShards[s].col);
projectile.rigidRoot.collidersHash = new HashSet<Collider>(collidersTemp);
}
// Collider belongs to inactive shard
if (projectile.rigidRoot.collidersHash.Contains (col) == true)
{
for (int i = 0; i < projectile.rigidRoot.inactiveShards.Count; i++)
{
if (projectile.rigidRoot.inactiveShards[i].col == col)
{
projectile.shard = projectile.rigidRoot.inactiveShards[i];
projectile.simType = projectile.shard.sm;
break;
}
}
}
}
}
// Set Rigid component sim type
else
{
projectile.simType = projectile.rigid.simTp;
}
/*
// Skip inactive objects
if (affectInactive == false)
{
if (projectile.rigid != null && projectile.rigid.simTp == SimType.Inactive)
continue;
if (projectile.shard != null && projectile.shard.sm == SimType.Inactive)
continue;
}
// Skip kinematik objects
if (affectKinematic == false)
{
if (projectile.rigid != null && projectile.rigid.simTp == SimType.Kinematic)
continue;
if (projectile.shard != null && projectile.shard.sm == SimType.Kinematic)
continue;
}
*/
// Collect projectile
projectiles.Add (projectile);
// Remember rigid body
rigidbodies.Add (rb);
}
}
// TODo nullify collider has list in RigidRoots
// do not collect kinematic
// collect rigid kinematic if can be activated
}
// Set RayFire Rigid refs for projectiles
bool SetRigidDamage()
{
// Recollect state for new fragments after demolition
bool recollectState = false;
// Apply damage to rigid and demolish first
if (applyDamage == true && damageValue > 0)
{
for (int i = 0; i < projectiles.Count; i++)
{
// Rigid exist and damage enabled
if (projectiles[i].rigid != null && projectiles[i].rigid.damage.en == true)
{
// Apply damage and demolish
if (projectiles[i].rigid.ApplyDamage (damageValue * projectiles[i].fade, projectiles[i].expPos, range) == true)
recollectState = true;
}
}
}
return recollectState;
}
// Deletion
void Deletion()
{
if (deletion > 0)
{
// Get deletion projectiles and remove from force projectiles list
deletionProjectiles = new List<Projectile>();
for (int i = projectiles.Count - 1; i >= 0; i--)
if (Vector3.Distance (projectiles[i].positionClosest, projectiles[i].expPos) < range * deletion / 100f)
{
deletionProjectiles.Add (projectiles[i]);
projectiles.RemoveAt (i);
}
// Destroy
if (deletionProjectiles.Count > 0)
for (int i = 0; i < deletionProjectiles.Count; i++)
{
if (deletionProjectiles[i].rigid != null)
RayfireMan.DestroyFragment (deletionProjectiles[i].rigid, null, deletionProjectiles[i].rigid.reset.destroyDelay);
else
Destroy (deletionProjectiles[i].rb.gameObject);
}
}
}
// Activate inactive and kinematic objects
void Activate()
{
// Activate disabled
if (affectInactive == false && affectKinematic == false)
return;
foreach (Projectile projectile in projectiles)
{
// Outside of range
if (projectile.fade <= 0)
return;
// Affect Kinematic rigid body
if (affectKinematic == true && projectile.rb.isKinematic == true)
{
// Convert kinematic to dynamic via rigid script
if (projectile.rigid != null)
projectile.rigid.Activate();
// Activate kinematic rigidRoot shard
else if (projectile.shard != null)
{
if (projectile.shard.sm == SimType.Kinematic)
RFActivation.ActivateShard (projectile.shard, projectile.rigidRoot);
}
// Convert regular kinematic to dynamic
else
{
projectile.rb.isKinematic = false;
// TODO Set mass
// Set convex
MeshCollider meshCol = projectile.rb.gameObject.GetComponent<MeshCollider>();
if (meshCol != null && meshCol.convex == false)
meshCol.convex = true;
}
// Skip inactive object activation.
continue;
}
// Affect inactive
if (affectInactive == true)
{
// Activate inactive via rigid script
if (projectile.rigid != null)
{
if (projectile.rigid.simTp == SimType.Inactive)
projectile.rigid.Activate();
}
// Activate inactive rigidRoot shard
else if (projectile.shard != null)
{
if (projectile.shard.sm == SimType.Inactive)
RFActivation.ActivateShard (projectile.shard, projectile.rigidRoot);
}
}
}
}
// Apply explosion force, vector and rotation to projectiles
void SetForce()
{
// Set same random state
Random.InitState (1);
// Set forceMode by mass state
ForceMode forceMode = ForceMode.Impulse;
if (forceByMass == false)
forceMode = ForceMode.VelocityChange;
// Get str for each object by explode type with variation
foreach (Projectile projectile in projectiles)
{
// Skip inactive or kinematik
if (affectInactive == false && projectile.simType == SimType.Inactive) continue;
if (projectile.simType == SimType.Kinematic) continue;
// Get local velocity strength
float strVar = strength * variation / 100f + strength;
float str = Random.Range (strength, strVar);
float strMult = projectile.fade * str * 10f;
// Get explosion vector from explosion position to projectile center of mass
Vector3 vector = ExplosionVector (projectile);
// Apply force
projectile.rb.AddForce (vector * strMult, forceMode);
// Get local rotation strength
int chaosRot = chaos / 2;
Vector3 rot = new Vector3 (Random.Range (-chaosRot, chaosRot), Random.Range (-chaosRot, chaosRot), Random.Range (-chaosRot, chaosRot));
// Set rotation impulse
projectile.rb.angularVelocity = rot;
}
}
/// /////////////////////////////////////////////////////////
/// Support
/// /////////////////////////////////////////////////////////
// Fade multiplier
float Fade (Vector3 bombPos, Vector3 fragPos)
{
// Get rate by fade type
float fade = 1f;
// Linear or Exponential fade
if (fadeType == BombFadeType.Linear)
fade = 1f - Vector3.Distance (bombPos, fragPos) / range;
// Exponential fade
else if (fadeType == BombFadeType.Exponential)
{
fade = 1f - Vector3.Distance (bombPos, fragPos) / range;
fade *= fade;
}
// By curve
else if (fadeType == BombFadeType.ByCurve)
{
fade = curve.Evaluate (Vector3.Distance (bombPos, fragPos) / range);;
}
// Cap fade
if (fade < 0.01f)
fade = 0;
return fade;
}
// Get explosion vector from explosion position to projectile center of mass
Vector3 ExplosionVector (Projectile projectile)
{
return Vector3.Normalize (projectile.positionPivot - projectile.expPos);
}
// Get closest point on plane
private Vector3 ClosestOnPlane(Vector3 pos, Vector3 plPos, Vector3 plNorm)
{
float f = -Vector3.Dot(plNorm, (pos - plPos)) / Vector3.Dot(plNorm, plNorm);
return pos + f * plNorm;
}
}
}