上一节学习了如何让顶点动起来,形成水的效果,那问题又来了,有物体掉入水中怎么办?这可不是直接掉在地上那么简单哦(说简单是因为直接使用Unity的刚体),是不是就得模拟漂浮物的效果呢?就好比下面这个效果:
简单的记录几个关键点:1.先获取物体所包含的碰撞体,在碰撞体的范围内生成若干浮力盒,然后根据浮力盒的位置,给当前位置施加推力。2.其中涉及到浮力的概念,当物体的浮力m_density小于水的浮力(设置1000)时,物体才会浮起来。
3.物体的阻力和角阻力,当物体在水中时,阻力相对较大,所以变化速度比较慢。
代码如下:
using System;
using UnityEngine;
using System.Collections.Generic;
namespace Lin
{
[RequireComponent(typeof(Rigidbody))]
public class BuoyancySetting : MonoBehaviour
{
private CreateMesh m_water;
private Transform m_trans;
private Rigidbody m_rigidbody;
// 原代码中碰撞体是集合
// 可以获取子对象的碰撞体生成不规则的浮力盒
// 比如残破的船只等
private Collider m_collider;
public float m_boxDensity = 2; // 浮力盒的密度
private Bounds m_bounds;
private float m_boxSize = 0; // 格子大小
private Vector3 m_boxCount = Vector3.one; // XYZ轴各方向的格子个数
private BuoyancyBox[] m_buoyancyBoxs; // 浮力盒集合
private struct BuoyancyBox
{
// 浮力盒在物体坐标的位置
public Vector3 Position;
// 是否在边界
public bool IsOnColliderEdge;
}
// 物体密度,当物体密度小于水密度时才会浮起来
public float m_density = 750f;
// 流动时的阻力和角阻力,阻力越大运动越慢,越不明显
public float m_inWaterDrag = 1;
public float m_inWaterAngularDrag = 1;
// 物体初始化时的阻力和角阻力
private float m_initDrag;
private float m_initAngularDrag;
private void Start()
{
m_trans = transform;
m_water = GameObject.Find("water").GetComponent();
m_rigidbody = GetComponent();
m_collider = GetComponent();
m_initDrag = m_rigidbody.drag;
m_initAngularDrag = m_rigidbody.angularDrag;
InitBoxs();
}
// 初始化浮力盒
private void InitBoxs()
{
Quaternion originalRotation = m_trans.rotation;
Vector3 originalPosition = m_trans.position;
// 旋转和位置归零
m_trans.rotation = Quaternion.identity;
m_trans.position = Vector3.zero;
// 计算浮力盒大小
m_bounds.Encapsulate(m_collider.bounds);
m_boxSize = m_bounds.size.magnitude / m_boxDensity / 2;
// XYZ各方向的【浮力盒个数】= 边界长度/单位大小
m_boxCount.x = Mathf.RoundToInt(m_bounds.size.x / m_boxSize) + 1;
m_boxCount.y = Mathf.RoundToInt(m_bounds.size.y / m_boxSize) + 1;
m_boxCount.z = Mathf.RoundToInt(m_bounds.size.z / m_boxSize) + 1;
m_buoyancyBoxs = SliceIntoVoxels().ToArray();
// 还原物体位置
m_trans.rotation = originalRotation;
m_trans.position = originalPosition;
m_boxSize = Mathf.Pow(m_bounds.size.x * m_bounds.size.y * m_bounds.size.z /
(m_boxCount.x * m_boxCount.y * m_boxCount.z), 1f / 3f);
}
private List SliceIntoVoxels()
{
List boxList = new List((int)(m_boxCount.x * m_boxCount.y * m_boxCount.z));
for (int ix = 0; ix < m_boxCount.x; ix++)
{
for (int iy = 0; iy < m_boxCount.y; iy++)
{
for (int iz = 0; iz < m_boxCount.z; iz++)
{
float x = m_bounds.min.x + m_bounds.size.x / m_boxCount.x * (0.5f + ix);
float y = m_bounds.min.y + m_bounds.size.y / m_boxCount.y * (0.5f + iy);
float z = m_bounds.min.z + m_bounds.size.z / m_boxCount.z * (0.5f + iz);
// 获取当前每个格子的位置
Vector3 point = new Vector3(x, y, z);
// 如果格子的位置在包围盒内,就添加到列表
if (ColliderTools.IsPointInsideCollider(m_collider, point))
{
BuoyancyBox buoyancyBox;
// 转到物体坐标
buoyancyBox.Position = m_trans.InverseTransformPoint(point);
// 在包围盒的边缘
buoyancyBox.IsOnColliderEdge = ColliderTools.IsPointAtColliderEdge(m_collider, point, m_boxSize);
boxList.Add(buoyancyBox);
}
}
}
}
return boxList;
}
private void FixedUpdate()
{
// 浮力增量大于0时,说明物体在水里,那么速度和旋转的变换是很慢的
float m_boxUpDelta = 0;
// 比容就是密度的倒数
// 比容就是单位质量的体积
float VFactor = 1f / m_density;
// 公式:重力G = g * 质量m
// 计算单位质量的体积所受重力,这里理解为单位体积所受的重力
float unitVG = -Physics.gravity.y * VFactor;
// 公式:体积 = 质量 / 密度
// 计算当前物体在水中的体积
float V = m_water._density * m_water._density * m_rigidbody.mass / m_water._density * Time.deltaTime;
// 总施加力 = 单位体积的重力 * 总体积
// 给每一个浮力盒的力
Vector3 _singleForce = Vector3.up * (unitVG * V / m_buoyancyBoxs.Length);
// 浮力盒个数
int boxLength = m_buoyancyBoxs.Length;
for (int i = 0; i < boxLength; i++)
{
// 获取浮力盒在物体中的位置
Vector3 point = m_buoyancyBoxs[i].Position;
// 获取浮力盒在世界坐标的位置
Vector3 wPoint = m_trans.TransformPoint(point);
// 获取当前位置水面高度
float waterHeight = m_water.GetWaterLevel(wPoint.x, wPoint.z);
if (waterHeight != float.NegativeInfinity && (wPoint.y - m_boxSize / 1f < waterHeight))
{
// k = 1 浮力盒完全在水里面,k = 0 浮力盒完全在水外面
float k = (waterHeight - wPoint.y) / (2f * m_boxSize) + 0.5f;
if (k > 1f)
k = 1f;
else if (k < 0f)
k = 0f;
// 在水中浮力盒的个数
m_boxUpDelta += k;
// 计算最终施加给物体的力
Vector3 lastForce = k * _singleForce;
// 在水里面才需要添加一个力到刚体,使用质量
m_rigidbody.AddForceAtPosition(lastForce, wPoint, ForceMode.Impulse);
}
}
// 当浮力盒在水里面的时候,阻力+1,阻力变大,物体下降就变慢
// 当浮力盒都在水外面的时候,使用较小的阻力,物体下降就快
m_boxUpDelta /= boxLength;
m_rigidbody.drag = Mathf.Lerp(m_rigidbody.drag, m_boxUpDelta > 0.0001f ? m_initDrag + m_inWaterDrag : m_initDrag, 15f * Time.deltaTime);
m_rigidbody.angularDrag = Mathf.Lerp(m_rigidbody.angularDrag, m_boxUpDelta > 0.0001f ? m_initAngularDrag + m_inWaterAngularDrag : m_initAngularDrag, 15f * Time.deltaTime);
}
private void OnDrawGizmos()
{
Gizmos.DrawIcon(transform.position, "DynamicWater/BuoyancyForce.png");
if (!Application.isEditor || m_buoyancyBoxs == null)
{
return;
}
Vector3 gizmoSize = Vector3.one * m_boxSize;
Gizmos.color = new Color(Color.yellow.r, Color.yellow.g, Color.yellow.b, 0.5f);
foreach (var p in m_buoyancyBoxs)
{
Gizmos.DrawCube(transform.TransformPoint(p.Position), gizmoSize);
}
Gizmos.color = Color.red;
Gizmos.DrawSphere(rigidbody.worldCenterOfMass, m_boxSize / 2f);
}
}
public static class ColliderTools
{
public static bool IsPointInsideCollider(Collider collider, Vector3 point)
{
RaycastHit hit;
if !UNITY_FLASH
if (collider is TerrainCollider)
{
if (!collider.Raycast(new Ray(point, Vector3.up), out hit, collider.bounds.size.y))
{
return false;
}
}
else
endif
if (collider is MeshCollider && !((MeshCollider)collider).convex)
{
if (!IsPointInsideMeshCollider(collider, point))
{
return false;
}
}
else
{
Vector3 direction = collider.bounds.center - point;
float directionMagnitude = direction.magnitude;
if (directionMagnitude > 0.01f &&
collider.Raycast(new Ray(point, direction.normalized), out hit, directionMagnitude))
{
return false;
}
}
return true;
}
public static bool IsPointInsideMeshCollider(Collider collider, Vector3 point)
{
Vector3[] directions = { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back };
foreach (var ray in directions)
{
RaycastHit hit;
if (collider.Raycast(new Ray(point - ray * 1000f, ray), out hit, 1000f) == false)
{
return false;
}
}
return true;
}
public static bool IsPointAtColliderEdge(Collider collider, Vector3 point, float tolerance)
{
RaycastHit hit;
tolerance *= 0.71f; // Approximately 1/sqrt(2)
Vector3 direction = collider.bounds.center - point;
Vector3 directionNormalized = direction.normalized;
bool result = direction != Vector3.zero &&
collider.Raycast(new Ray(point - directionNormalized * tolerance, directionNormalized),
out hit, tolerance);
return result;
}
}
}
注:可以设置物体密度和阻力达到其他效果,比如
http://blog.sina.com.cn/s/blog_89d90b7c0102vpa3.html