簡単な水の浮力生成
この効果は、ステーション B の特定のアップ オーナーの方法を参照しています。実装は比較的単純で、パフォーマンスは比較的平均的です。主なアイデアは、オブジェクトの喫水量を計算することによって、対応する喫水量の上向き浮力を計算することです。ただし、ここでは回転エフェクトは追加されておらず、上下に浮いているだけです。
計算方法:アルキメデスの原理 忘れたら高校の教科書を読み返してください~~
まず、オブジェクトの体積を見つけるメソッドが必要です。
//下面是一个方法的创建,这一个方法可以计算出水面上的不规则物体的体积,思路:向Mesh的包围盒里面打出多个点,最后是很多万个,之后统计留在Mesh里面的点占总的打出去的点的
//百分之多少,就可以计算出物体的体积!!;
//我们要将当前的gameobject设置为这一个物体,并创建一个矩阵和数组将在模型空间中进行计算,之后再转换世界坐标中并传入到数组中去;
//同时不要忘了获取包围盒的中心;
public float CalculateVolumn()
{
GameObject object1 = this.gameObject;//首先将场景中的模型设置为当前的模型;
MeshCollider object1_mc = object1.GetComponent<MeshCollider>();//此处获取到顶点的组件;
Vector3 object1_center = object1_mc.bounds.center;//获取到组件的顶点包围盒的中心;
Matrix4x4 localToWorld = object1_mc.transform.localToWorldMatrix;//获取到物体将顶点转换到世界空间中的一个矩阵;
Vector3[] vertices_object1 = object1_mc.GetComponent<MeshFilter>().mesh.vertices;//将获取到的顶点组件的顶点信息传入到数组当中;
//这里会创建三个顶点个数长度的数组,并将顶点的xyz经过矩阵变换后移动到世界空间;
float[] x = new float[vertices_object1.Length];
float[] y = new float[vertices_object1.Length];
float[] z = new float[vertices_object1.Length];
//下面进行矩阵从模型空间转移到世界空间的转换;
for (int i = 0; i < vertices_object1.Length; i++)
{
Vector3 world_v = localToWorld.MultiplyPoint3x4(vertices_object1[i]);//这一步将数组中的每一个顶点通过变换转移到世界空间中;
//下面会将顶点的xyz传入到我们上面创建的三个float数组当中去;
x[i] = world_v.x;
y[i] = world_v.y;
z[i] = world_v.z;
}
//下面要计算包围盒的长度,首先要进行大小的排序;
Array.Sort(x);
Array.Sort(y);
Array.Sort(z);
//大小的排序结束之后就可以直接进行包围盒长度的计算;
float x_length = x[x.Length - 1] - x[0];
float y_length = y[y.Length - 1] - y[0];
float z_length = z[z.Length - 1] - z[0];
//计算步长;
float lerp_x = x_length / SamplerCount;
float lerp_y = y_length / SamplerCount;
float lerp_z = z_length / SamplerCount;
//下面要计算打出的点中在Mesh中的点的个数;
int pointCount = 0;
for (int i = 0; i < SamplerCount; i++)
{
for (int j = 0; j < SamplerCount; j++)
{
for (int k = 0; k < SamplerCount; k++)
{
//创建samplerDot来接受每一个打出的点;
Vector3 samplerDot = new Vector3(x[0] + i * lerp_x, y[0] + lerp_y * j, z[0] + lerp_z * k);
if (IsInCollider(object1_mc, object1_center, samplerDot))
{
pointCount++;//如果打出的点在包围盒的内部则将点数加一;
}
}
}
}
//跳出循环,下面是物体体积的计算;
//计算模型的体积,单位为立方体;
float volumn = (float)pointCount / (SamplerCount * SamplerCount * SamplerCount) * (x_length * y_length * z_length);
Debug.Log("Volumn:"+volumn);//弄一个Log;
return volumn;//最后返回体积;
}
他の変数の設定は次のとおりです。
//先声明一些变量,包括在水里1阻力,旋转的阻力,空中的阻力,空中旋转的阻力,以及密度水面的高度等等;
private int SamplerCount = 100;//这里是每一个方向会选取100个点,总共会选取100万个点;
public float density = 1f;//物体的密度;
public float volumn;//体积大小;
public float underWaterDrag = 3f;//水里的阻力大小;
public float underWaterAngularDrag = 1f;//旋转的阻力;
public float airDrag = 0f;//空气的阻力;
public float airAngularDrag = 0.05f;//空气中旋转的阻力;
public float waterHeight = 0f; //水面的高度;
Rigidbody m_Rigidbody;//创建一个Rigidbody对象,用来获取到场景中的物体;
bool underWater;//用来确定是否在水下;
MeshRenderer mesh;//创建mesh来获取到场景中物体的所有面;
Start 関数とFixedUpdate 関数は次のとおりです。ここでは、次のFixedUpdate 関数に焦点を当てます。
Update とFixedUpdate の違い:
-->Update(): フレームごとに実行され、コードは 1 秒間に N 回実行されますが、コンピュータの各フレームの時間が安定していないため、各実行の時間間隔は異なります。
-->FixedUpdate() (物理フレーム実行): 実行順序は Update の後で、フレームごとに 1 秒あたり N 回実行されますが、各フレームの時間は固定されています。つまり、各フレーム間の時間は同じです。
したがって、Update()を使用してオブジェクトの物理的な関係(衝突など) を記述する場合、オブジェクトが各フレームで不均一な力を受けると問題が発生します (2D 衝突により画像のジッターが発生するなど) 。物理的な関係に関しては、FixedUpdate を使用してください。() の方が良いです。
以下は 2 つの関数のコードです。
void Start()
{
m_Rigidbody = GetComponent<Rigidbody>();//获取到场景对象并传入到m_Rigidbody对象中;
mesh = GetComponent<MeshRenderer>();//获取到物体的面,并存储到mesh对象里面;
volumn = CalculateVolumn();//通过下面定义的CalculateVolumn方法来获取到物体的体积;
}
void FixedUpdate()
{
//每一帧的时间相同,可以使得物理受力更加的均匀,这里主要是进行浮力相关的计算;
//计算m = p*V;mass即为m;
m_Rigidbody.mass = volumn * Mathf.Clamp(density, 0, density);//m = pV的计算,此处将变量density限制在了0到density之间;
var difference = transform.position.y - mesh.bounds.size.y / 2 - waterHeight;
if (difference < 0)
{
//给物体一个向上的浮力;
m_Rigidbody.AddForceAtPosition(Vector3.up * volumn * 9.81f * Mathf.Clamp(Mathf.Abs(difference),0,mesh.bounds.size.y),transform.position,ForceMode.Force);
//判断是否在水下:
if (!underWater)
{
underWater = true;
SwitchState(true);
}
else if(underWater)
{
underWater = false;
SwitchState(false);
}
}
}
いくつかの追加機能もあります。
void SwitchState(bool isUnderWater)
{
if (isUnderWater)
{
//修改阻力参数为水下参数;
m_Rigidbody.drag = underWaterDrag;
m_Rigidbody.angularDrag = underWaterAngularDrag;
}
else
{
//如果不在水下则修改成为空气的阻力参数;
m_Rigidbody.drag = airDrag;
m_Rigidbody.angularDrag = airAngularDrag;
}
}
//用于判断打出的点是否在包围盒的内部,如果是则返回True;
//这里传入的是顶点的组件,包围盒的中心,以及每一个打出的顶点位置;
bool IsInCollider(MeshCollider other, Vector3 center, Vector3 point)
{
//这一个函数会对场景中的所有物体进行获取并投射射线,如果透射出的射线属于other则返回false,因为如果物体打出的点是在内部则不会有射线;
Vector3 direction = center - point;//获取射线方向;
RaycastHit[] hits = Physics.RaycastAll(point, direction);//获取所有场景中的射线并传入到数组hits当中;
foreach (RaycastHit hit in hits)
{
if (hit.collider == other)
{
//对于射线的数组如果判断为改other对象的射线,则返回false;
return false;
}
}
return true;//否则返回false;
}
すべてのコードは次のとおりです。
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
[RequireComponent(typeof(MeshCollider))]
[RequireComponent(typeof(Rigidbody))]
public class buoyancy : MonoBehaviour
{
//先声明一些变量,包括在水里1阻力,旋转的阻力,空中的阻力,空中旋转的阻力,以及密度水面的高度等等;
private int SamplerCount = 100;//这里是每一个方向会选取100个点,总共会选取100万个点;
public float density = 1f;//物体的密度;
public float volumn;//体积大小;
public float underWaterDrag = 3f;//水里的阻力大小;
public float underWaterAngularDrag = 1f;//旋转的阻力;
public float airDrag = 0f;//空气的阻力;
public float airAngularDrag = 0.05f;//空气中旋转的阻力;
public float waterHeight = 0f;
Rigidbody m_Rigidbody;//创建一个Rigidbody对象,用来获取到场景中的物体;
bool underWater;//用来确定是否在水下;
MeshRenderer mesh;//创建mesh来获取到场景中物体的所有面;
void Start()
{
m_Rigidbody = GetComponent<Rigidbody>();//获取到场景对象并传入到m_Rigidbody对象中;
mesh = GetComponent<MeshRenderer>();//获取到物体的面,并存储到mesh对象里面;
volumn = CalculateVolumn();//通过下面定义的CalculateVolumn方法来获取到物体的体积;
}
void FixedUpdate()
{
//每一帧里面都会进行调用,这里主要是进行浮力相关的计算;
//计算m = p*V;mass即为m;
m_Rigidbody.mass = volumn * Mathf.Clamp(density, 0, density);//m = pV的计算,此处将变量density限制在了0到density之间;
var difference = transform.position.y - mesh.bounds.size.y / 2 - waterHeight;
if (difference < 0)
{
//给物体一个向上的浮力;
m_Rigidbody.AddForceAtPosition(Vector3.up * volumn * 9.81f * Mathf.Clamp(Mathf.Abs(difference),0,mesh.bounds.size.y),transform.position,ForceMode.Force);
if (!underWater)
{
underWater = true;
SwitchState(true);
}
else if(underWater)
{
underWater = false;
SwitchState(false);
}
}
}
void SwitchState(bool isUnderWater)
{
if (isUnderWater)
{
//修改阻力参数为水下参数;
m_Rigidbody.drag = underWaterDrag;
m_Rigidbody.angularDrag = underWaterAngularDrag;
}
else
{
//如果不在水下则修改成为空气的阻力参数;
m_Rigidbody.drag = airDrag;
m_Rigidbody.angularDrag = airAngularDrag;
}
}
//用于判断打出的点是否在包围盒的内部,如果是则返回True;
//这里传入的是顶点的组件,包围盒的中心,以及每一个打出的顶点位置;
bool IsInCollider(MeshCollider other, Vector3 center, Vector3 point)
{
//这一个函数会对场景中的所有物体进行获取并投射射线,如果透射出的射线属于other则放回false,因为如果物体打出的点是在内部则不会有射线;
Vector3 direction = center - point;//获取射线方向;
RaycastHit[] hits = Physics.RaycastAll(point, direction);//获取所有场景中的射线并传入到数组hits当中;
foreach (RaycastHit hit in hits)
{
if (hit.collider == other)
{
//对于射线的数组如果判断为改other对象,则返回false;
return false;
}
}
return true;//否则返回false;
}
//下面是一个方法的创建,这一个方法可以计算出水面上的不规则物体的体积,思路:向Mesh的包围盒里面打出多个点,最后是很多万个,之后统计留在Mesh里面的点占总的打出去的点的
//百分之多少,就可以计算出物体的体积!!;
//我们要将当前的gameobject设置为这一个物体,并创建一个矩阵和数组将在模型空间中进行计算,之后再转换世界坐标中并传入到数组中去;
//同时不要忘了获取包围盒的中心;
public float CalculateVolumn()
{
GameObject object1 = this.gameObject;//首先将场景中的模型设置为当前的模型;
MeshCollider object1_mc = object1.GetComponent<MeshCollider>();//此处获取到顶点的组件;
Vector3 object1_center = object1_mc.bounds.center;//获取到组件的顶点包围盒的中心;
Matrix4x4 localToWorld = object1_mc.transform.localToWorldMatrix;//获取到物体将顶点转换到世界空间中的一个矩阵;
Vector3[] vertices_object1 = object1_mc.GetComponent<MeshFilter>().mesh.vertices;//将获取到的顶点组件的顶点信息传入到数组当中;
//这里会创建三个顶点个数长度的数组,并将顶点的xyz经过矩阵变换后移动到世界空间;
float[] x = new float[vertices_object1.Length];
float[] y = new float[vertices_object1.Length];
float[] z = new float[vertices_object1.Length];
//下面进行矩阵从模型空间转移到世界空间的转换;
for (int i = 0; i < vertices_object1.Length; i++)
{
Vector3 world_v = localToWorld.MultiplyPoint3x4(vertices_object1[i]);//这一步将数组中的每一个顶点通过变换转移到世界空间中;
//下面会将顶点的xyz传入到我们上面创建的三个float数组当中去;
x[i] = world_v.x;
y[i] = world_v.y;
z[i] = world_v.z;
}
//下面要计算包围盒的长度,首先要进行大小的排序;
Array.Sort(x);
Array.Sort(y);
Array.Sort(z);
//大小的排序结束之后就可以直接进行包围盒长度的计算;
float x_length = x[x.Length - 1] - x[0];
float y_length = y[y.Length - 1] - y[0];
float z_length = z[z.Length - 1] - z[0];
//计算步长;
float lerp_x = x_length / SamplerCount;
float lerp_y = y_length / SamplerCount;
float lerp_z = z_length / SamplerCount;
//下面要计算打出的点中在Mesh中的点的个数;
int pointCount = 0;
for (int i = 0; i < SamplerCount; i++)
{
for (int j = 0; j < SamplerCount; j++)
{
for (int k = 0; k < SamplerCount; k++)
{
//创建samplerDot来接受每一个打出的点;
Vector3 samplerDot = new Vector3(x[0] + i * lerp_x, y[0] + lerp_y * j, z[0] + lerp_z * k);
if (IsInCollider(object1_mc, object1_center, samplerDot))
{
pointCount++;//如果打出的点在包围盒的内部则将点数加一;
}
}
}
}
//跳出循环,下面的体积的计算;
//计算模型的体积,单位为立方体;
float volumn = (float)pointCount / (SamplerCount * SamplerCount * SamplerCount) * (x_length * y_length * z_length);
Debug.Log("Volumn:"+volumn);//弄一个Log;
return volumn;//最后返回体积;
}
}
最後に、操作中にオブジェクトに Rigidbody とスクリプトを追加します。実行後、図に示すように、waterHeight を適切な高さに調整して浮力効果を確認します。
密度を調整すると、オブジェクトを浮かせたり、沈めたりすることができます。