Files
BlueArchiveMiniGame/Assets/ThirdParty/DestroyIt/Scripts/Runtime/Behaviors/StructuralSupport.cs
2025-09-17 18:56:28 +08:00

159 lines
7.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
// ReSharper disable once InlineOutVariableDeclaration
namespace DestroyIt
{
/// <summary>
/// Put this script on a destroyed object that has debris pieces you want to stay connected together with joints. (Note: if the object is a prefab, unpack it first.)
/// The joints this script creates will give your destroyed object's debris pieces structural support until they are destroyed or knocked off by force.
/// </summary>
public class StructuralSupport : MonoBehaviour
{
[Tooltip("This is the maximum distance allowed to make a structural support connection. Reduce it if you're getting pieces that float in the air and defy physics. Increase it if too many pieces aren't connecting when they should be.")]
public float maxConnectionDistance = 1.25f;
[Tooltip("The force required to break a joint on the structure. Set to -1 for Infinity.")]
public float breakForce = 1250f;
[Tooltip("The torque required to break a joint on the structure. Set to -1 for Infinity.")]
public float breakTorque = 3000f;
private class StructuralPiece
{
public GameObject GameObject { get; set; }
public Rigidbody Rigidbody { get; set; }
public Vector3 CenterPoint { get; set; }
}
public void FixedUpdate()
{
// Inspect all the joints during every physics loop and remove any that have missing connected rigidbodies
FixedJoint[] joints = gameObject.GetComponentsInChildren<FixedJoint>();
bool jointsRemoved = false;
for (int i = 0; i < joints.Length; i++)
{
if (joints[i].connectedBody == null)
{
Destroy(joints[i]); // remove the joint
jointsRemoved = true;
}
}
// If any joints were removed, wake up the rigidbodies on the object
if (jointsRemoved)
{
Rigidbody[] rbodies = gameObject.GetComponentsInChildren<Rigidbody>();
foreach (Rigidbody rbody in rbodies)
rbody.WakeUp();
}
}
[ExecuteInEditMode]
public void AddStructuralSupport()
{
#if UNITY_EDITOR
List<StructuralPiece> pieces = new List<StructuralPiece>();
List<StructuralPiece> otherPieces = new List<StructuralPiece>();
// First, get a list of all rigidbodies on the object
List<Rigidbody> rbodies = gameObject.GetComponentsInChildren<Rigidbody>().ToList();
// Clear off any old joints on the object so we can create new joint connections.
foreach (var comp in gameObject.GetComponentsInChildren<Component>())
{
if (comp is Joint)
DestroyImmediate(comp);
}
// Next, get the mesh centerpoint of each rigidbody's game object
foreach (Rigidbody rbody in rbodies)
{
Vector3 center = Vector3.zero;
foreach (Collider coll in rbody.gameObject.GetComponentsInChildren<Collider>())
center = center != Vector3.zero ? Vector3.Lerp(center, coll.bounds.center, 0.5f) : coll.bounds.center;
pieces.Add(new StructuralPiece {
GameObject = rbody.gameObject,
Rigidbody = rbody,
CenterPoint = center
});
otherPieces.Add(new StructuralPiece {
GameObject = rbody.gameObject,
Rigidbody = rbody,
CenterPoint = center
});
}
// Now, for each piece, try to linecast from the center of it to the center of every other piece.
foreach (StructuralPiece piece in pieces)
{
for (int i = 0; i < otherPieces.Count; i++)
{
// skip if this piece is trying to linecast to itself.
if (piece.GameObject.GetInstanceID() == otherPieces[i].GameObject.GetInstanceID()) continue;
// If the connection distance is farther than our threshold will allow, exit.
if (Vector3.Distance(piece.CenterPoint, otherPieces[i].CenterPoint) > maxConnectionDistance) continue;
// Capture the layer this object is on, and move it to the IgnoreRaycast layer temporarily before doing the linecast, so it ignores itself.
int originalLayer = piece.GameObject.layer;
int ignoreLayer = LayerMask.NameToLayer("Ignore Raycast");
piece.GameObject.SetLayerRecursively(ignoreLayer);
// If we hit a collider while linecasting to the other piece, check it and see if it IS the other piece. If so, that means it's adjacent, and we want to attach a joint connecting the two.
RaycastHit hitInfo;
if (Physics.Linecast(piece.CenterPoint, otherPieces[i].CenterPoint, out hitInfo))
{
if (hitInfo.collider.attachedRigidbody == otherPieces[i].Rigidbody)
{
//Debug.Log($"{piece.GameObject.name} is connected to {otherPieces[i].GameObject.name}");
for (int j=0; j<8; j++)
Debug.DrawLine(piece.CenterPoint, otherPieces[i].CenterPoint, Color.green, 10f);
Vector3 midPoint = Vector3.Lerp(piece.CenterPoint, otherPieces[i].CenterPoint, 0.5f);
if (breakForce <= -0.1f)
breakForce = Single.PositiveInfinity;
if (breakTorque <= -0.1f)
breakTorque = Single.PositiveInfinity;
piece.GameObject.AddStiffJoint(otherPieces[i].Rigidbody, midPoint, Vector3.zero, breakForce, breakTorque);
}
}
// Set the layer on the piece back to what it was originally
piece.GameObject.SetLayerRecursively(originalLayer);
}
// Remove this piece from the "other pieces" to check
otherPieces.RemoveAll(x => x.GameObject.GetInstanceID() == piece.GameObject.GetInstanceID());
}
#endif
}
[ExecuteInEditMode]
public void RemoveStructuralSupport()
{
#if UNITY_EDITOR
FixedJoint[] joints = gameObject.GetComponentsInChildren<FixedJoint>();
for (int i = 0; i < joints.Length; i++)
DestroyImmediate(joints[i]); // remove the joint
#endif
}
}
public static class LayerExtensions
{
public static void SetLayerRecursively(this GameObject obj, int layer)
{
obj.layer = layer;
foreach (Transform child in obj.transform)
child.gameObject.SetLayerRecursively(layer);
}
}
}