修改浮漂和绳子逻辑

This commit is contained in:
Bob.Song
2026-03-26 12:18:24 +08:00
parent 6b14035ad9
commit 83da5b3196
10 changed files with 1774 additions and 1323 deletions

View File

@@ -85,6 +85,12 @@ public class Rope : MonoBehaviour
[SerializeField, Range(1, 8), Tooltip("每隔多少次FixedUpdate更新一次水面约束")]
private int waterUpdateEvery = 1;
[SerializeField, Range(0f, 1f), Tooltip("水面约束抬升强度(每次更新的插值强度),越小越渐进")]
private float waterLiftStrength = 0.25f;
[SerializeField, Tooltip("startAnchor 在水下时,让其相邻端节点强制跟随 startAnchor避免被抬到水面导致脱离")]
private bool keepStartAdjacentNodeFollow = true;
[SerializeField, Range(0, 8), Tooltip("水面约束后,再做几次长度约束,减少局部折角")]
private int waterPostConstraintIterations = 2;
@@ -201,6 +207,7 @@ public class Rope : MonoBehaviour
waterSampleStep = Mathf.Max(1, waterSampleStep);
waterUpdateEvery = Mathf.Max(1, waterUpdateEvery);
waterSurfaceOffset = Mathf.Max(0f, waterSurfaceOffset);
waterLiftStrength = Mathf.Clamp01(waterLiftStrength);
waterPostConstraintIterations = Mathf.Clamp(waterPostConstraintIterations, 0, 8);
}
@@ -630,16 +637,18 @@ public class Rope : MonoBehaviour
int step = Mathf.Max(1, waterSampleStep);
float surfaceY = waterLevelY + waterSurfaceOffset;
bool startUnderWater = _pCurr[0].y < surfaceY;
int startAdjacentIdx = GetStartAdjacentNodeIndex(last);
int prevSampleIdx = 1;
float prevSurfaceY = surfaceY;
ApplyWaterSurface(prevSampleIdx, prevSurfaceY);
ApplyWaterSurface(prevSampleIdx, prevSurfaceY, startUnderWater, startAdjacentIdx);
for (int i = 1 + step; i < last; i += step)
{
float nextSurfaceY = surfaceY;
ApplyWaterSurface(i, nextSurfaceY);
ApplyWaterSurface(i, nextSurfaceY, startUnderWater, startAdjacentIdx);
if (waterInterpolate)
{
@@ -651,13 +660,13 @@ public class Rope : MonoBehaviour
int idx = a + j;
float t = j / (float)span;
float y = Mathf.Lerp(prevSurfaceY, nextSurfaceY, t);
ApplyWaterSurface(idx, y);
ApplyWaterSurface(idx, y, startUnderWater, startAdjacentIdx);
}
}
else
{
for (int idx = prevSampleIdx + 1; idx < i; idx++)
ApplyWaterSurface(idx, prevSurfaceY);
ApplyWaterSurface(idx, prevSurfaceY, startUnderWater, startAdjacentIdx);
}
prevSampleIdx = i;
@@ -665,20 +674,38 @@ public class Rope : MonoBehaviour
}
for (int i = prevSampleIdx + 1; i < last; i++)
ApplyWaterSurface(i, prevSurfaceY);
ApplyWaterSurface(i, prevSurfaceY, startUnderWater, startAdjacentIdx);
}
private void ApplyWaterSurface(int i, float surfaceY)
private int GetStartAdjacentNodeIndex(int last)
{
if (last <= 1) return 1;
Vector3 s = _pCurr[0];
float d1 = (_pCurr[1] - s).sqrMagnitude;
float d2 = (_pCurr[last - 1] - s).sqrMagnitude;
return d1 <= d2 ? 1 : last - 1;
}
private void ApplyWaterSurface(int i, float surfaceY, bool startUnderWater, int startAdjacentIdx)
{
if (keepStartAdjacentNodeFollow && startUnderWater && i == startAdjacentIdx)
{
Vector3 s = _pCurr[0];
_pCurr[i] = s;
_pPrev[i] = s;
return;
}
Vector3 p = _pCurr[i];
if (p.y < surfaceY)
{
p.y = surfaceY;
p.y = Mathf.Lerp(p.y, surfaceY, waterLiftStrength);
_pCurr[i] = p;
// 同步 prev杀掉向下惯性,避免反复穿透水面
// 渐进同步 prev削弱向下惯性,避免反复穿透水面
Vector3 prev = _pPrev[i];
if (prev.y < surfaceY) prev.y = surfaceY;
if (prev.y < p.y) prev.y = Mathf.Lerp(prev.y, p.y, waterLiftStrength);
_pPrev[i] = prev;
}
}
@@ -798,4 +825,4 @@ public class Rope : MonoBehaviour
for (int i = 0; i < _physicsNodes; i++)
Gizmos.DrawSphere(_pCurr[i], 0.01f);
}
}
}

