From 01a1c1c34188072839f183a9ffedb889a132ae79 Mon Sep 17 00:00:00 2001 From: BobSong <605277374@qq.com> Date: Sun, 1 Mar 2026 08:51:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=AE=E6=BC=82=E6=B5=8B=E8=AF=95=E4=BB=A3?= =?UTF-8?q?=E7=A0=811?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scenes/BobberTest.unity | 11 +- Assets/Scripts/Test/BobberBuoyancyStable.cs | 111 +++++++++---- .../Test/BobberBuoyancyStable_MultiPoint.cs | 155 ++++++++++++++++++ .../BobberBuoyancyStable_MultiPoint.cs.meta | 3 + 4 files changed, 246 insertions(+), 34 deletions(-) create mode 100644 Assets/Scripts/Test/BobberBuoyancyStable_MultiPoint.cs create mode 100644 Assets/Scripts/Test/BobberBuoyancyStable_MultiPoint.cs.meta diff --git a/Assets/Scenes/BobberTest.unity b/Assets/Scenes/BobberTest.unity index 56431a87b..4c660da45 100644 --- a/Assets/Scenes/BobberTest.unity +++ b/Assets/Scenes/BobberTest.unity @@ -193,7 +193,7 @@ Rigidbody: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 154764976} serializedVersion: 5 - m_Mass: 0.01 + m_Mass: 0.1 m_LinearDamping: 0 m_AngularDamping: 0.05 m_CenterOfMass: {x: 0, y: 0, z: 0} @@ -250,15 +250,16 @@ MonoBehaviour: waterLevelY: 0 enterWaterDepth: 0.003 smoothDepth: 0.02 - buoyancySpring: 30 + buoyancySpring: 100 buoyancyDamping: 8 maxUpAcceleration: 0 extraLinearDampingInWater: 2 extraAngularDampingInWater: 2 - driveCenterOfMassFromCapsule: 1 - extraCenterOfMassOffset: {x: 0, y: -0.01, z: 0} + driveCenterOfMassFromCapsule: 0 + extraCenterOfMassOffset: {x: 0, y: 0, z: 0} rightingTorque: 1.5 rightingDamping: 0.5 + buoyancyOffset: 0.02 --- !u!1 &203844586 GameObject: m_ObjectHideFlags: 0 @@ -3419,7 +3420,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 0 + m_IsActive: 1 --- !u!1 &1065788238234039155 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Test/BobberBuoyancyStable.cs b/Assets/Scripts/Test/BobberBuoyancyStable.cs index b8a008b6e..f925a2b2d 100644 --- a/Assets/Scripts/Test/BobberBuoyancyStable.cs +++ b/Assets/Scripts/Test/BobberBuoyancyStable.cs @@ -27,11 +27,11 @@ public class BobberBuoyancyStable : MonoBehaviour [Header("Center Of Mass")] public bool driveCenterOfMassFromCapsule = true; - public Vector3 extraCenterOfMassOffset = new Vector3(0f, -0.01f, 0f); + public Vector3 extraCenterOfMassOffset = new Vector3(0f, -0.01f, 0f); // 恢复原来的重心下移 [Header("Righting")] - public float rightingTorque = 1.5f; - public float rightingDamping = 0.5f; + public float rightingTorque = 3f; // 适中的归正扭矩 + public float rightingDamping = 0.8f; Rigidbody rb; CapsuleCollider cap; @@ -50,6 +50,7 @@ public class BobberBuoyancyStable : MonoBehaviour ApplyCenterOfMass(); rb.maxAngularVelocity = 50f; + // 移除了强制设置物理参数的代码,保留用户在Inspector中的设置 } void FixedUpdate() @@ -60,57 +61,76 @@ public class BobberBuoyancyStable : MonoBehaviour float bottomY = b.min.y; float topY = b.max.y; - // 用“底部点”判定是否真正入水(必须超过阈值) - float bottomSubmersion = waterLevelY - bottomY; // >0 表示底部在水下 + // 用"底部点"判定是否真正入水 + float bottomSubmersion = waterLevelY - bottomY; 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; - - // 限制最大上浮加速度(可选) + // 关键修正:正确的浮力计算 + float volume = Mathf.PI * cap.radius * cap.radius * cap.height; + float submergedVolume = volume * Mathf.Clamp01(bottomSubmersion / cap.height); + + // 浮力 = 排开液体重量 = 体积 × 密度 × 重力 + float buoyantForce = submergedVolume * 1000f * 9.81f; + + // 物体重量 + float weight = rb.mass * 9.81f; + + // 净浮力(浮力 - 重量) + float netBuoyancy = buoyantForce - weight; + + // 添加弹簧阻尼系统 + float velocity = Vector3.Dot(rb.linearVelocity, Vector3.up); + float springForce = buoyancySpring * bottomSubmersion; + float dampingForce = buoyancyDamping * velocity; + + float totalForce = netBuoyancy + springForce - dampingForce; + totalForce *= w; // 平滑过渡 + + // 限制向上的力 + if (totalForce < 0) totalForce = 0; + + // 限制最大加速度 if (maxUpAcceleration > 0f) { float maxForce = rb.mass * maxUpAcceleration; - if (forceY > maxForce) forceY = maxForce; + if (totalForce > maxForce) totalForce = maxForce; } - // 浮力作用点:必须放在水面下(否则会出现奇怪力矩) - float buoyY = Mathf.Min(waterLevelY - 0.001f, topY); // 强制在水面下1mm - buoyY = Mathf.Max(buoyY, bottomY); // 不低于底部 + // 浮力作用点 + float buoyY = Mathf.Min(waterLevelY - 0.001f, topY); + buoyY = Mathf.Max(buoyY, bottomY); Vector3 buoyPoint = new Vector3(b.center.x, buoyY, b.center.z); - rb.AddForceAtPosition(Vector3.up * forceY, buoyPoint, ForceMode.Force); + rb.AddForceAtPosition(Vector3.up * totalForce, buoyPoint, ForceMode.Force); - // 归正扭矩(只在水里生效) + // 简化的归正扭矩系统 + SimpleRightingSystem(w); + } + + void SimpleRightingSystem(float weight) + { 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 angle = Mathf.Asin(Mathf.Clamp(mag, -1f, 1f)) * Mathf.Rad2Deg; float angVelOnAxis = Vector3.Dot(rb.angularVelocity, axis); - float torque = (rightingTorque * angle - rightingDamping * angVelOnAxis) * w; + + // 归正扭矩 + float torque = (rightingTorque * angle - rightingDamping * angVelOnAxis) * weight; rb.AddTorque(axis * torque, ForceMode.Acceleration); } } @@ -124,7 +144,40 @@ public class BobberBuoyancyStable : MonoBehaviour static float Smooth01(float t) { t = Mathf.Clamp01(t); - // smoothstep return t * t * (3f - 2f * t); } -} \ No newline at end of file + + void OnDrawGizmos() + { + if (cap == null) return; + + Gizmos.color = Color.blue; + Bounds bounds = cap.bounds; + + // 绘制浸入部分 + float bottomY = bounds.min.y; + float submergedHeight = Mathf.Max(0, waterLevelY - bottomY); + + Vector3 submergedCenter = new Vector3( + bounds.center.x, + bottomY + submergedHeight * 0.5f, + bounds.center.z + ); + + Vector3 submergedSize = new Vector3( + bounds.size.x, + submergedHeight, + bounds.size.z + ); + + Gizmos.DrawWireCube(submergedCenter, submergedSize); + + // 显示重心 + if (rb != null) + { + Gizmos.color = Color.red; + Vector3 comWorld = transform.TransformPoint(rb.centerOfMass); + Gizmos.DrawSphere(comWorld, 0.005f); + } + } +} diff --git a/Assets/Scripts/Test/BobberBuoyancyStable_MultiPoint.cs b/Assets/Scripts/Test/BobberBuoyancyStable_MultiPoint.cs new file mode 100644 index 000000000..a4580161f --- /dev/null +++ b/Assets/Scripts/Test/BobberBuoyancyStable_MultiPoint.cs @@ -0,0 +1,155 @@ +using UnityEngine; + +[DisallowMultipleComponent] +[RequireComponent(typeof(Rigidbody))] +[RequireComponent(typeof(CapsuleCollider))] +public class BobberBuoyancyStable_MultiPoint : MonoBehaviour +{ + [Header("Water")] + public float waterLevelY = 0f; + public float enterWaterDepth = 0.003f; + public float smoothDepth = 0.02f; + + [Header("Per-Point Buoyancy Spring (IMPORTANT)")] + [Tooltip("每个采样点的弹簧系数(N/m)。总浮力刚度≈ringPoints * perPointSpring")] + public float perPointSpring = 60f; + + [Tooltip("每个采样点的阻尼(N·s/m),用点的竖直速度做阻尼")] + public float perPointDamping = 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; + + [Tooltip("想更容易躺漂:把y调成正数(上移重心)。想更站漂:y调成负数(下移重心)。")] + public Vector3 extraCenterOfMassOffset = new Vector3(0f, 0.00f, 0f); + + [Header("Sample Ring")] + [Range(4, 12)] public int ringPoints = 4; + [Range(0.2f, 1.2f)] public float ringRadiusScale = 0.9f; + + [Tooltip("采样环离最低点的高度(米)。0=贴底;建议 0.001~0.003")] + public float ringLiftFromBottom = 0.0015f; + + [Header("Righting (建议先关)")] + public float rightingTorque = 0f; + public float rightingDamping = 0f; + + Rigidbody rb; + CapsuleCollider cap; + + float airLinearDamping; + float airAngularDamping; + + void Awake() + { + rb = GetComponent(); + cap = GetComponent(); + + airLinearDamping = rb.linearDamping; + airAngularDamping = rb.angularDamping; + + ApplyCenterOfMass(); + rb.maxAngularVelocity = 50f; + } + + void FixedUpdate() + { + ApplyCenterOfMass(); + + // === 1) 用胶囊几何算 bottom/top(不使用 cap.bounds) === + // 注意:这里假设 Transform 的缩放是等比或至少 x/z 缩放差不多。 + Vector3 centerW = transform.TransformPoint(cap.center); + + float scaleY = Mathf.Abs(transform.lossyScale.y); + float scaleX = Mathf.Abs(transform.lossyScale.x); + float scaleZ = Mathf.Abs(transform.lossyScale.z); + float radiusW = cap.radius * Mathf.Max(scaleX, scaleZ); + float halfH_W = (cap.height * 0.5f) * scaleY; + + float bottomY = centerW.y - halfH_W; + float topY = centerW.y + halfH_W; + + // 用“最低点”判定入水 + float bottomSub = waterLevelY - bottomY; + if (bottomSub <= enterWaterDepth) + { + rb.linearDamping = airLinearDamping; + rb.angularDamping = airAngularDamping; + return; + } + + float w = Smooth01((bottomSub - enterWaterDepth) / Mathf.Max(1e-4f, smoothDepth)); + + rb.linearDamping = airLinearDamping + extraLinearDampingInWater * w; + rb.angularDamping = airAngularDamping + extraAngularDampingInWater * w; + + // === 2) 在胶囊底部附近放一圈采样点(姿态力矩来自这里) === + // 环中心:在最低点上抬一点(ringLiftFromBottom) + float ringY = bottomY + ringLiftFromBottom; + Vector3 ringCenter = new Vector3(centerW.x, ringY, centerW.z); + + float ringR = radiusW * ringRadiusScale; + + // 每点加速限制 + float maxForcePerPoint = float.PositiveInfinity; + if (maxUpAcceleration > 0f) + maxForcePerPoint = (rb.mass * maxUpAcceleration) / ringPoints; + + for (int i = 0; i < ringPoints; i++) + { + float a = (i / (float)ringPoints) * Mathf.PI * 2f; + + // 用世界方向的 right/forward(随姿态旋转) + Vector3 offset = (transform.right * Mathf.Cos(a) + transform.forward * Mathf.Sin(a)) * ringR; + Vector3 p = ringCenter + offset; + + float sub = waterLevelY - p.y; + if (sub <= enterWaterDepth) continue; + + float pw = Smooth01((sub - enterWaterDepth) / Mathf.Max(1e-4f, smoothDepth)) * w; + + float vY = rb.GetPointVelocity(p).y; + float fY = perPointSpring * sub - perPointDamping * vY; + if (fY < 0f) fY = 0f; + fY *= pw; + + if (fY > maxForcePerPoint) fY = maxForcePerPoint; + + rb.AddForceAtPosition(Vector3.up * fY, p, ForceMode.Force); + } + + // === 3) 可选:归正(建议最后再加) === + if (rightingTorque > 0f) + { + Vector3 axis = Vector3.Cross(transform.up, Vector3.up); + float mag = axis.magnitude; + if (mag > 1e-5f) + { + 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); + return t * t * (3f - 2f * t); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Test/BobberBuoyancyStable_MultiPoint.cs.meta b/Assets/Scripts/Test/BobberBuoyancyStable_MultiPoint.cs.meta new file mode 100644 index 000000000..324f2b202 --- /dev/null +++ b/Assets/Scripts/Test/BobberBuoyancyStable_MultiPoint.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d18347d6a7a44aa8be1ea374a3d9067d +timeCreated: 1772295188 \ No newline at end of file