This commit is contained in:
2025-09-17 18:56:28 +08:00
commit 54c72710a5
5244 changed files with 5717609 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
using System;
namespace DestroyIt
{
public static class ArrayExtensions
{
/// <summary>Provides a quick way to remove elements from a standard array.</summary>
public static T[] RemoveAllAt<T>(this T[] array, int[] removeIndices)
{
T[] newArray = new T[0];
if (removeIndices.Length == 0) return array;
if (removeIndices.Length >= array.Length) return newArray;
newArray = new T[array.Length];
int i = 0;
int j = 0;
int itemsKept = 0;
while (i < array.Length)
{
bool keepItem = true;
for (int x = 0; x < removeIndices.Length; x++)
{
if (i == removeIndices[x])
keepItem = false;
}
if (keepItem)
{
itemsKept++;
newArray[j] = array[i];
j++;
}
i++;
}
Array.Resize(ref newArray, itemsKept);
return newArray;
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bbde6c1a812a73349864180510448026
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@@ -0,0 +1,233 @@
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor.SceneManagement;
#endif
// ReSharper disable ForCanBeConvertedToForeach
namespace DestroyIt
{
public static class DestructionExtensions
{
public static void Update(this List<float> models, int withinSeconds)
{
bool isChanged = false;
if (models.Count > 0)
{
for (int i = 0; i < models.Count; i++)
{
if (Time.time > (models[i] + withinSeconds))
{
models.Remove(models[i]);
isChanged = true;
}
}
if (isChanged)
DestructionManager.Instance.FireDestroyedPrefabCounterChangedEvent();
}
}
public static void ReleaseClingingDebris(this Destructible destroyedObj)
{
List<Transform> clingingDebris = new List<Transform>();
TagIt[] tagIts = destroyedObj.GetComponentsInChildren<TagIt>();
if (tagIts == null) return;
for (int i = 0; i < tagIts.Length; i++)
{
for (int j = 0; j < tagIts[i].tags.Count; j++)
{
if (tagIts[i].tags[j] == Tag.ClingingDebris)
clingingDebris.Add(tagIts[i].transform);
}
}
for (int i = 0; i < clingingDebris.Count; i++)
{
//TODO: When releasing clinging debris, we need to add back the same rigidbody configuration the object had before becoming debris.
clingingDebris[i].gameObject.AddComponent<Rigidbody>();
}
}
public static void MakeDebrisCling(this GameObject destroyedObj)
{
// Check to see if any debris pieces will be clinging to nearby rigidbodies
ClingPoint[] clingPoints = destroyedObj.GetComponentsInChildren<ClingPoint>();
for (int i=0; i<clingPoints.Length; i++)
{
Rigidbody clingPointRbody = clingPoints[i].transform.parent.GetComponent<Rigidbody>();
if (clingPointRbody == null) continue;
// Check percent chance first
if (clingPoints[i].chanceToCling < 100) // 100% chance always clings
{
int randomNbr = Random.Range(1, 100);
if (randomNbr > clingPoints[i].chanceToCling) // exit if random number is outside the possible chance.
continue;
}
// Check if there's anything to cling to.
Ray ray = new Ray(clingPoints[i].transform.position - (clingPoints[i].transform.forward * 0.025f), clingPoints[i].transform.forward); // need to start the ray behind the transform a little
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo, 0.075f))
{
if (hitInfo.collider.isTrigger) continue; // ignore trigger colliders.
clingPointRbody.transform.parent = hitInfo.collider.gameObject.transform;
// If the debris is Destructible, add DestructibleParent script to the parent so debris will get OnCollisionEnter() events.
if (clingPointRbody.gameObject.GetComponent<Destructible>() && !hitInfo.collider.gameObject.GetComponent<DestructibleParent>())
hitInfo.collider.gameObject.AddComponent<DestructibleParent>();
// If the object this debris is clinging to is also destructible, set it up so it will be released when the parent object is destroyed.
Destructible destructibleObj = hitInfo.collider.gameObject.GetComponentInParent<Destructible>();
if (destructibleObj != null)
{
destructibleObj.unparentOnDestroy.Add(clingPointRbody.gameObject);
DelayedRigidbody delayedRbody = clingPointRbody.gameObject.AddComponent<DelayedRigidbody>();
delayedRbody.mass = clingPointRbody.mass;
delayedRbody.drag = clingPointRbody.drag;
delayedRbody.angularDrag = clingPointRbody.angularDrag;
}
// Remove all cling points from this clinging debris object
ClingPoint[] clingPointsToDestroy = clingPointRbody.gameObject.GetComponentsInChildren<ClingPoint>();
for (int j = 0; j < clingPointsToDestroy.Length; j++)
Object.Destroy(clingPointsToDestroy[j].gameObject);
// Remove all rigidbodies from this clinging debris object
clingPointRbody.gameObject.RemoveAllFromChildren<Rigidbody>();
}
}
}
public static void ProcessDestructibleCollision(this Destructible destructibleObj, Collision collision, Rigidbody collidingRigidbody)
{
// Ignore collisions if collidingRigidbody is null
if (collidingRigidbody == null) return;
// Check for DontDoDamage tag on the collidingRigidbody
if (collidingRigidbody.gameObject.HasTag(Tag.DontDoDamage)) return;
// Ignore collisions if this object is destroyed.
if (destructibleObj.IsDestroyed) return;
// Check that the impact is forceful enough to cause damage
if (collision.relativeVelocity.magnitude < destructibleObj.ignoreCollisionsUnder) return;
if (collision.contacts.Length == 0) return;
float impactDamage;
Rigidbody otherRbody = collision.contacts[0].otherCollider.gameObject.GetComponentInParent<Rigidbody>(true);
// If we've collided with another rigidbody, use the average mass of the two objects for impact damage.
if (otherRbody != null)
{
if (otherRbody.gameObject.HasTag(Tag.DontDoDamage)) return;
float avgMass = (otherRbody.mass + collidingRigidbody.mass) / 2;
impactDamage = Vector3.Dot(collision.contacts[0].normal, collision.relativeVelocity) * avgMass;
}
else // If we've collided with a static object (terrain, static collider, etc), use this object's attached rigidbody.
{
Rigidbody rbody = destructibleObj.GetComponent<Rigidbody>();
impactDamage = Vector3.Dot(collision.contacts[0].normal, collision.relativeVelocity) * collidingRigidbody.mass;
}
impactDamage = Mathf.Abs(impactDamage); // can't have negative damage
if (impactDamage > 1f) // impact must do at least 1 damage to bother with.
{
//Debug.Log("Impact Damage: " + impactDamage);
//Debug.DrawRay(otherRbody.transform.position, collision.relativeVelocity, Color.yellow, 10f); // yellow: where the impact force is heading
ImpactDamage impactInfo = new ImpactDamage() { ImpactObject = otherRbody, DamageAmount = impactDamage, ImpactObjectVelocityFrom = collision.relativeVelocity * -1 };
destructibleObj.ApplyDamage(impactInfo);
}
}
public static void CalculateDamageLevels(this List<DamageLevel> damageLevels, float maxHitPoints)
{
if (maxHitPoints <= 0) { return; }
if (damageLevels == null || damageLevels.Count == 0) { return; }
// Sort the list descending on Damage Percent field.
//damageLevels.Sort((x, y) => -1 * x.damagePercent.CompareTo(y.damagePercent));
int prevHealthPercent = -1;
for (int i = 0; i < damageLevels.Count; i++)
{
if (damageLevels[i] == null) continue;
if (damageLevels[i].healthPercent <= 0)
{
damageLevels[i].hasError = true;
continue;
}
if (prevHealthPercent > -1 && damageLevels[i].healthPercent >= prevHealthPercent)
{
damageLevels[i].hasError = true;
prevHealthPercent = damageLevels[i].healthPercent;
continue; // Health percents should go down with every subsequent damage level.
}
damageLevels[i].hasError = false;
if (i == 0) // highest damage level, set max hit points to maxHitPoints of destructible object.
damageLevels[i].maxHitPoints = maxHitPoints;
else // not the highest damage level, so set the previous level's minHitPoints to this level's maxHitPoints.
{
damageLevels[i].maxHitPoints = maxHitPoints * (.01f * damageLevels[i].healthPercent);
damageLevels[i - 1].minHitPoints = maxHitPoints * (.01f * damageLevels[i].healthPercent) + .1f;
}
if (i == damageLevels.Count - 1) // lowest damage level, set min hit point range to 0.
damageLevels[i].minHitPoints = 0;
prevHealthPercent = damageLevels[i].healthPercent;
}
}
public static DamageLevel GetDamageLevel(this List<DamageLevel> damageLevels, float hitPoints)
{
if (damageLevels == null || damageLevels.Count == 0) return null;
if (hitPoints <= 0)
return damageLevels[damageLevels.Count - 1];
for (int i = 0; i < damageLevels.Count; i++)
{
if (damageLevels[i].maxHitPoints >= hitPoints && damageLevels[i].minHitPoints <= hitPoints)
return damageLevels[i];
}
return null;
}
public static void ReparentChildren(this Destructible destObj, GameObject newObj)
{
if (destObj.childrenToReParentByName != null)
{
if (destObj.childrenToReParentByName.Count > 0)
{
foreach (string childName in destObj.childrenToReParentByName)
{
Transform child = destObj.transform.Find(childName);
if (child != null)
child.SetParent(newObj.transform);
}
}
}
}
/// <summary>If autoDeativateDestructibles is set to true on DestructionManager, start the Destructible component deactivated.</summary>
/// <remarks>Very useful for trees and other terrain objects, makes performance better.</remarks>
public static void SetActiveOrInactive(this Destructible destObj, DestructionManager destructionManager)
{
if (!destObj.isTerrainTree) // non-terrain tree destructibles
{
if (destructionManager.autoDeactivateDestructibles)
destObj.enabled = false;
}
else // terrain tree destructibles
{
if (destructionManager.autoDeactivateDestructibleTerrainObjects)
destObj.enabled = false;
}
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6eb3be1ee0fb46543b91b8fada9859a8
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@@ -0,0 +1,104 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
// ReSharper disable ForCanBeConvertedToForeach
namespace DestroyIt
{
public static class GameObjectExtensions
{
/// <summary>Removes all components of type T from the game object and its children.</summary>
public static void RemoveAllFromChildren<T>(this GameObject obj) where T : Component
{
if (obj == null) return;
foreach (T comp in obj.GetComponentsInChildren<T>())
Object.Destroy(comp);
}
public static void RemoveComponent<T>(this GameObject obj) where T : Component
{
if (obj == null) return;
T component = obj.GetComponent<T>();
if (component != null)
Object.Destroy(component);
}
public static List<T> GetComponentsInChildrenOnly<T>(this GameObject obj) where T : Component
{
return GetComponentsInChildrenOnly<T>(obj, false);
}
public static List<T> GetComponentsInChildrenOnly<T>(this GameObject obj, bool includeInactive) where T : Component
{
var components = obj.GetComponentsInChildren<T>(includeInactive).ToList();
components.Remove(obj.GetComponent<T>());
return components;
}
/// <summary>Be sure to set SolverIterationCount to around 25-30 in your Project Settings in order to get solid joints.</summary>
public static void AddStiffJoint(this GameObject go, Rigidbody connectedBody, Vector3 anchorPosition, Vector3 axis, float breakForce, float breakTorque)
{
FixedJoint joint = go.AddComponent<FixedJoint>();
joint.anchor = anchorPosition;
joint.connectedBody = connectedBody;
joint.breakForce = breakForce;
joint.breakTorque = breakTorque;
}
/// <summary>Attempts to get the center point location of a game object's combined meshes.</summary>
/// <example>If your gameobject has multiple meshes under it which together make up a car (wheels, body, etc), this function will attempt to find
/// the centerpoint of the car, taking into account all of the child meshes.</example>
/// <param name="go">The gameobject parent containing the meshes.</param>
/// <param name="meshRenderers">Pass in the collection of mesh renderers on this game object (including children) to save on performance.</param>
/// <returns>The centerpoint location of the gameobject's meshes.</returns>
public static Vector3 GetMeshCenterPoint(this GameObject go, MeshRenderer[] meshRenderers = null)
{
// first, get all the mesh renderers on the game object and children if they are not provided
if (meshRenderers == null)
meshRenderers = go.GetComponentsInChildren<MeshRenderer>();
// if there are no mesh renderers, return a zero vector (the gameobject's pivot position).
if (meshRenderers.Length == 0)
return Vector3.zero;
// if any mesh renderer on this game object is marked as Static, return a zero vector (the gameobject's pivot position) instead
// of trying to get the bounding boxes of static meshes.
if (go.IsAnyMeshPartOfStaticBatch(meshRenderers))
return Vector3.zero;
// if we made it this far, calculate the center point of the combined mesh bounds for the object and use that.
Bounds combinedBounds = new Bounds();
MeshFilter[] meshFilters = go.GetComponentsInChildren<MeshFilter>();
foreach (MeshFilter meshFilter in meshFilters)
{
Mesh sharedMesh = meshFilter.sharedMesh;
if (sharedMesh != null) // some meshFilters do not have shared meshes.
combinedBounds.Encapsulate(sharedMesh.bounds);
}
return combinedBounds.center;
}
public static bool IsAnyMeshPartOfStaticBatch(this GameObject go, MeshRenderer[] meshRenderers = null)
{
// first, get all the mesh renderers on the game object and children if they are not provided
if (meshRenderers == null)
meshRenderers = go.GetComponentsInChildren<MeshRenderer>();
// if there are no mesh renderers, return false.
if (meshRenderers.Length == 0)
return false;
// if any mesh renderer on this game object is marked as Static, return true.
for (int i = 0; i < meshRenderers.Length; i++)
{
if (meshRenderers[i].isPartOfStaticBatch)
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 505d79f55b31a5848a3507d798740d6e
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using UnityEngine;
namespace DestroyIt
{
public static class GizmoExtensions
{
/// <summary>Creates an editor-visible Gizmo for each damage effect, showing where they will play.</summary>
public static void DrawGizmos(this List<DamageEffect> damageEffects, Transform transform)
{
if (damageEffects == null) return;
foreach (DamageEffect effect in damageEffects)
{
if (effect == null) continue;
Gizmos.color = Color.cyan;
Gizmos.DrawWireCube(transform.TransformPoint(effect.Offset), new Vector3(0.1f, 0.1f, 0.1f));
Quaternion rotatedVector = transform.rotation * Quaternion.Euler(effect.Rotation);
Gizmos.DrawRay(transform.TransformPoint(effect.Offset), rotatedVector * Vector3.forward * .5f);
}
}
/// <summary>Creates an editor-visible Gizmo for each CenterPointOverride for a Fallback Particle Effect.</summary>
public static void DrawGizmos(this Vector3 centerPointOverride, Transform transform)
{
if (centerPointOverride == Vector3.zero) return;
Gizmos.color = Color.magenta;
Gizmos.DrawWireSphere(transform.TransformPoint(centerPointOverride), 0.1f);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a101a46a2c7abef4687612a2c307fe1e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
using UnityEngine;
namespace DestroyIt
{
public static class MaterialExtensions
{
public static bool IsProgressiveDamageCapable(this Material mat)
{
if (mat == null || mat.shader == null) return false;
return mat.HasProperty("_DetailMask") && mat.HasProperty("_DetailAlbedoMap") && mat.HasProperty("_DetailNormalMap") && mat.GetTexture("_DetailMask") != null &&
mat.GetTexture("_DetailAlbedoMap") != null && mat.GetTexture("_DetailNormalMap") != null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ae764e00d679d804f83eb97fa3b46897
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
using System;
namespace DestroyIt
{
public static class StringExtensions
{
public static string SceneFolder(this string scenePath)
{
string[] pathParts = scenePath.Split('/');
if (pathParts.Length > 1)
{
string[] folderPath = new string[pathParts.Length - 1];
for (int i = 0; i < pathParts.Length - 1; i++)
folderPath[i] = pathParts[i];
return String.Join("/", folderPath);
}
return scenePath;
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 19de84c49e9991e45a5f29da0dffe76b
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@@ -0,0 +1,86 @@
using System.Collections.Generic;
using UnityEngine;
// ReSharper disable ForCanBeConvertedToForeach
namespace DestroyIt
{
/// <summary>These extension methods work with the TagIt multi-tag system to make it easier to search for and add tags.</summary>
public static class TagItExtensions
{
public static bool HasTag(this GameObject go, params Tag[] searchTags)
{
TagIt tagIt = go.GetComponent<TagIt>();
if (tagIt == null) return false;
for (int i = 0; i < searchTags.Length; i++)
{
for (int j = 0; j < tagIt.tags.Count; j++)
if (searchTags[i] == tagIt.tags[j])
return true;
}
return false;
}
public static bool HasTagInParent(this GameObject go, params Tag[] searchTags)
{
TagIt tagIt = go.GetComponentInParent<TagIt>();
if (tagIt == null) return false;
for (int i = 0; i < searchTags.Length; i++)
{
for (int j = 0; j < tagIt.tags.Count; j++)
if (searchTags[i] == tagIt.tags[j])
return true;
}
return false;
}
public static void AddTag(this GameObject go, Tag tag)
{
TagIt tagIt = go.GetComponent<TagIt>();
if (tagIt == null)
{
tagIt = go.AddComponent<TagIt>();
tagIt.tags = new List<Tag>();
}
else if (tagIt.tags.Contains(tag))
return;
tagIt.tags.Add(tag);
}
public static void RemoveTag(this GameObject go, Tag tag)
{
TagIt tagIt = go.GetComponent<TagIt>();
if (tagIt == null) return;
tagIt.tags.Remove(tag);
}
/// <summary>Search for the highest parent found containing this tag.</summary>
public static GameObject GetHighestParentWithTag(this GameObject go, Tag tag)
{
// First, get all parents of this gameobject.
List<Transform> parents = new List<Transform>();
Transform trans = go.transform;
while (trans != null)
{
parents.Add(trans);
trans = trans.parent;
}
// Now check each parent, starting with the oldest one, to see if any contains the Tag.
TagIt tagIt = null;
for (int i = parents.Count - 1; i >= 0; i--)
{
tagIt = parents[i].GetComponent<TagIt>();
if (tagIt != null && tagIt.tags.Contains(tag))
return parents[i].gameObject;
}
return null;
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bd49149637e7d974cbdf94df71dca769
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@@ -0,0 +1,44 @@
using UnityEngine;
// ReSharper disable SuggestVarOrType_Elsewhere
// ReSharper disable SuggestVarOrType_SimpleTypes
// ReSharper disable ForCanBeConvertedToForeach
// ReSharper disable SuggestVarOrType_BuiltInTypes
namespace DestroyIt
{
public static class TerrainExtensions
{
public static TerrainTree ClosestTreeToPoint(this Terrain terrain, Vector3 point)
{
TreeInstance[] trees = terrain.terrainData.treeInstances;
if (trees.Length == 0) return null;
TerrainTree closestTree = new TerrainTree {Index = -1};
float closestTreeDist = float.MaxValue;
for (int i = 0; i < trees.Length; i++)
{
Vector3 treePos = Vector3.Scale(trees[i].position, terrain.terrainData.size) + terrain.transform.position;
float treeDist = Vector3.Distance(treePos, point);
if (treeDist < closestTreeDist)
{
closestTreeDist = treeDist;
closestTree.Index = i;
closestTree.Position = treePos;
closestTree.TreeInstance = trees[i];
}
}
return closestTree;
}
public static Vector3 WorldPositionOfTree(this Terrain terrain, int treeIndex)
{
TreeInstance[] trees = terrain.terrainData.treeInstances;
if (trees.Length == 0) return Vector3.zero;
return Vector3.Scale(trees[treeIndex].position, terrain.terrainData.size) + terrain.transform.position;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 72d78a56f36fb95459686acf834c58fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using UnityEngine;
namespace DestroyIt
{
public static class Vector3Extensions
{
public static Vector3 LerpByDistance(this Vector3 startPoint, Vector3 endPoint, float distance)
{
Vector3 point = distance * Vector3.Normalize(endPoint - startPoint) + startPoint;
return point;
}
public static Vector3 ClosestDirection(this Vector3 vector)
{
Vector3[] compass = new Vector3[]{Vector3.left, Vector3.right, Vector3.forward, Vector3.back, Vector3.up, Vector3.down};
Vector3 closestDirection = Vector3.zero;
float maxDot = -(Mathf.Infinity);
foreach (Vector3 direction in compass)
{
float dot = Vector3.Dot(vector, direction);
if (dot > maxDot) {
closestDirection = direction;
maxDot = dot;
}
}
return closestDirection;
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c0d0a5f4fc450344babc3dcafcf802fd
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: