using UnityEngine; [DisallowMultipleComponent] [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(CapsuleCollider))] public class BobberBuoyancyStable : MonoBehaviour { [Header("Water")] public float waterLevelY = 0f; [Tooltip("必须至少浸入这么深才开始产生浮力(防止还没入水就被顶)")] public float enterWaterDepth = 0.003f; // 3mm(按你的尺度改) [Tooltip("在这个深度范围内做平滑过渡(越大越软)")] public float smoothDepth = 0.02f; [Header("Buoyancy Spring")] public float buoyancySpring = 30f; public float buoyancyDamping = 8f; [Tooltip("最大上浮加速度限制(0=不限制)")] public float maxUpAcceleration = 0f; [Header("Water Drag")] public float extraLinearDampingInWater = 2f; public float extraAngularDampingInWater = 2f; [Header("Center Of Mass")] public bool driveCenterOfMassFromCapsule = true; public Vector3 extraCenterOfMassOffset = new Vector3(0f, -0.01f, 0f); [Header("Righting")] public float rightingTorque = 1.5f; public float rightingDamping = 0.5f; Rigidbody rb; CapsuleCollider cap; float airLinearDamping; float airAngularDamping; void Awake() { rb = GetComponent(); cap = GetComponent(); rb.useGravity = true; airLinearDamping = rb.linearDamping; airAngularDamping = rb.angularDamping; ApplyCenterOfMass(); rb.maxAngularVelocity = 50f; } void FixedUpdate() { ApplyCenterOfMass(); Bounds b = cap.bounds; float bottomY = b.min.y; float topY = b.max.y; // 用“底部点”判定是否真正入水(必须超过阈值) float bottomSubmersion = waterLevelY - bottomY; // >0 表示底部在水下 if (bottomSubmersion <= enterWaterDepth) { // 认为未入水:不施加浮力,恢复空气阻尼 rb.linearDamping = airLinearDamping; rb.angularDamping = airAngularDamping; return; } // 进入水中:阻尼随浸入增强 // 这里用一个0~1的平滑权重,避免刚入水就“猛顶” float w = Smooth01((bottomSubmersion - enterWaterDepth) / Mathf.Max(1e-4f, smoothDepth)); rb.linearDamping = airLinearDamping + extraLinearDampingInWater * w; rb.angularDamping = airAngularDamping + extraAngularDampingInWater * w; // 垂直速度(用刚体自身速度就够稳定) float vY = rb.linearVelocity.y; // 弹簧+阻尼浮力(仅向上) float forceY = buoyancySpring * bottomSubmersion - buoyancyDamping * vY; if (forceY < 0f) forceY = 0f; // 平滑权重:刚入水时逐渐接管 forceY *= w; // 限制最大上浮加速度(可选) if (maxUpAcceleration > 0f) { float maxForce = rb.mass * maxUpAcceleration; if (forceY > maxForce) forceY = maxForce; } // 浮力作用点:必须放在水面下(否则会出现奇怪力矩) float buoyY = Mathf.Min(waterLevelY - 0.001f, topY); // 强制在水面下1mm buoyY = Mathf.Max(buoyY, bottomY); // 不低于底部 Vector3 buoyPoint = new Vector3(b.center.x, buoyY, b.center.z); rb.AddForceAtPosition(Vector3.up * forceY, buoyPoint, ForceMode.Force); // 归正扭矩(只在水里生效) Vector3 up = transform.up; Vector3 axis = Vector3.Cross(up, Vector3.up); float mag = axis.magnitude; if (mag > 1e-4f) { axis /= mag; float angle = Mathf.Asin(Mathf.Clamp(mag, -1f, 1f)); float angVelOnAxis = Vector3.Dot(rb.angularVelocity, axis); float torque = (rightingTorque * angle - rightingDamping * angVelOnAxis) * w; rb.AddTorque(axis * torque, ForceMode.Acceleration); } } void ApplyCenterOfMass() { if (!driveCenterOfMassFromCapsule) return; rb.centerOfMass = cap.center + extraCenterOfMassOffset; } static float Smooth01(float t) { t = Mathf.Clamp01(t); // smoothstep return t * t * (3f - 2f * t); } }