219 lines
9.2 KiB
C#
219 lines
9.2 KiB
C#
using UnityEngine;
|
||
using WaveHarmonic.Crest;
|
||
|
||
namespace NBF
|
||
{
|
||
public sealed class BobberFloating : MonoBehaviour
|
||
{
|
||
public WaterRenderer _water;
|
||
|
||
[SerializeField] Rigidbody _RigidBody;
|
||
|
||
[Tooltip("要瞄准哪一层水的碰撞层。")] [SerializeField]
|
||
CollisionLayer _Layer = CollisionLayer.AfterAnimatedWaves;
|
||
|
||
[Header("浮力")]
|
||
[Header("力强度")]
|
||
[Tooltip("对于探测器而言,大致为 100 比 1 的质量与力的比例,以使质心保持在表面附近。对于“对齐法线”,默认值适用于具有默认刚体的默认球体。")]
|
||
[SerializeField]
|
||
float _BuoyancyForceStrength = 10f;
|
||
|
||
[Header("扭矩强度")] [Tooltip("使船体方向与水的法线方向一致时所施加扭矩的大小。")] [SerializeField]
|
||
float _BuoyancyTorqueStrength = 8f;
|
||
|
||
[Header("最大力矩")] [Tooltip("将浮力值固定在此数值上。\n\n适用于处理完全浸没的物体。")] [SerializeField]
|
||
float _MaximumBuoyancyForce = 100f;
|
||
|
||
[Header("高度偏移")] [Tooltip("从变换中心到船体底部的高度偏移(如果存在)。\n\n默认值适用于默认球体。该值无需精确测量从中心到底部的距离。")] [SerializeField]
|
||
float _CenterToBottomOffset = -1f;
|
||
|
||
[Tooltip("顺着波浪 “冲浪” 的近似流体动力学效果。")] [Range(0, 1)] [SerializeField]
|
||
float _AccelerateDownhill;
|
||
|
||
|
||
[Header("拖拽")] [Tooltip("在水中时使用拖拽功能。\n将此属性添加到刚体所声明的拖拽力上。")] [SerializeField]
|
||
Vector3 _Drag = new(2f, 3f, 1f);
|
||
|
||
[Tooltip("在水中会产生旋转阻力。\n\n将此阻力添加到刚体上已声明的旋转阻力值之上。")] [SerializeField]
|
||
float _AngularDrag = 0.2f;
|
||
|
||
[Tooltip("施加拉力的位置的垂直偏移量。")] [SerializeField]
|
||
float _ForceHeightOffset;
|
||
|
||
|
||
[Header("波响应")] [Tooltip("用于物理计算的物体宽度。\n\n此值越大,波响应的滤波效果和平滑程度就越高。如果无法对较大波长进行滤波,则应增加 LOD 级别。")] [SerializeField]
|
||
float _ObjectWidth = 3f;
|
||
|
||
|
||
// Debug
|
||
[Space(10)] [SerializeField] DebugFields _Debug = new();
|
||
|
||
[System.Serializable]
|
||
sealed class DebugFields
|
||
{
|
||
[SerializeField] internal bool _DrawQueries = false;
|
||
}
|
||
|
||
internal const string k_FixedUpdateMarker = "Crest.FloatingObject.FixedUpdate";
|
||
|
||
static Unity.Profiling.ProfilerMarker s_FixedUpdateMarker = new(k_FixedUpdateMarker);
|
||
|
||
/// <summary>
|
||
/// 这个物体的任何部分是否浸泡在水中?
|
||
/// </summary>
|
||
public bool InWater { get; private set; }
|
||
|
||
readonly SampleFlowHelper _SampleFlowHelper = new();
|
||
|
||
Vector3[] _QueryPoints;
|
||
Vector3[] _QueryResultDisplacements;
|
||
Vector3[] _QueryResultVelocities;
|
||
Vector3[] _QueryResultNormal;
|
||
|
||
internal FloatingObjectProbe[] _Probe = new FloatingObjectProbe[] { new() { _Weight = 1f } };
|
||
|
||
|
||
private void Start()
|
||
{
|
||
if (_RigidBody == null) TryGetComponent(out _RigidBody);
|
||
|
||
var points = _Probe;
|
||
// Advanced 还需要为中心增设一个位置。
|
||
var length = points.Length;
|
||
_QueryPoints = new Vector3[length];
|
||
_QueryResultDisplacements = new Vector3[length];
|
||
_QueryResultVelocities = new Vector3[length];
|
||
_QueryResultNormal = new Vector3[length];
|
||
}
|
||
|
||
private void FixedUpdate()
|
||
{
|
||
s_FixedUpdateMarker.Begin(this);
|
||
|
||
var points = _Probe;
|
||
|
||
// 查询
|
||
var collisions = _water.AnimatedWavesLod.Provider;
|
||
|
||
// 更新查询点。
|
||
for (var i = 0; i < points.Length; i++)
|
||
{
|
||
var point = points[i];
|
||
_QueryPoints[i] =
|
||
transform.TransformPoint(point._Position + new Vector3(0, _RigidBody.centerOfMass.y, 0));
|
||
}
|
||
|
||
_QueryPoints[^1] = transform.position + new Vector3(0, _RigidBody.centerOfMass.y, 0);
|
||
|
||
collisions.Query(GetHashCode(), _ObjectWidth, _QueryPoints, _QueryResultDisplacements,
|
||
_QueryResultNormal, _QueryResultVelocities, _Layer);
|
||
|
||
|
||
//我们可以将表面速度过滤为最近两帧中的较小值。
|
||
//存在一种极端情况:当波长被开启 / 关闭时,会产生单帧速度尖峰——
|
||
//因为此时水面确实会发生极快的运动。
|
||
var surfaceVelocity = _QueryResultVelocities[^1];
|
||
_SampleFlowHelper.Sample(transform.position, out var surfaceFlow, minimumLength: _ObjectWidth);
|
||
surfaceVelocity += new Vector3(surfaceFlow.x, 0, surfaceFlow.y);
|
||
|
||
if (_Debug._DrawQueries)
|
||
{
|
||
Debug.DrawLine(transform.position + 5f * Vector3.up,
|
||
transform.position + 5f * Vector3.up + surfaceVelocity, new(1, 1, 1, 0.6f));
|
||
}
|
||
|
||
{
|
||
var height = _QueryResultDisplacements[0].y + _water.SeaLevel;
|
||
var bottomDepth = height - transform.position.y - _CenterToBottomOffset;
|
||
var normal = _QueryResultNormal[0];
|
||
|
||
if (_Debug._DrawQueries)
|
||
{
|
||
var surfPos = transform.position;
|
||
surfPos.y = height;
|
||
DebugUtility.DrawCross(Debug.DrawLine, surfPos, normal, 1f, Color.red);
|
||
}
|
||
|
||
InWater = bottomDepth > 0f;
|
||
if (!InWater)
|
||
{
|
||
s_FixedUpdateMarker.End();
|
||
return;
|
||
}
|
||
|
||
var buoyancy = _BuoyancyForceStrength * bottomDepth * bottomDepth * bottomDepth *
|
||
-Physics.gravity.normalized;
|
||
if (_MaximumBuoyancyForce < Mathf.Infinity)
|
||
{
|
||
buoyancy = Vector3.ClampMagnitude(buoyancy, _MaximumBuoyancyForce);
|
||
}
|
||
|
||
_RigidBody.AddForce(buoyancy, ForceMode.Acceleration);
|
||
|
||
// 在水面上滑行的近似流体动力学
|
||
if (_AccelerateDownhill > 0f)
|
||
{
|
||
_RigidBody.AddForce(_AccelerateDownhill * -Physics.gravity.y * new Vector3(normal.x, 0f, normal.z),
|
||
ForceMode.Acceleration);
|
||
}
|
||
|
||
|
||
// 朝向
|
||
// 与水面垂直。默认使用一个垂直方向,但也可以使用单独的垂直方向。
|
||
// 根据船的长度与宽度的比例。这会根据船只的不同而产生不同的旋转效果。
|
||
// dimensions.
|
||
{
|
||
var normalLatitudinal = normal;
|
||
|
||
if (_Debug._DrawQueries)
|
||
Debug.DrawLine(transform.position, transform.position + 5f * normalLatitudinal, Color.green);
|
||
|
||
var torqueWidth = Vector3.Cross(transform.up, normalLatitudinal);
|
||
_RigidBody.AddTorque(torqueWidth * _BuoyancyTorqueStrength, ForceMode.Acceleration);
|
||
_RigidBody.AddTorque(-_AngularDrag * _RigidBody.angularVelocity);
|
||
}
|
||
}
|
||
|
||
// 相对于水进行拖拽操作
|
||
if (_Drag != Vector3.zero)
|
||
{
|
||
var velocityRelativeToWater = _RigidBody.linearVelocity - surfaceVelocity;
|
||
|
||
var forcePosition = _RigidBody.worldCenterOfMass + _ForceHeightOffset * Vector3.up;
|
||
_RigidBody.AddForceAtPosition(
|
||
_Drag.x * Vector3.Dot(transform.right, -velocityRelativeToWater) * transform.right, forcePosition,
|
||
ForceMode.Acceleration);
|
||
_RigidBody.AddForceAtPosition(_Drag.y * Vector3.Dot(Vector3.up, -velocityRelativeToWater) * Vector3.up,
|
||
forcePosition, ForceMode.Acceleration);
|
||
_RigidBody.AddForceAtPosition(
|
||
_Drag.z * Vector3.Dot(transform.forward, -velocityRelativeToWater) * transform.forward,
|
||
forcePosition, ForceMode.Acceleration);
|
||
}
|
||
|
||
s_FixedUpdateMarker.End();
|
||
}
|
||
}
|
||
|
||
static class DebugUtility
|
||
{
|
||
public delegate void DrawLine(Vector3 position, Vector3 up, Color color, float duration);
|
||
|
||
public static void DrawCross(DrawLine draw, Vector3 position, float r, Color color, float duration = 0f)
|
||
{
|
||
draw(position - Vector3.up * r, position + Vector3.up * r, color, duration);
|
||
draw(position - Vector3.right * r, position + Vector3.right * r, color, duration);
|
||
draw(position - Vector3.forward * r, position + Vector3.forward * r, color, duration);
|
||
}
|
||
|
||
public static void DrawCross(DrawLine draw, Vector3 position, Vector3 up, float r, Color color,
|
||
float duration = 0f)
|
||
{
|
||
up.Normalize();
|
||
var right = Vector3.Normalize(Vector3.Cross(up, Vector3.forward));
|
||
var forward = Vector3.Cross(up, right);
|
||
draw(position - up * r, position + up * r, color, duration);
|
||
draw(position - right * r, position + right * r, color, duration);
|
||
draw(position - forward * r, position + forward * r, color, duration);
|
||
}
|
||
}
|
||
} |