194 lines
5.9 KiB
C#
194 lines
5.9 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
public class GlassRingController : MonoBehaviour
|
|
{
|
|
[Header("Auto Setup")]
|
|
public bool autoCollectOnAwake = true;
|
|
public bool includeInactive = true;
|
|
public float pieceMaxHealth = 100f;
|
|
|
|
[Header("Rotation")]
|
|
public float rotationSpeedDegrees = 45f;
|
|
|
|
[Header("Audio")]
|
|
public AudioSource audioSource;
|
|
public AudioClip damageClip;
|
|
public AudioClip breakClip;
|
|
[Range(0f, 1f)] public float damageVolume = 0.7f;
|
|
[Range(0f, 1f)] public float breakVolume = 1f;
|
|
|
|
private GlassPiece[] pieces;
|
|
public GlassPiece[] Pieces => pieces;
|
|
|
|
private void Awake()
|
|
{
|
|
if (audioSource == null)
|
|
audioSource = GetComponent<AudioSource>();
|
|
|
|
if (audioSource == null)
|
|
audioSource = gameObject.AddComponent<AudioSource>();
|
|
|
|
if (Application.isPlaying && autoCollectOnAwake)
|
|
{
|
|
CollectPiecesFromChildren();
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!Application.isPlaying || Mathf.Abs(rotationSpeedDegrees) < 0.001f)
|
|
return;
|
|
|
|
transform.Rotate(Vector3.right, rotationSpeedDegrees * Time.deltaTime, Space.World);
|
|
}
|
|
|
|
[ContextMenu("Collect Pieces From Children")]
|
|
public void CollectPiecesFromChildren()
|
|
{
|
|
List<GlassPiece> foundPieces = new List<GlassPiece>();
|
|
|
|
Transform[] allChildren = GetComponentsInChildren<Transform>(includeInactive);
|
|
|
|
foreach (Transform t in allChildren)
|
|
{
|
|
if (t == transform) continue;
|
|
|
|
Renderer rend = t.GetComponent<Renderer>();
|
|
if (rend == null)
|
|
rend = t.GetComponentInChildren<Renderer>();
|
|
|
|
if (rend == null)
|
|
continue;
|
|
|
|
Collider col = t.GetComponent<Collider>();
|
|
|
|
bool invalidMeshCollider = false;
|
|
MeshCollider existingMeshCollider = col as MeshCollider;
|
|
if (existingMeshCollider != null && existingMeshCollider.sharedMesh == null)
|
|
{
|
|
invalidMeshCollider = true;
|
|
}
|
|
|
|
if (col == null || invalidMeshCollider)
|
|
{
|
|
if (invalidMeshCollider)
|
|
Destroy(existingMeshCollider);
|
|
|
|
BoxCollider box = t.GetComponent<BoxCollider>();
|
|
if (box == null)
|
|
box = t.gameObject.AddComponent<BoxCollider>();
|
|
|
|
Bounds worldBounds = rend.bounds;
|
|
Vector3 localCenter = t.InverseTransformPoint(worldBounds.center);
|
|
|
|
box.center = localCenter;
|
|
|
|
Vector3 lossy = t.lossyScale;
|
|
box.size = new Vector3(
|
|
worldBounds.size.x / Mathf.Max(lossy.x, 0.0001f),
|
|
worldBounds.size.y / Mathf.Max(lossy.y, 0.0001f),
|
|
worldBounds.size.z / Mathf.Max(lossy.z, 0.0001f)
|
|
);
|
|
|
|
col = box;
|
|
}
|
|
|
|
GlassPiece piece = t.GetComponent<GlassPiece>();
|
|
if (piece == null)
|
|
piece = t.gameObject.AddComponent<GlassPiece>();
|
|
|
|
Rigidbody rb = t.GetComponent<Rigidbody>();
|
|
if (rb == null)
|
|
rb = t.gameObject.AddComponent<Rigidbody>();
|
|
|
|
rb.isKinematic = true;
|
|
rb.useGravity = false;
|
|
|
|
foundPieces.Add(piece);
|
|
}
|
|
|
|
foundPieces.Sort((a, b) =>
|
|
{
|
|
float angleA = Mathf.Atan2(a.transform.localPosition.z, a.transform.localPosition.x);
|
|
float angleB = Mathf.Atan2(b.transform.localPosition.z, b.transform.localPosition.x);
|
|
return angleA.CompareTo(angleB);
|
|
});
|
|
|
|
pieces = foundPieces.ToArray();
|
|
|
|
for (int i = 0; i < pieces.Length; i++)
|
|
{
|
|
pieces[i].Initialize(i, pieceMaxHealth);
|
|
}
|
|
|
|
Debug.Log($"GlassRingController: collected {pieces.Length} pieces.");
|
|
}
|
|
|
|
public void ApplyProjectileImpact(int hitIndex, ProjectileBehaviour projectile, float charge01, Vector3 hitPoint)
|
|
{
|
|
if (pieces == null || pieces.Length == 0) return;
|
|
|
|
float sigma = Mathf.Max(0.01f, projectile.sigma);
|
|
float chargeMultiplier = 0.55f + 1.45f * Mathf.Pow(charge01, 1.4f);
|
|
|
|
bool anyHealthChanged = false;
|
|
bool anyNewBreak = false;
|
|
|
|
for (int i = 0; i < pieces.Length; i++)
|
|
{
|
|
GlassPiece piece = pieces[i];
|
|
if (piece == null || piece.IsBroken) continue;
|
|
|
|
int d = CircularDistance(i, hitIndex, pieces.Length);
|
|
float gaussian = Mathf.Exp(-(d * d) / (2f * sigma * sigma));
|
|
|
|
if (gaussian < 0.005f) continue;
|
|
|
|
float damage = projectile.baseDamage * chargeMultiplier * gaussian;
|
|
if (i == hitIndex)
|
|
damage *= projectile.directHitMultiplier;
|
|
|
|
Vector3 impulseDir = piece.transform.position - hitPoint;
|
|
if (impulseDir.sqrMagnitude < 0.0001f)
|
|
impulseDir = piece.transform.position - transform.position;
|
|
|
|
float impulse = projectile.breakImpulse * chargeMultiplier * gaussian;
|
|
if (i == hitIndex)
|
|
impulse *= projectile.directHitMultiplier;
|
|
|
|
bool brokeNow = piece.ApplyDamage(damage, hitPoint, impulseDir, impulse);
|
|
|
|
anyHealthChanged = true;
|
|
if (brokeNow)
|
|
anyNewBreak = true;
|
|
}
|
|
|
|
if (anyNewBreak)
|
|
{
|
|
PlayBreakSound();
|
|
}
|
|
else if (anyHealthChanged)
|
|
{
|
|
PlayDamageSound();
|
|
}
|
|
}
|
|
|
|
private void PlayDamageSound()
|
|
{
|
|
if (audioSource != null && damageClip != null)
|
|
audioSource.PlayOneShot(damageClip, damageVolume);
|
|
}
|
|
|
|
private void PlayBreakSound()
|
|
{
|
|
if (audioSource != null && breakClip != null)
|
|
audioSource.PlayOneShot(breakClip, breakVolume);
|
|
}
|
|
|
|
private int CircularDistance(int a, int b, int count)
|
|
{
|
|
int raw = Mathf.Abs(a - b);
|
|
return Mathf.Min(raw, count - raw);
|
|
}
|
|
} |