添加浮力脚本

This commit is contained in:
Bob.Song
2026-02-27 14:54:38 +08:00
parent fedef338c1
commit a6e061d9ce
30 changed files with 3059 additions and 1971 deletions

View File

@@ -0,0 +1,219 @@
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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c103db8c4b014d87845103a400b1ace5
timeCreated: 1772159688

View File

@@ -1,209 +0,0 @@
using System;
using UnityEngine;
[ExecuteInEditMode]
[RequireComponent(typeof(Rigidbody))]
public class Buoyancy : MonoBehaviour
{
// ====== No water plugin dependency ======
[Header("Water (No Plugin)")]
[Tooltip("Fallback flat water level (Y). Set this to match your scene water height for now.")]
public float waterLevel = 0f;
[Tooltip("If false, buoyancy is disabled and rigidbody uses gravity.")]
public bool waterAvailable = true;
// ====== Original fields (kept) ======
public bool includeDeformation = true;
[Tooltip("Approxative radius of object for buoyancy.")]
public float sphereRadiusApproximation = 0.25f;
[Tooltip("Specifies the multiplier for the movement induced by other deformation (waves, swell... etc).")]
public float waveForceMultiplier = 1f;
[Tooltip("Specifies the multiplier for the movement induced by the current of the water surface.")]
public float currentSpeedMultiplier = 1f;
[Tooltip("Specifies the multiplier for the drag forces induced by the viscosity of the mediums.")]
public float dragMultiplier = 1f;
public float defaultRigidbodyDrag = 0.1f;
public float underwaterRigidbodyAngularDrag = 1f;
public float overwaterRigidbodyAngularDrag = 0.05f;
[Tooltip("Specifies the value for surface tension. A high value will stop the object bouncing faster on water.")]
public float surfaceTensionDamping = 10f;
[Tooltip("When enabled, the net force is applied with a random offset to create an angular velocity.")]
public bool applyForceWithRandomOffset;
// 原脚本里这个是 HDRP 的“自定义网格水面高度偏移”,这里保留字段但不再依赖水插件
[Tooltip("Optional extra offset added to sampled water height. Useful if your water mesh pivot isn't at surface level.")]
public float waterHeightOffset = 0f;
public bool drawDebug;
private Vector3 currentDirection;
private Vector3 A;
private Vector3 B;
private Vector3 C;
private Vector3 waterPosition;
private Vector3 normal;
private Vector3 deformationDirection;
private Rigidbody rigidbodyComponent;
private float h;
private float hNormalized;
private bool _isEnabled = true;
private void Start()
{
rigidbodyComponent = GetComponent<Rigidbody>();
rigidbodyComponent.useGravity = false;
rigidbodyComponent.linearDamping = defaultRigidbodyDrag;
}
public void EnablePhysics(bool set)
{
_isEnabled = set;
}
private void FixedUpdate()
{
// ✅ 关键:没有水 / 禁用时回到“用Unity重力”
if (!_isEnabled || !waterAvailable)
{
h = (hNormalized = 0f);
rigidbodyComponent.useGravity = true;
return;
}
rigidbodyComponent.useGravity = false;
FetchWaterSurfaceData(transform.position, out waterPosition, out normal, out currentDirection);
deformationDirection = Vector3.ProjectOnPlane(normal, Vector3.up);
h = Mathf.Clamp(
waterPosition.y - (transform.position.y - sphereRadiusApproximation),
0f,
2f * sphereRadiusApproximation
);
hNormalized = h * 1f / (2f * sphereRadiusApproximation);
float num = MathF.PI * h * h / 3f * (3f * sphereRadiusApproximation - h);
rigidbodyComponent.angularDamping = Mathf.Lerp(overwaterRigidbodyAngularDrag, underwaterRigidbodyAngularDrag, hNormalized);
// === 以下保持原脚本单位/写法(尽量贴近原行为) ===
Vector3 b = rigidbodyComponent.mass * Physics.gravity;
Vector3 vector = Vector3.Lerp(Physics.gravity, b, hNormalized);
float num2 = 997f;
float num3 = 0.001293f;
float num4 = 1.81E-05f;
float num5 = 0.001f;
float num6 = 0.47f;
Vector3 vector2 = (0f - num2) * num * Physics.gravity;
Vector3 a = MathF.PI * 6f * sphereRadiusApproximation * num4 * -rigidbodyComponent.linearVelocity;
Vector3 b2 = MathF.PI * 6f * sphereRadiusApproximation * num5 * -rigidbodyComponent.linearVelocity;
Vector3 vector3 = Vector3.Lerp(a, b2, hNormalized) * dragMultiplier;
float num7 = Mathf.Lerp(
b: Mathf.Sqrt(2f * rigidbodyComponent.mass * (0f - Physics.gravity.y) /
(num2 * MathF.PI * Mathf.Pow(sphereRadiusApproximation, 2f) * num6)),
a: Mathf.Sqrt(2f * rigidbodyComponent.mass * (0f - Physics.gravity.y) /
(num3 * MathF.PI * Mathf.Pow(sphereRadiusApproximation, 2f) * num6)),
t: hNormalized
);
Vector3 force = vector + vector2 + vector3;
Vector3 vector4 = applyForceWithRandomOffset
? (new Vector3(
UnityEngine.Random.Range(-1f, 1f),
UnityEngine.Random.Range(-1f, 1f),
UnityEngine.Random.Range(-1f, 1f)
) * sphereRadiusApproximation / 5f)
: Vector3.zero;
rigidbodyComponent.AddForceAtPosition(force, transform.position + vector4, ForceMode.Acceleration);
if (hNormalized > 0f && hNormalized < 1f)
{
Vector3 force2 = -(Vector3.Dot(rigidbodyComponent.linearVelocity, Physics.gravity.normalized)
* Physics.gravity.normalized) * surfaceTensionDamping;
rigidbodyComponent.AddForce(force2, ForceMode.Acceleration);
if (includeDeformation)
{
rigidbodyComponent.AddForce(deformationDirection * waveForceMultiplier, ForceMode.Acceleration);
rigidbodyComponent.AddForce(currentDirection * currentSpeedMultiplier, ForceMode.Acceleration);
}
}
if (rigidbodyComponent.linearVelocity.magnitude > num7)
{
rigidbodyComponent.linearVelocity = rigidbodyComponent.linearVelocity.normalized * num7;
}
}
private Vector3 FetchWaterSurfaceData(Vector3 point, out Vector3 positionWS, out Vector3 normalWS, out Vector3 currentDirectionWS)
{
// ✅ 插件无关:统一从 GetWaterInfo 拿数据
GetWaterInfo(point, out float waterHeight, out normalWS, out currentDirectionWS);
positionWS = new Vector3(point.x, waterHeight + waterHeightOffset, point.z);
if (normalWS == Vector3.zero) normalWS = Vector3.up;
return positionWS;
}
/// <summary>
/// Water provider (no plugin dependency).
/// 默认:水平水面,高度 = waterLevel
/// 以后接 Crest5你只要改这个函数即可。
/// </summary>
protected virtual void GetWaterInfo(Vector3 worldPoint, out float waterHeight, out Vector3 waterNormal, out Vector3 waterCurrentDir)
{
waterHeight = waterLevel; // 默认水面高度可在Inspector里调对齐
waterNormal = Vector3.up; // 默认法线
waterCurrentDir = Vector3.zero; // 默认水流方向
}
public Vector3 GetCurrentWaterPosition() => waterPosition;
public float GetNormalizedHeightOfSphereBelowSurface() => hNormalized;
private void OnDrawGizmosSelected()
{
if (!drawDebug) return;
Gizmos.color = Color.magenta;
Gizmos.DrawLine(transform.position, transform.position + normal);
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position, transform.position + deformationDirection * 10f);
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, transform.position + currentDirection);
Gizmos.color = Color.yellow;
Gizmos.DrawSphere(transform.position, sphereRadiusApproximation);
Gizmos.color = Color.blue;
Gizmos.DrawSphere(A, sphereRadiusApproximation / 10f);
Gizmos.DrawSphere(B, sphereRadiusApproximation / 10f);
Gizmos.DrawSphere(C, sphereRadiusApproximation / 10f);
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: dd1aa9f0de8b435448c6893ecc60d021

