using UnityEngine; [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(Collider))] public class CentimeterBuoyancy : MonoBehaviour { [Header("浮力设置")] [SerializeField] private float waterDensity = 1000f; // 水的密度 kg/m³ [SerializeField] private float buoyancyScale = 1f; // 浮力缩放系数 [SerializeField] private float dragScale = 1f; // 水中阻力系数 [Header("水面设置")] [SerializeField] private float waterLevel = 0f; // 水面高度 [SerializeField] private LayerMask waterLayer; // 水体层级 [Header("调试")] [SerializeField] private bool showDebugInfo = true; [SerializeField] private Color debugColor = Color.cyan; private Rigidbody rb; private Collider objCollider; private float volumeInCm; private Vector3[] samplePoints; private Bounds localBounds; void Start() { rb = GetComponent(); objCollider = GetComponent(); // 计算物体体积(立方厘米) volumeInCm = CalculateVolumeInCm(); // 计算局部空间的包围盒 CalculateLocalBounds(); if (showDebugInfo) { Debug.Log($"物体体积: {volumeInCm:F2} cm³,质量: {rb.mass * 1000:F2} g"); Debug.Log($"局部包围盒: 中心 {localBounds.center}, 大小 {localBounds.size}"); } // 初始化采样点 InitializeSamplePoints(); } void FixedUpdate() { ApplyBuoyancy(); ApplyWaterDrag(); } // 计算局部空间的包围盒 void CalculateLocalBounds() { // 获取碰撞器的局部空间边界 if (objCollider is BoxCollider box) { localBounds = new Bounds(box.center, box.size); } else if (objCollider is SphereCollider sphere) { Vector3 size = Vector3.one * sphere.radius * 2; localBounds = new Bounds(sphere.center, size); } else if (objCollider is CapsuleCollider capsule) { float radius = capsule.radius; float height = capsule.height; Vector3 size; // 根据胶囊方向确定尺寸 switch (capsule.direction) { case 0: // X轴 size = new Vector3(height, radius * 2, radius * 2); break; case 1: // Y轴 size = new Vector3(radius * 2, height, radius * 2); break; case 2: // Z轴 size = new Vector3(radius * 2, radius * 2, height); break; default: size = new Vector3(radius * 2, height, radius * 2); break; } localBounds = new Bounds(capsule.center, size); } else { // 对于复杂碰撞器,使用世界边界转换到局部空间 Bounds worldBounds = objCollider.bounds; localBounds = new Bounds( transform.InverseTransformPoint(worldBounds.center), transform.InverseTransformVector(worldBounds.size) ); } } // 计算体积(立方厘米) float CalculateVolumeInCm() { Bounds bounds = objCollider.bounds; Vector3 sizeInCm = bounds.size * 100f; // 转换为厘米 // 根据不同碰撞器类型估算体积 if (objCollider is BoxCollider) { return sizeInCm.x * sizeInCm.y * sizeInCm.z; } else if (objCollider is SphereCollider) { float radiusInCm = bounds.extents.x * 100f; return (4f / 3f) * Mathf.PI * Mathf.Pow(radiusInCm, 3); } else if (objCollider is CapsuleCollider) { CapsuleCollider capsule = objCollider as CapsuleCollider; float radiusInCm = capsule.radius * 100f; float heightInCm = capsule.height * 100f; return Mathf.PI * radiusInCm * radiusInCm * heightInCm; } else { // 对于复杂碰撞器,使用边界框估算 return sizeInCm.x * sizeInCm.y * sizeInCm.z * 0.5f; } } // 初始化采样点 void InitializeSamplePoints() { int sampleCount = 3; // 每个维度采样点数(减少采样点以提高性能) // 创建网格采样点用于检测浸入深度 samplePoints = new Vector3[sampleCount * sampleCount * sampleCount]; int index = 0; for (int x = 0; x < sampleCount; x++) { for (int y = 0; y < sampleCount; y++) { for (int z = 0; z < sampleCount; z++) { // 生成 -0.5 到 0.5 范围内的归一化坐标 float nx = (float)x / (sampleCount - 1) - 0.5f; float ny = (float)y / (sampleCount - 1) - 0.5f; float nz = (float)z / (sampleCount - 1) - 0.5f; samplePoints[index] = new Vector3(nx, ny, nz); index++; } } } } void ApplyBuoyancy() { float immersedVolume = 0f; float totalImmersedDepth = 0f; int immersedPoints = 0; Vector3 buoyancyCenter = Vector3.zero; // 计算浸入体积 foreach (Vector3 normalizedPoint in samplePoints) { // 将归一化坐标转换为局部空间坐标 Vector3 localPoint = new Vector3( localBounds.center.x + normalizedPoint.x * localBounds.size.x, localBounds.center.y + normalizedPoint.y * localBounds.size.y, localBounds.center.z + normalizedPoint.z * localBounds.size.z ); // 转换到世界空间 Vector3 worldPoint = transform.TransformPoint(localPoint); // 调试:打印第一个点的坐标 if (showDebugInfo && immersedPoints == 0 && samplePoints.Length > 0) { Debug.DrawLine(worldPoint, worldPoint + Vector3.up * 0.01f, Color.red, 0.1f); } if (worldPoint.y < waterLevel) { immersedVolume += 1f; totalImmersedDepth += (waterLevel - worldPoint.y); buoyancyCenter += worldPoint; immersedPoints++; } } // 计算浸入比例 float immersedRatio = (float)immersedPoints / samplePoints.Length; if (immersedRatio < 0.01f) return; // 几乎没有浸入,不施加浮力 // 计算平均浸入深度 float averageImmersedDepth = immersedPoints > 0 ? totalImmersedDepth / immersedPoints : 0; // 计算浮力中心 if (immersedPoints > 0) { buoyancyCenter /= immersedPoints; } // 计算实际浸入体积(立方厘米) float actualImmersedVolumeCm = volumeInCm * immersedRatio; // 转换为立方米(1 m³ = 1,000,000 cm³) float actualImmersedVolumeM = actualImmersedVolumeCm / 1000000f; // 计算浮力 = 水的密度 * 重力加速度 * 浸入体积 // 水的密度默认 1000 kg/m³,重力加速度 9.8 m/s² float buoyancyForceMagnitude = waterDensity * Physics.gravity.magnitude * actualImmersedVolumeM * buoyancyScale; // 根据浸入深度调整浮力(更深的地方浮力更大) float depthFactor = Mathf.Clamp01(averageImmersedDepth * 10f); // 每10cm深度增加一倍 buoyancyForceMagnitude *= (1f + depthFactor); // 施加浮力 Vector3 buoyancyForce = Vector3.up * buoyancyForceMagnitude; rb.AddForceAtPosition(buoyancyForce, buoyancyCenter, ForceMode.Force); // 施加浮力产生的扭矩(使物体自然上浮) Vector3 torque = Vector3.Cross(buoyancyCenter - rb.worldCenterOfMass, buoyancyForce); rb.AddTorque(torque * 0.1f); if (showDebugInfo) { // 绘制浮力线 Debug.DrawLine(buoyancyCenter, buoyancyCenter + buoyancyForce * 0.001f, debugColor, 0.1f); // 绘制浸没点 foreach (Vector3 normalizedPoint in samplePoints) { Vector3 localPoint = new Vector3( localBounds.center.x + normalizedPoint.x * localBounds.size.x, localBounds.center.y + normalizedPoint.y * localBounds.size.y, localBounds.center.z + normalizedPoint.z * localBounds.size.z ); Vector3 worldPoint = transform.TransformPoint(localPoint); if (worldPoint.y < waterLevel) { Debug.DrawLine(worldPoint, worldPoint + Vector3.up * 0.005f, debugColor, 0.1f); } } } } void ApplyWaterDrag() { float immersedRatio = CalculateImmersedRatio(); if (immersedRatio > 0.01f) { // 线性阻力 Vector3 dragForce = -rb.linearVelocity * dragScale * immersedRatio * 10f; rb.AddForce(dragForce, ForceMode.Force); // 角阻力 Vector3 angularDrag = -rb.angularVelocity * dragScale * immersedRatio * 5f; rb.AddTorque(angularDrag, ForceMode.Force); } } float CalculateImmersedRatio() { int immersedPoints = 0; foreach (Vector3 normalizedPoint in samplePoints) { Vector3 localPoint = new Vector3( localBounds.center.x + normalizedPoint.x * localBounds.size.x, localBounds.center.y + normalizedPoint.y * localBounds.size.y, localBounds.center.z + normalizedPoint.z * localBounds.size.z ); Vector3 worldPoint = transform.TransformPoint(localPoint); if (worldPoint.y < waterLevel) { immersedPoints++; } } return (float)immersedPoints / samplePoints.Length; } void OnDrawGizmosSelected() { if (!showDebugInfo || !Application.isPlaying) return; // 绘制局部包围盒 Gizmos.color = Color.yellow; Vector3[] corners = new Vector3[8]; // 计算局部包围盒的8个角在世界空间的位置 for (int i = 0; i < 8; i++) { Vector3 localCorner = localBounds.center; localCorner.x += (i & 1) == 0 ? -localBounds.extents.x : localBounds.extents.x; localCorner.y += (i & 2) == 0 ? -localBounds.extents.y : localBounds.extents.y; localCorner.z += (i & 4) == 0 ? -localBounds.extents.z : localBounds.extents.z; corners[i] = transform.TransformPoint(localCorner); } // 绘制包围盒的边 int[] edges = new int[] { 0,1, 1,3, 3,2, 2,0, 4,5, 5,7, 7,6, 6,4, 0,4, 1,5, 2,6, 3,7 }; for (int i = 0; i < edges.Length; i += 2) { Gizmos.DrawLine(corners[edges[i]], corners[edges[i + 1]]); } } }