Files
Fishing2/Assets/Scripts/Test/CentimeterBuoyancy.cs
2026-02-28 17:45:08 +08:00

317 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<Rigidbody>();
objCollider = GetComponent<Collider>();
// 计算物体体积(立方厘米)
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]]);
}
}
}