using UnityEngine; using UnityEditor; /** * Free body 설정, * 각각의 Radius는 값이 크면, 묵직하고 관성이 강해짐, 외부 힘에 덜 민감하고, 흔들리더라도 천천히 크게 출렁이며 진동이 오래 남아. 가슴의 중심부에 배치해서 무거운 느낌을 줄 때 사용. * 값이 작으면, 매우 가볍고 외부 힘에 민감하지만 관성이 없어 금방 제자리로 돌아오려고 함. Kinematic 바디는 아니지만, 고정된 경계 근처에 배치해서 그 부분의 움직임을 최소화하고 싶을 때 사용. * */ [ExecuteInEditMode] [RequireComponent(typeof(JellySprite))] public class BreastJigglePhysics : MonoBehaviour { [Header("=== JellySprite 물리 설정 ===")] [Tooltip("JellySprite 물리 속성을 이 스크립트 값으로 자동 적용. (기본: True)")] public bool autoConfigureJellySprite = true; [Header("JellySprite 강성/감쇠 설정")] [Tooltip("스프링 강성. 높을수록 단단하고 빠르게 진동. 낮을수록 물렁하고 느림.")] //높게 (3.0 이상): 단단하고 탄력 있는(Firm) 느낌. 진동 주기가 빠르고 출렁임이 작음. //낮게(1.5 이하) : 물렁하고 부드러운(Soft/Jelly) 느낌.출렁임이 느리고 크게 늘어남. [Range(0.5f, 5f)] public float stiffness = 2.2f; [Tooltip("감쇠 계수. 높을수록 흔들림이 빨리 멈춤. 낮을수록 오래 지속.")] //높게 (0.5 이상): 흔들림이 빨리 멈추고 잔진동이 적어 깔끔함. //낮게 (0.3 이하): 흔들림이 오래 지속되고 잔진동이 많아 물컹거리는 느낌이 강함. [Range(0f, 1f)] public float dampingRatio = 0.4f; [Header("JellySprite 질량/중력/드래그")] [Tooltip("전체 질량. 높을수록 둔하고, 낮을수록 민감하게 반응.(높을스록 흔들림 주기가 길어짐)")] //높게 (1.0 이상): 관성이 커져 외부 힘에 둔하게 반응하며, 흔들림 주기가 길어짐. //낮게 (0.5 이하): 외부 힘에 민감하게 반응하고, 가벼워 보임. [Range(0.1f, 2f)] public float mass = 0.8f; [Tooltip("중력 배율. 높을수록 아래로 처지는(Sag) 느낌이 강해짐.")] //높게 (0.5 이상): 아래로 처지는(Sag) 효과가 강해져 더 육중한 느낌을 줌. //0에 가깝게: 처짐 효과가 거의 없어 매우 가볍게 출렁거림. [Range(0f, 2f)] public float gravityScale = 0f; //0.25f; [Tooltip("이동 시 공기 저항(선형 드래그). 움직임 전반을 둔화시킴.")] //높게: 움직임이 전반적으로 둔화되고 속도가 느려짐. 물리 폭주를 막는 보조 수단으로도 활용. [Range(0f, 2f)] public float drag = 0f; //0.35f; [Tooltip("회전 저항(Angular Drag). 회전 움직임을 둔화시킴.")] //높게: 움직임이 전반적으로 둔화되고 속도가 느려짐. 물리 폭주를 막는 보조 수단으로도 활용. [Range(0f, 2f)] public float angularDrag = 0.6f; [Header("=== Player(Blade) 흔들림 설정 ===")] [Tooltip("Player(터치) 충돌 시 가슴에 가해지는 힘의 강도. 높을수록 크게 출렁임.")] //높게: 외부 터치에 가슴이 더 크게 밀려나고 출렁이게 한다. (강한 상호작용) [Range(1f, 30f)] public float playerJiggleStrength = 26f; [Tooltip("Player 흔들림 힘에 적용되는 랜덤 변동 비율. (0.3 이상 권장)")] //높게 (0.3 이상): 충돌마다 가해지는 힘의 크기가 불규칙해져 매번 다른 흔들림을 연출하여 자연스러움을 높인다. [Range(0f, 1f)] public float randomVariation = 0.5f; [Header("흔들림 방향 설정")] [Tooltip("흔들림 발생 시 좌우로 퍼지는 범위. 0이면 수직으로만 흔들림. (0.2~0.5 권장)")] //적절한 양수 (0.2~0.5): 힘이 좌우로도 퍼져서 흔들림이 수직을 넘어 옆으로도 퍼지게 하여 더 풍부한 움직임을 만든다. [Range(-1f, 1f)] public float horizontalSpread = 0.5f; [Tooltip("위아래 편향. 높을수록 아래로 힘이 쏠려 처짐/출렁임을 강조. (1.0 이상 권장)")] //높게 (1.0 이상): 힘의 방향이 아래쪽으로 강하게 쏠려 중력에 의한 처짐과 아래 방향 출렁임을 강조한다. (자연스러운 처짐 연출) [Range(0f, 3f)] public float verticalBias = 0.8f; [Header("=== Ball 흔들림 설정 (물리엔진 사용) ===")] [Tooltip("Ball 충돌 시 기본 물리 반응에 더해지는 추가 힘.")] //0 이상: Ball 충돌 시 기본 물리 반응에 이 추가 힘을 더해 더욱 강력한 충격 반응을 줄 수 있다. [Range(0f, 30f)] public float ballJiggleStrength = 26f; [Tooltip("충돌 지점 반발력이 전체 힘에 미치는 영향도. 높을수록 밀리는 느낌이 강해짐.")] //높게 (1.5 이상): 충돌한 지점의 반발력이 전체 힘 방향에 강하게 반영되도록 하여, verticalBias보다 국소적인 반응을 강조한다. [Range(0f, 5f)] public float contactPointInfluence = 3f; [Header("=== 힘 제한 ===")] [Tooltip("JellySprite에 가할 수 있는 최대 힘 제한. 물리 폭주 방지용. (10.0~20.0 권장)")] //양수 (10.0~20.0): 값이 너무 크면 물리 폭주로 가슴 메쉬가 과도하게 늘어나거나 튕겨 나갈 수 있으므로, 안정적인 값으로 제한한다. public float maxJellyForce = 26f; [Header("=== 충돌 위치 기반 반응 ===")] [Tooltip("충돌 지점 기반의 반발력 방향 사용 여부. (가장 현실적인 반응, 권장: True)")] public bool useContactPointPhysics = true; [Header("=== 디버그 옵션 ===")] [Tooltip("충돌/힘 적용 방향을 Scene 뷰에 시각화")] public bool showDebugGizmos = false; [Tooltip("충돌 및 힘 적용 로그를 콘솔에 표시")] public bool showDebugLog = false; // 연속 충돌 무시 시간 private float minCollisionInterval = 0.18f;// 0.08f;//0.2f; private float lastCollisionTime = -999f; private JellySprite jelly; private Vector2 lastContactPoint; private void Awake() { jelly = GetComponent(); if (jelly == null) { Debug.LogError("JellySprite 컴포넌트를 찾을 수 없습니다!"); return; } if (autoConfigureJellySprite) ApplyJellySpriteSettings(); } /// JellySprite 물리 속성 적용 private void ApplyJellySpriteSettings() { jelly.m_Stiffness = stiffness; jelly.m_DampingRatio = dampingRatio; jelly.m_Mass = mass; jelly.m_GravityScale = gravityScale; jelly.m_Drag = drag; jelly.m_AngularDrag = angularDrag; if (Application.isPlaying) { jelly.InitMass(); jelly.UpdateJoints(); jelly.WakeUp(); } jelly.m_UseGravity = true; /* #if UNITY_EDITOR SerializedObject so = new SerializedObject(jelly); so.Update(); so.ApplyModifiedProperties(); #endif */ if (showDebugLog) Debug.Log($"[BreastJiggle] JellySprite 설정 적용 - Stiffness: {stiffness}, Damping: {dampingRatio}"); } private void OnValidate() { if (jelly != null && autoConfigureJellySprite && Application.isPlaying) ApplyJellySpriteSettings(); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { if (Time.time - lastCollisionTime < minCollisionInterval) { if (showDebugLog) Debug.Log("[BreastJiggle] 충돌 간격이 너무 짧아서 무시됨"); return; } lastCollisionTime = Time.time; //jelly.m_Stiffness = 2.6f; ApplyJellyForceForPlayer(DSUtil.RandomVector(0.01f, 0.1f), playerJiggleStrength); } // 멈추기 실패 //if (Time.time - lastCollisionTime > 1f) //{ // lastCollisionTime = Time.time; // jelly.m_Stiffness = 0f; //} } private void HandleCollision(Collider2D other) { //if (!other.CompareTag("Player") && !other.CompareTag("Ball")) return; if (!other.CompareTag("Player")) return; if (Time.time - lastCollisionTime < minCollisionInterval) { if (showDebugLog) Debug.Log("[BreastJiggle] 충돌 간격이 너무 짧아서 무시됨"); return; } lastCollisionTime = Time.time; Vector2 contactPoint = other.ClosestPoint(transform.position); lastContactPoint = contactPoint; if (showDebugLog) Debug.Log($"[BreastJiggle] 충돌 감지: {other.name} (Tag: {other.tag})"); // Player 터치일 경우 AddForce로 흔들림 적용 if (other.CompareTag("Player")) { ApplyJellyForceForPlayer(contactPoint, playerJiggleStrength); //GameManager.Instance.StartRandomReaction(); } /* // Ball일 경우 물리엔진 적용 + Ball용 힘 추가 if (other.CompareTag("Ball")) { ApplyJellyForceForBall(contactPoint, ballJiggleStrength); } */ } /// Player/Blade 전용 힘 적용 private void ApplyJellyForceForPlayer(Vector2 contactPoint, float strength) { Vector2 direction = CalculateJiggleDirection(contactPoint); float force = strength; if (maxJellyForce > 0) force = Mathf.Min(force, maxJellyForce); jelly.AddForce(direction * force); if (showDebugLog) Debug.Log($"[BreastJiggle] Player 힘 적용: {force:F2} (방향: {direction})"); if (showDebugGizmos) Debug.DrawRay(transform.position, direction * force * 0.5f, Color.yellow, 1f); } /// /// Ball이 닿았을 때 AddForce로 가슴에 가해지는 힘, 기본 물리엔진에+Add /// /// /// private void ApplyJellyForceForBall(Vector2 contactPoint, float strength) { if (strength <= 0.0f) return; Vector2 direction = CalculateJiggleDirection(contactPoint); float force = strength; // Ball은 랜덤 variation 없이 일정 힘 if (maxJellyForce > 0) force = Mathf.Min(force, maxJellyForce); jelly.AddForce(direction * force); if (showDebugLog) Debug.Log($"[BreastJiggle] Ball 힘 적용: {force:F2} (방향: {direction})"); if (showDebugGizmos) Debug.DrawRay(transform.position, direction * force * 0.5f, Color.green, 1f); } /// 충돌 지점 기반 힘 방향 계산 private Vector2 CalculateJiggleDirection(Vector2 contactPoint) { if (useContactPointPhysics) { Vector2 offset = contactPoint - (Vector2)transform.position; Vector2 pushDir = -offset.normalized; Vector2 gravityBiasVec = Vector2.down * verticalBias; float horizWave = Random.Range(-horizontalSpread, horizontalSpread); Vector2 lateralMove = Vector2.right * horizWave; return (pushDir * contactPointInfluence + gravityBiasVec + lateralMove).normalized; } else { return new Vector2(Random.Range(-horizontalSpread, horizontalSpread), -verticalBias + Random.Range(-0.3f, 0.2f)).normalized; } } /// /// Player(Blade)와 충돌시 이벤트 발생 /// /// void OnJellyTriggerEnter2D(JellySprite.JellyCollider2D trigger) => HandleCollision(trigger.Collider2D); /// /// Ball과 충돌시 이벤트 발생 /// /// void OnJellyCollisionEnter2D(JellySprite.JellyCollision2D collision) => HandleCollision(collision.Collision2D.collider); private void OnDrawGizmos() { if (!showDebugGizmos || !Application.isPlaying) return; Gizmos.color = Color.red; Gizmos.DrawWireSphere(lastContactPoint, 0.15f); Gizmos.color = Color.blue; Gizmos.DrawWireSphere(transform.position, 0.1f); Gizmos.color = Color.cyan; Gizmos.DrawLine(transform.position, lastContactPoint); } }