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); /// /// 这个物体的任何部分是否浸泡在水中? /// 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 } }; public 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); } } }