317 lines
11 KiB
C#
317 lines
11 KiB
C#
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]]);
|
||
}
|
||
}
|
||
} |