View File

@@ -1,5 +1,7 @@
using UnityEngine;
using WaveHarmonic.Crest;
/// <summary>
/// 简单水面接口。你可以替换成自己的水系统。
/// </summary>
@@ -26,10 +28,10 @@ public enum BobberControlMode
public enum BobberBiteType
{
None,
Tap, // 轻点
SlowSink, // 缓沉
Lift, // 送漂
BlackDrift // 黑漂/快速拖入
Tap, // 轻点
SlowSink, // 缓沉
Lift, // 送漂
BlackDrift // 黑漂/快速拖入
}
public enum BobberPosture
@@ -43,135 +45,106 @@ public enum BobberPosture
[RequireComponent(typeof(Rigidbody))]
public class BobberPresentationController : MonoBehaviour
{
[Header("Water")]
[Tooltip("没有水提供器时使用固定水位")]
[Header("Water")] [Tooltip("没有水提供器时使用固定水位")]
public float fallbackWaterLevel = 0f;
[Tooltip("Crest 水体。为空时会尝试从 SceneSettings 读取")]
public WaterRenderer waterRenderer;
[Tooltip("Crest 查询层级")] public CollisionLayer waterCollisionLayer = CollisionLayer.AfterAnimatedWaves;
[Tooltip("Crest 波面查询宽度(参考 BobberFloating")]
public float waterQueryObjectWidth = 0.5f;
[Tooltip("可选:挂实现了 IWaterSurfaceProvider 的组件")]
public MonoBehaviour waterProviderBehaviour;
[Header("Enter Water")]
[Tooltip("底部进入水面多少米后切换为漂像控制")]
[Header("Enter Water")] [Tooltip("底部进入水面多少米后切换为漂像控制")]
public float enterWaterDepth = 0.002f;
[Tooltip("离开水面多少米后回到空中物理。一般给负值做滞回")]
public float exitWaterDepth = -0.01f;
[Tooltip("离开水面多少米后回到空中物理。一般给负值做滞回")] public float exitWaterDepth = -0.01f;
[Header("Geometry")]
[Tooltip("浮漂总高度(米)")]
[Header("Geometry")] [Tooltip("浮漂总高度(米)")]
public float floatHeight = 0.08f;
[Tooltip("如果 Pivot 在浮漂底部,这里填 0如果 Pivot 在模型中心,就填底部相对 Pivot 的本地 Y")]
public float bottomOffsetLocalY = 0f;
[Header("Base Float")]
[Tooltip("基础吃铅比例,决定静止时有多少在水下")]
[Range(0.05f, 0.95f)]
[Header("Base Float")] [Tooltip("基础吃铅比例,决定静止时有多少在水下")] [Range(0.05f, 0.95f)]
public float baseSubmergeRatio = 0.28f;
[Tooltip("Y 轴平滑时间,越小响应越快")]
public float ySmoothTime = 0.08f;
[Tooltip("Y 轴平滑时间,越小响应越快")] public float ySmoothTime = 0.08f;
[Tooltip("最大竖直速度限制(用于 SmoothDamp")]
public float maxYSpeed = 2f;
[Tooltip("最大竖直速度限制(用于 SmoothDamp")] public float maxYSpeed = 2f;
[Tooltip("静止小死区,减少微抖")]
public float yDeadZone = 0.0005f;
[Tooltip("静止小死区,减少微抖")] public float yDeadZone = 0.0005f;
[Header("Surface Motion")]
[Tooltip("是否启用轻微水面起伏")]
[Header("Surface Motion")] [Tooltip("是否启用轻微水面起伏")]
public bool enableSurfaceBobbing = true;
[Tooltip("水面轻微起伏振幅(米)")]
public float surfaceBobAmplitude = 0.0015f;
[Tooltip("水面轻微起伏振幅(米)")] public float surfaceBobAmplitude = 0.0015f;
[Tooltip("水面轻微起伏频率")]
public float surfaceBobFrequency = 1.2f;
[Tooltip("水面轻微起伏频率")] public float surfaceBobFrequency = 1.2f;
[Header("XZ Motion")]
[Tooltip("入水后是否锁定 XZ 到入水点附近")]
[Header("XZ Motion")] [Tooltip("入水后是否锁定 XZ 到入水点附近")]
public bool lockXZAroundAnchor = true;
[Tooltip("XZ 跟随平滑时间")]
public float xzSmoothTime = 0.15f;
[Tooltip("XZ 跟随平滑时间")] public float xzSmoothTime = 0.15f;
[Tooltip("水流/拖拽带来的额外平面偏移最大值")]
public float maxPlanarOffset = 0.15f;
[Tooltip("水流/拖拽带来的额外平面偏移最大值")] public float maxPlanarOffset = 0.15f;
[Header("Sink By Weight / Tension")]
[Tooltip("外部向下拉力映射为下沉量的系数。你可以把钩/铅/线组的等效向下拉力喂进来")]
[Header("Sink By Weight / Tension")] [Tooltip("外部向下拉力映射为下沉量的系数。你可以把钩/铅/线组的等效向下拉力喂进来")]
public float downForceToSink = 0.0025f;
[Tooltip("向下拉力下沉的最大附加量")]
public float maxExtraSink = 0.08f;
[Tooltip("向下拉力下沉的最大附加量")] public float maxExtraSink = 0.08f;
[Header("Bottom Touch")]
[Tooltip("触底时是否启用修正")]
[Header("Bottom Touch")] [Tooltip("触底时是否启用修正")]
public bool enableBottomTouchAdjust = true;
[Tooltip("触底后减少的下沉量(例如铅坠到底,漂会回升一点)")]
public float bottomTouchLift = 0.01f;
[Tooltip("触底后减少的下沉量(例如铅坠到底,漂会回升一点)")] public float bottomTouchLift = 0.01f;
[Header("Posture Source")]
[Tooltip("下方 Lure / 钩组 / 铅坠的刚体。姿态主要根据它和浮漂的相对位置判断")]
[Header("Posture Source")] [Tooltip("下方 Lure / 钩组 / 铅坠的刚体。姿态主要根据它和浮漂的相对位置判断")]
public Rigidbody lureBody;
[Tooltip("用于归一化的参考长度。一般填:浮漂到 Lure 在“正常拉直”时的大致长度")]
public float referenceLength = 0.30f;
[Header("Posture Threshold")]
[Tooltip("最小入水比例。不够时优先躺漂")]
[Header("Posture Threshold")] [Tooltip("最小入水比例。不够时优先躺漂")]
public float minSubmergeToStand = 0.16f;
[Tooltip("垂直分量比低于该值时,优先躺漂")]
public float verticalLieThreshold = 0.18f;
[Tooltip("垂直分量比低于该值时,优先躺漂")] public float verticalLieThreshold = 0.18f;
[Tooltip("垂直分量比高于该值,且水平分量较小时,允许立漂")]
public float verticalUprightThreshold = 0.75f;
[Tooltip("垂直分量比高于该值,且水平分量较小时,允许立漂")] public float verticalUprightThreshold = 0.75f;
[Tooltip("水平分量比高于该值时,不允许完全立漂")]
public float planarTiltThreshold = 0.30f;
[Tooltip("水平分量比高于该值时,不允许完全立漂")] public float planarTiltThreshold = 0.30f;
[Tooltip("水平分量明显大于垂直分量时,优先躺漂")]
public float planarDominanceMultiplier = 1.20f;
[Tooltip("水平分量明显大于垂直分量时,优先躺漂")] public float planarDominanceMultiplier = 1.20f;
[Tooltip("姿态切换滞回")]
public float postureHysteresis = 0.04f;
[Tooltip("姿态切换滞回")] public float postureHysteresis = 0.04f;
[Header("Posture Rotation")]
[Tooltip("倾斜状态角度")]
[Header("Posture Rotation")] [Tooltip("倾斜状态角度")]
public float tiltedAngle = 38f;
[Tooltip("躺漂角度")]
public float lyingAngle = 88f;
[Tooltip("躺漂角度")] public float lyingAngle = 88f;
[Tooltip("立漂时允许的最大附加倾角")]
public float uprightMaxTiltAngle = 8f;
[Tooltip("立漂时允许的最大附加倾角")] public float uprightMaxTiltAngle = 8f;
[Tooltip("平面方向对立漂/斜漂附加倾角的影响强度")]
public float planarTiltFactor = 120f;
[Tooltip("平面方向对立漂/斜漂附加倾角的影响强度")] public float planarTiltFactor = 120f;
[Tooltip("姿态平滑速度")]
public float rotationLerpSpeed = 8f;
[Tooltip("姿态平滑速度")] public float rotationLerpSpeed = 8f;
[Header("Debug Input")]
[Tooltip("调试:按 R 恢复默认")]
[Header("Debug Input")] [Tooltip("调试:按 R 恢复默认")]
public bool debugResetKey = true;
[Tooltip("调试:按 T 触发轻点")]
public bool debugTapKey = true;
[Tooltip("调试:按 T 触发轻点")] public bool debugTapKey = true;
[Tooltip("调试:按 G 触发缓沉")]
public bool debugSlowSinkKey = true;
[Tooltip("调试:按 G 触发缓沉")] public bool debugSlowSinkKey = true;
[Tooltip("调试:按 H 触发送漂")]
public bool debugLiftKey = true;
[Tooltip("调试:按 H 触发送漂")] public bool debugLiftKey = true;
[Tooltip("调试:按 B 触发黑漂")]
public bool debugBlackDriftKey = true;
[Tooltip("调试:按 B 触发黑漂")] public bool debugBlackDriftKey = true;
[Header("Debug")]
public bool drawDebug = false;
[Header("Debug")] public bool drawDebug = false;
public BobberControlMode CurrentMode => _mode;
public BobberPosture CurrentPosture => _posture;
@@ -218,6 +191,12 @@ public class BobberPresentationController : MonoBehaviour
private float _verticalDistance;
private float _planarDistance;
private bool _hasCrestSampleThisFrame;
private readonly Vector3[] _waterQueryPoints = new Vector3[1];
private readonly Vector3[] _waterQueryResultDisplacements = new Vector3[1];
private readonly Vector3[] _waterQueryResultVelocities = new Vector3[1];
private readonly Vector3[] _waterQueryResultNormal = new Vector3[1];
private void Awake()
{
_rb = GetComponent<Rigidbody>();
@@ -229,6 +208,9 @@ public class BobberPresentationController : MonoBehaviour
if (waterProviderBehaviour != null)
_waterProvider = waterProviderBehaviour as IWaterSurfaceProvider;
if (waterRenderer == null && SceneSettings.Instance != null)
waterRenderer = SceneSettings.Instance.Water;
_targetRotation = transform.rotation;
}
@@ -404,7 +386,7 @@ public class BobberPresentationController : MonoBehaviour
}
float surfaceBob = 0f;
if (enableSurfaceBobbing)
if (enableSurfaceBobbing && !_hasCrestSampleThisFrame)
{
surfaceBob = Mathf.Sin(Time.time * surfaceBobFrequency * Mathf.PI * 2f) * surfaceBobAmplitude;
}
@@ -701,6 +683,7 @@ public class BobberPresentationController : MonoBehaviour
float k = (t - 0.35f) / 0.65f;
targetOffset = -Mathf.Lerp(_biteAmplitude, 0f, k);
}
break;
case BobberBiteType.SlowSink:
@@ -742,7 +725,35 @@ public class BobberPresentationController : MonoBehaviour
private float GetWaterHeight(Vector3 worldPos)
{
return _waterProvider != null ? _waterProvider.GetWaterHeight(worldPos) : fallbackWaterLevel;
if (_waterProvider != null)
{
_hasCrestSampleThisFrame = false;
return _waterProvider.GetWaterHeight(worldPos);
}
if (
waterRenderer != null
&& waterRenderer.AnimatedWavesLod != null
&& waterRenderer.AnimatedWavesLod.Provider != null
)
{
_waterQueryPoints[0] = worldPos;
waterRenderer.AnimatedWavesLod.Provider.Query(
GetHashCode(),
Mathf.Max(0.001f, waterQueryObjectWidth),
_waterQueryPoints,
_waterQueryResultDisplacements,
_waterQueryResultNormal,
_waterQueryResultVelocities,
waterCollisionLayer
);
_hasCrestSampleThisFrame = true;
return _waterQueryResultDisplacements[0].y + waterRenderer.SeaLevel;
}
_hasCrestSampleThisFrame = false;
return fallbackWaterLevel;
}
private Vector3 GetBottomWorldPosition()
@@ -821,6 +832,7 @@ public class BobberPresentationController : MonoBehaviour
maxExtraSink = Mathf.Max(0f, maxExtraSink);
surfaceBobAmplitude = Mathf.Max(0f, surfaceBobAmplitude);
surfaceBobFrequency = Mathf.Max(0f, surfaceBobFrequency);
waterQueryObjectWidth = Mathf.Max(0.001f, waterQueryObjectWidth);
yDeadZone = Mathf.Max(0f, yDeadZone);
referenceLength = Mathf.Max(0.0001f, referenceLength);
@@ -839,4 +851,4 @@ public class BobberPresentationController : MonoBehaviour
#endif
#endregion
}
}