View File

@@ -1,16 +0,0 @@
using UnityEngine;
public interface IWaterHeightProvider
{
/// <summary>返回 worldPos 位置的水面高度Y</summary>
float GetWaterHeight(Vector3 worldPos);
}
/// <summary>
/// 最简单的水面:固定平面(适合先跑通逻辑)
/// </summary>
public class FlatWaterHeightProvider : MonoBehaviour, IWaterHeightProvider
{
public float waterY = 0f;
public float GetWaterHeight(Vector3 worldPos) => waterY;
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: a1300e9b5a5c347408708087176324c0

View File

@@ -1,370 +0,0 @@
// using UnityEngine;
//
// [DisallowMultipleComponent]
// public class FloatBobberController : MonoBehaviour
// {
// public enum FloatState
// {
// FreePhysics, // 正常物理
// Standing, // 站漂(可选:更“稳”的物理态)
// Laying, // 躺漂(浮力过大/出水过多)
// AnimBite // 漂相动画接管:黑漂/顿漂/移漂/顶漂
// }
//
// public enum BiteType
// {
// Black, // 黑漂:快速下沉
// Dip, // 顿漂:下压一下回弹
// Move, // 移漂:水平拖动
// Lift // 顶漂:上顶(上浮)
// }
//
// [Header("Refs")]
// public Rigidbody rb;
// public ConfigurableJoint joint; // 浮漂与鱼钩端连接的 joint
// public MonoBehaviour waterProviderMB; // 拖一个实现 IWaterHeightProvider 的组件(如 FlatWaterHeightProvider / KWS适配器
// private IWaterHeightProvider water;
//
// [Header("Buoyancy")]
// [Tooltip("浮漂的有效高度(决定淹没比例计算)")]
// public float floatHeight = 0.18f;
// [Tooltip("浮力系数(越大越容易浮起)")]
// public float buoyancy = 18f;
// [Tooltip("水中阻尼(抑制乱抖/失控)")]
// public float waterDrag = 2.0f;
// [Tooltip("水中角阻尼")]
// public float waterAngularDrag = 2.0f;
//
// [Header("Auto Lay (躺漂判定)")]
// [Tooltip("出水比例超过这个值0~1且浮力明显大于重力时进入躺漂")]
// [Range(0f, 1f)] public float tooMuchOutOfWater01 = 0.55f;
// [Tooltip("浮力/重力比超过该值,认为浮力过大(更容易躺漂)")]
// public float buoyancyOverGravityToLay = 1.15f;
// [Tooltip("躺漂时目标倾角(度)")]
// public float layTiltAngle = 75f;
// [Tooltip("躺漂倾斜速度")]
// public float layTiltSpeed = 6f;
//
// [Header("Joint Drive (用于动画接管)")]
// public float animDriveSpring = 1200f;
// public float animDriveDamper = 120f;
// public float animMaxForce = 10000f;
//
// [Header("Bite Animation Settings")]
// public BiteAnim black = new BiteAnim
// {
// duration = 0.35f,
// verticalOffset = -0.14f,
// curve = AnimationCurve.EaseInOut(0, 0, 1, 1)
// };
//
// public BiteAnim dip = new BiteAnim
// {
// duration = 0.55f,
// verticalOffset = -0.08f,
// curve = new AnimationCurve(
// new Keyframe(0, 0),
// new Keyframe(0.25f, 1f),
// new Keyframe(0.6f, 0.2f),
// new Keyframe(1f, 0)
// )
// };
//
// public BiteAnim lift = new BiteAnim
// {
// duration = 0.6f,
// verticalOffset = +0.06f,
// curve = new AnimationCurve(
// new Keyframe(0, 0),
// new Keyframe(0.3f, 1f),
// new Keyframe(1f, 0)
// )
// };
//
// public BiteAnim move = new BiteAnim
// {
// duration = 0.9f,
// horizontalOffset = 0.18f,
// curve = AnimationCurve.EaseInOut(0, 0, 1, 1)
// };
//
// [System.Serializable]
// public struct BiteAnim
// {
// public float duration;
// public AnimationCurve curve;
// [Tooltip("相对水面的竖直偏移幅度m负数=下沉,正数=上顶")]
// public float verticalOffset;
// [Tooltip("水平偏移幅度m用于移漂沿 forward 或指定方向)")]
// public float horizontalOffset;
// }
//
// [Header("Runtime")]
// public FloatState state = FloatState.FreePhysics;
//
// // 内部:动画接管
// private BiteType currentBite;
// private float biteT;
// private float biteDuration;
// private BiteAnim biteAnim;
// private Vector3 biteDirWorld; // 移漂方向
// private Vector3 jointBaseTargetPos;
// private bool jointHadTarget;
//
// private void Reset()
// {
// rb = GetComponent<Rigidbody>();
// joint = GetComponent<ConfigurableJoint>();
// }
//
// private void Awake()
// {
// if (!rb) rb = GetComponent<Rigidbody>();
// if (!joint) joint = GetComponent<ConfigurableJoint>();
//
// water = waterProviderMB as IWaterHeightProvider;
// if (water == null && waterProviderMB != null)
// water = waterProviderMB.GetComponent<IWaterHeightProvider>();
//
// // joint 初始化建议(确保 targetPosition 驱动生效)
// if (joint)
// {
// joint.configuredInWorldSpace = false; // targetPosition 在 joint 的本地坐标系更稳定
// joint.rotationDriveMode = RotationDriveMode.Slerp;
//
// // 建议锁住角度,避免动画接管时乱拧;如果你需要漂转向可放开
// joint.angularXMotion = ConfigurableJointMotion.Locked;
// joint.angularYMotion = ConfigurableJointMotion.Locked;
// joint.angularZMotion = ConfigurableJointMotion.Locked;
//
// // 位置建议X/Z 限制Y 可自由(看你钓组结构)
// // 这里不强制修改,你项目里如果已经配好了就别动
// }
// }
//
// private void FixedUpdate()
// {
// if (water == null)
// {
// // 没水面采样就只能纯物理
// state = FloatState.FreePhysics;
// return;
// }
//
// Vector3 pos = rb.position;
// float waterY = water.GetWaterHeight(pos);
//
// // 计算淹没比例:以浮漂中心为基准,上下 floatHeight/2
// float topY = pos.y + floatHeight * 0.5f;
// float bottomY = pos.y - floatHeight * 0.5f;
//
// float submerged01 = 0f;
// if (waterY <= bottomY) submerged01 = 0f;
// else if (waterY >= topY) submerged01 = 1f;
// else submerged01 = Mathf.InverseLerp(bottomY, topY, waterY);
//
// float outOfWater01 = 1f - submerged01;
//
// // 基础浮力 & 阻尼(只在水中起作用)
// ApplyBuoyancy(submerged01, waterY);
//
// // 躺漂判定(只在非动画接管时)
// if (state != FloatState.AnimBite)
// {
// bool shouldLay = ShouldLay(outOfWater01, submerged01);
// if (shouldLay)
// state = FloatState.Laying;
// else
// state = FloatState.FreePhysics; // 你也可以在这里细分 Standing
// }
//
// // 状态行为
// switch (state)
// {
// case FloatState.Laying:
// DoLay(waterY);
// break;
//
// case FloatState.AnimBite:
// DoBiteAnim(waterY);
// break;
//
// default:
// // 正常物理时恢复 joint drive避免被动画参数影响
// RestoreJointDriveIfNeeded();
// break;
// }
// }
//
// private void ApplyBuoyancy(float submerged01, float waterY)
// {
// // 水中阻尼:淹没越多阻尼越强
// rb.linearDamping = Mathf.Lerp(rb.linearDamping, submerged01 > 0 ? waterDrag : 0f, 0.25f);
// rb.angularDamping = Mathf.Lerp(rb.angularDamping, submerged01 > 0 ? waterAngularDrag : 0.05f, 0.25f);
//
// if (submerged01 <= 0f) return;
//
// // 浮力:与淹没比例近似线性(你也可以换成平方让“接近全淹没时更强”)
// float g = Physics.gravity.magnitude;
// float buoyForce = buoyancy * submerged01;
//
// // 让浮力作用点略低于中心,产生一点“站漂”稳定性(可选)
// Vector3 forcePoint = rb.worldCenterOfMass + Vector3.down * (floatHeight * 0.15f);
//
// rb.AddForceAtPosition(Vector3.up * buoyForce, forcePoint, ForceMode.Force);
//
// // 额外:把浮漂轻轻拉向水面(避免小抖导致上下乱跳)
// float surfacePull = 6f * submerged01;
// float yError = waterY - rb.position.y;
// rb.AddForce(Vector3.up * (yError * surfacePull), ForceMode.Force);
// }
//
// private bool ShouldLay(float outOfWater01, float submerged01)
// {
// if (submerged01 <= 0f) return false;
//
// // “浮力/重力”粗判浮力系数与质量、g相关
// float gravityForce = rb.mass * Physics.gravity.magnitude;
// // 这里的 buoyancy 是 ForceMode.Force 下的“牛顿”,所以直接比值即可
// float buoyForceAtFull = buoyancy; // submerged=1 时
// float ratio = buoyForceAtFull / Mathf.Max(0.0001f, gravityForce);
//
// return (outOfWater01 >= tooMuchOutOfWater01) && (ratio >= buoyancyOverGravityToLay);
// }
//
// private void DoLay(float waterY)
// {
// // 躺漂:把浮漂慢慢倾斜到 layTiltAngle同时让它更贴近水面
// Quaternion targetRot = Quaternion.AngleAxis(layTiltAngle, transform.right) * Quaternion.LookRotation(transform.forward, Vector3.up);
// rb.MoveRotation(Quaternion.Slerp(rb.rotation, targetRot, layTiltSpeed * Time.fixedDeltaTime));
//
// // 贴近水面一点(不要完全锁死,保持自然)
// float targetY = waterY + floatHeight * 0.15f;
// Vector3 p = rb.position;
// p.y = Mathf.Lerp(p.y, targetY, 3f * Time.fixedDeltaTime);
// rb.MovePosition(p);
//
// RestoreJointDriveIfNeeded();
// }
//
// private void DoBiteAnim(float waterY)
// {
// biteT += Time.fixedDeltaTime;
// float t01 = (biteDuration <= 0.0001f) ? 1f : Mathf.Clamp01(biteT / biteDuration);
// float k = biteAnim.curve != null ? biteAnim.curve.Evaluate(t01) : t01;
//
// // 目标偏移:相对水面
// float yOff = biteAnim.verticalOffset * k;
// float xzOff = biteAnim.horizontalOffset * k;
//
// // 用 joint.targetPosition 驱动:更像“鱼线/钓组拉着漂走/压漂”
// if (joint)
// {
// EnsureJointDriveForAnim();
//
// // joint.targetPosition 是在 joint space本地
// // 这里做一个“基准 targetPosition + 偏移”,基准取进入动画时的 targetPosition
// Vector3 target = jointBaseTargetPos;
//
// // 竖直:沿 joint 的 local Y
// target += Vector3.up * yOff;
//
// // 移漂:我们用世界方向投到 joint 的 local XZ近似
// if (currentBite == BiteType.Move && xzOff != 0f)
// {
// Vector3 worldOffset = biteDirWorld.normalized * xzOff;
// Vector3 localOffset = transform.InverseTransformVector(worldOffset);
// target += new Vector3(localOffset.x, 0f, localOffset.z);
// }
//
// joint.targetPosition = target;
// }
// else
// {
// // 没 joint 就退化:用 MovePosition 做受控位移(稳定但不如 joint 自然)
// Vector3 p = rb.position;
// p.y = Mathf.Lerp(p.y, waterY + yOff, 12f * Time.fixedDeltaTime);
// if (currentBite == BiteType.Move && xzOff != 0f)
// p += biteDirWorld.normalized * (xzOff * 0.2f); // 轻推
// rb.MovePosition(p);
// }
//
// // 动画结束,回到物理
// if (t01 >= 1f)
// {
// state = FloatState.FreePhysics;
// RestoreJointDriveIfNeeded();
// }
// }
//
// private void EnsureJointDriveForAnim()
// {
// if (!joint) return;
//
// if (!jointHadTarget)
// {
// jointHadTarget = true;
// jointBaseTargetPos = joint.targetPosition;
// }
//
// // 只设置一次也行,这里每次保证一致
// JointDrive d = new JointDrive
// {
// positionSpring = animDriveSpring,
// positionDamper = animDriveDamper,
// maximumForce = animMaxForce
// };
//
// joint.xDrive = d;
// joint.yDrive = d;
// joint.zDrive = d;
// }
//
// private void RestoreJointDriveIfNeeded()
// {
// // 退出动画时恢复基准 target避免残留偏移
// if (!joint) return;
// if (!jointHadTarget) return;
//
// // 慢慢回到基准 targetPosition更平滑
// joint.targetPosition = Vector3.Lerp(joint.targetPosition, jointBaseTargetPos, 8f * Time.fixedDeltaTime);
//
// // 当接近后释放
// if ((joint.targetPosition - jointBaseTargetPos).sqrMagnitude < 0.000001f)
// {
// joint.targetPosition = jointBaseTargetPos;
// jointHadTarget = false;
// }
// }
//
// /// <summary>
// /// 外部触发漂相(鱼咬钩/流水/风/玩家提竿前的反馈等)
// /// </summary>
// public void PlayBite(BiteType type, Vector3? moveDirWorld = null)
// {
// currentBite = type;
// biteT = 0f;
//
// biteAnim = type switch
// {
// BiteType.Black => black,
// BiteType.Dip => dip,
// BiteType.Move => move,
// BiteType.Lift => lift,
// _ => black
// };
//
// biteDuration = Mathf.Max(0.02f, biteAnim.duration);
// biteDirWorld = moveDirWorld ?? transform.forward;
//
// // 进入动画接管
// state = FloatState.AnimBite;
//
// // 记录进入动画时 joint 的基准 target
// if (joint)
// {
// jointBaseTargetPos = joint.targetPosition;
// jointHadTarget = true;
// }
// }
// }

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 4c9bb50ca0b0b5547937d3ce82577fad