// 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(); // joint = GetComponent(); // } // // private void Awake() // { // if (!rb) rb = GetComponent(); // if (!joint) joint = GetComponent(); // // water = waterProviderMB as IWaterHeightProvider; // if (water == null && waterProviderMB != null) // water = waterProviderMB.GetComponent(); // // // 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; // } // } // // /// // /// 外部触发漂相(鱼咬钩/流水/风/玩家提竿前的反馈等) // /// // 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; // } // } // }