using UnityEngine; using System.Collections; using System.Collections.Generic; public class ClawBubble : MonoBehaviour { public ClawBubbleType type; private Rigidbody rb; public MeshRenderer bottomMeshRenderer; // ---------- Non-physics separation settings ---------- [Header("Non-Physics Separation")] [Tooltip("Enable separation without physics collisions.")] public bool enableSeparation = true; [Tooltip("Minimum center-to-center distance you want between balls (world units).")] public float minDistance = 0.28f; // tweak to your ball size [Tooltip("Max frames to try separating on spawn (0 = skip on spawn).")] public int relaxFramesOnSpawn = 10; // try a few frames right after spawn [Tooltip("Max frames to try separating after landing on Carousel.")] public int relaxFramesOnLand = 20; [Tooltip("How strongly to push each frame (larger = faster, but can jitter).")] public float pushStrength = 0.6f; [Tooltip("Clamp to avoid big jumps per frame.")] public float maxPushPerFrame = 0.06f; // Registry of all live bubbles (for neighbor checks) private static readonly List All = new List(); private void Awake() { rb = GetComponent(); } private void OnEnable() { if (!All.Contains(this)) All.Add(this); // Optional: a tiny relax pass right after spawn (before landing) if (enableSeparation && relaxFramesOnSpawn > 0) StartCoroutine(RelaxOverlap(frames: relaxFramesOnSpawn, onlyWithSameParent: false)); } private void OnDisable() { All.Remove(this); } void FreezeAllConstraints() { if (rb) rb.constraints = RigidbodyConstraints.FreezeAll; } public void LaunchUp(float force) { if (!rb) return; rb.velocity = Vector3.zero; rb.AddForce(Vector3.up * force, ForceMode.Impulse); } private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Carousel")) { transform.parent = collision.transform; FreezeAllConstraints(); isKinematicor(true); // After we “stick” to the Carousel, run a separation pass if (enableSeparation && relaxFramesOnLand > 0) StartCoroutine(RelaxOverlap(frames: relaxFramesOnLand, onlyWithSameParent: true)); } } public void isKinematicor(bool flag) { if (rb) rb.isKinematic = flag; } // ------------------------------------------------------- // Non-physics relaxation: resolves overlaps on the XZ plane // ------------------------------------------------------- private IEnumerator RelaxOverlap(int frames, bool onlyWithSameParent) { // Small stagger to avoid every ball moving in the same frame yield return null; float minSqr = minDistance * minDistance; for (int f = 0; f < frames; f++) { Vector3 pos = transform.position; Vector3 totalPush = Vector3.zero; for (int i = 0; i < All.Count; i++) { var other = All[i]; if (!other || other == this) continue; if (onlyWithSameParent && other.transform.parent != transform.parent) continue; Vector3 delta = pos - other.transform.position; delta.y = 0f; // only separate on the platform plane float sqr = delta.sqrMagnitude; if (sqr < 0.000001f) continue; if (sqr < minSqr) { float dist = Mathf.Sqrt(sqr); float needed = minDistance - dist; // how much more spacing we need Vector3 dir = delta / dist; // normalized on XZ totalPush += dir * needed; } } if (totalPush.sqrMagnitude > 0f) { // Clamp and scale the push so we don’t jump too far if (totalPush.magnitude > maxPushPerFrame) totalPush = totalPush.normalized * maxPushPerFrame; transform.position += totalPush * pushStrength; } else { // Already clear; stop early break; } yield return null; } } }