using UnityEngine; public class FloatBobberController : MonoBehaviour { [Header("水属性")] public float waterLevel = 0f; [Header("浮漂属性")] public float bobberVolume = 30f; // 浮漂最大排水体积 (cm³) public float bobberMass = 1f; // 浮漂自重 (g) public float bobberHeight = 0.25f; // 浮漂总高度 [Range(0, 1)] public float bobberFloatPartRatio = 0.7f; // 浮漂浮体部分占总高度的比例 [Header("配件重量")] public float sinkerWeight = 2f; public float baitWeight = 0.5f; public float hookWeight = 0.2f; [Header("行为参数")] public float fallSpeed = 8f; public float riseSpeed = 3f; public float smoothDamping = 8f; // 私有变量 private float totalDownwardWeight; private float maxBuoyancyForce; private float currentSubmergedLength; // 当前浸没长度 private float bobberFloatPartHeight; // 浮体部分高度 // 浮漂状态 private float currentBuoyancy; // 当前实际浮力 private Vector3 targetPosition; // 冲击力相关 private float impulseForce = 0f; private float impulseDecay = 4f; void Start() { InitializeBobber(); } void InitializeBobber() { // 计算浮体部分高度(能够产生浮力的部分) bobberFloatPartHeight = bobberHeight * bobberFloatPartRatio; // 最大浮力 = 浮体部分完全浸没时的排水量 maxBuoyancyForce = bobberFloatPartHeight * 100f; // 简化计算,可根据需要调整系数 // 初始位置调整:让浮漂底部刚好在水面 Vector3 pos = transform.position; pos.y = waterLevel - (bobberHeight * 0.5f); // 假设原点在中心,调整到浮漂底部在水面 transform.position = pos; RecalculateWeights(); } void FixedUpdate() { SimulateBobber(); } void RecalculateWeights() { totalDownwardWeight = bobberMass + sinkerWeight + baitWeight + hookWeight; } void SimulateBobber() { RecalculateWeights(); // ------------------------- // 1. 计算当前浸没长度和浮力 // ------------------------- // 浮漂的基准位置(底部位置) float bobberBottomY = transform.position.y; float bobberTopY = bobberBottomY + bobberHeight; // 计算浸没长度:浮漂底部到水面的距离,限制在0到浮体高度之间 float submergedLength = Mathf.Clamp(waterLevel - bobberBottomY, 0f, bobberFloatPartHeight); currentSubmergedLength = submergedLength; // 当前浮力 = (浸没长度 / 浮体高度) * 最大浮力 float submergedRatio = submergedLength / bobberFloatPartHeight; currentBuoyancy = submergedRatio * maxBuoyancyForce; // ------------------------- // 2. 计算净力并决定运动 // ------------------------- float netForce = currentBuoyancy - totalDownwardWeight; // 目标Y位置基于净力计算 float targetY = transform.position.y; if (Mathf.Abs(netForce) < 0.01f) // 基本平衡 { // 保持当前位置,微小波动可以在这里添加 targetY = transform.position.y; } else if (netForce > 0) // 浮力大于重力,上浮 { float riseAmount = netForce * 0.001f * riseSpeed; targetY += riseAmount * Time.deltaTime; // 限制不能浮出太多(露出部分不能超过非浮体部分) float maxBobberTopY = waterLevel + (bobberHeight - bobberFloatPartHeight); if (bobberTopY + riseAmount > maxBobberTopY) { targetY = maxBobberTopY - bobberHeight; } } else // 重力大于浮力,下沉 { float sinkAmount = Mathf.Abs(netForce) * 0.001f * fallSpeed; targetY -= sinkAmount * Time.deltaTime; // 限制不能沉没太多(浮体部分完全浸没后浮力达到最大) float minBobberBottomY = waterLevel - bobberFloatPartHeight; if (bobberBottomY - sinkAmount < minBobberBottomY) { targetY = minBobberBottomY; } } // ------------------------- // 3. 应用冲击力 // ------------------------- if (Mathf.Abs(impulseForce) > 0.01f) { targetY += impulseForce * Time.deltaTime; impulseForce = Mathf.Lerp(impulseForce, 0, Time.deltaTime * impulseDecay); } // ------------------------- // 4. 平滑移动 // ------------------------- targetPosition = new Vector3(transform.position.x, targetY, transform.position.z); transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * smoothDamping); // 调试信息 DebugDisplay(); } void DebugDisplay() { Debug.Log($"浸没比例: {(currentSubmergedLength / bobberFloatPartHeight):P1} " + $"当前浮力: {currentBuoyancy:F2} " + $"总重力: {totalDownwardWeight:F2} " + $"净力: {currentBuoyancy - totalDownwardWeight:F2}"); } // ---------------------------------------- // 公共方法 - 用于调漂和鱼咬钩 // ---------------------------------------- /// /// 调整配重(用于调漂) /// public void AdjustSinkerWeight(float newWeight) { sinkerWeight = newWeight; RecalculateWeights(); } /// /// 获取当前调目(浮漂露出水面的格数) /// public float GetCurrentVisibleMarks() { float bobberBottomY = transform.position.y; float visibleHeight = (bobberBottomY + bobberHeight) - waterLevel; float markHeight = bobberHeight / 10f; // 假设浮漂有10格 return Mathf.Max(0, visibleHeight / markHeight); } /// /// 设置目标调目(自动计算需要的配重) /// public void SetTargetMarks(float targetMarks) { float markHeight = bobberHeight / 10f; float targetVisibleHeight = targetMarks * markHeight; float targetSubmergedLength = bobberFloatPartHeight - targetVisibleHeight; // 需要的浮力 = (浸没长度 / 浮体高度) * 最大浮力 float requiredBuoyancy = (targetSubmergedLength / bobberFloatPartHeight) * maxBuoyancyForce; // 配重 = 浮漂自重 + 钩饵重 - 需要的浮力 float requiredSinkerWeight = bobberMass + hookWeight + baitWeight - requiredBuoyancy; sinkerWeight = Mathf.Max(0, requiredSinkerWeight); RecalculateWeights(); } // ---------------------------------------- // 鱼咬钩相关方法 // ---------------------------------------- public void TriggerDownPulse(float strength = 0.8f) { impulseForce -= Mathf.Abs(strength); } public void TriggerUpPulse(float strength = 0.8f) { impulseForce += Mathf.Abs(strength); } public void AddFishPull(float pullForce) { // 鱼的拉力相当于增加向下的力 sinkerWeight += pullForce; } public void ReleaseFishPull(float pullForce) { sinkerWeight -= pullForce; } // ---------------------------------------- // 可视化调试 // ---------------------------------------- void OnDrawGizmos() { // 绘制水面 Gizmos.color = Color.blue; Gizmos.DrawLine(new Vector3(-1, waterLevel, 0), new Vector3(1, waterLevel, 0)); // 绘制浮漂 if (Application.isPlaying) { // 浸没部分用蓝色 Gizmos.color = Color.cyan; float submergedBottom = transform.position.y; float submergedTop = submergedBottom + currentSubmergedLength; Gizmos.DrawWireCube( new Vector3(transform.position.x, (submergedBottom + submergedTop) * 0.5f, transform.position.z), new Vector3(0.1f, currentSubmergedLength, 0.1f) ); // 露出部分用红色 Gizmos.color = Color.red; float visibleBottom = submergedTop; float visibleTop = transform.position.y + bobberHeight; float visibleHeight = visibleTop - visibleBottom; if (visibleHeight > 0) { Gizmos.DrawWireCube( new Vector3(transform.position.x, (visibleBottom + visibleTop) * 0.5f, transform.position.z), new Vector3(0.1f, visibleHeight, 0.1f) ); } } } } // using UnityEngine; // // public class FloatBobberController : MonoBehaviour // { // [Header("水属性")] public float waterLevel = 0f; // [Header("浮漂最大浮力")] public float bobberVolume = 30f; // 浮漂最大浮力 (cm³) // [Header("浮漂自重")] public float bobberMass = 1f; // 浮漂自重 (g) // public float bobberHeight = 0.25f; // 浮漂长度,用来决定躺漂角度 // // [Header("配件重量")] public float sinkerWeight = 2f; // public float baitWeight = 0.5f; // public float hookWeight = 0.2f; // // [Header("Behaviour")] public float fallSpeed = 8f; // public float riseSpeed = 3f; // public float smoothDamping = 8f; // 插值平滑 // // // [Header("Noise")] public float noiseAmp = 0.015f; // // public float noiseFreq = 1.5f; // // float impulseForce = 0f; // float impulseDecay = 4f; // // void FixedUpdate() // { // SimulateBobber(); // } // // void SimulateBobber() // { // float totalDownwardWeight = bobberMass + sinkerWeight + baitWeight + hookWeight; // // float maxBuoyancy = bobberVolume; // 最大浮力 = 体积 // float netBuoyancy = maxBuoyancy - totalDownwardWeight; // // float targetY; // // // ------------------------- // // 1. 判断浮漂应该沉多少(吃水深度) // // ------------------------- // if (netBuoyancy > 0) // { // float buoyPercent = Mathf.Clamp01(netBuoyancy / maxBuoyancy); // float rise = buoyPercent * 0.1f; // 浮漂露出水面的高度 // // targetY = waterLevel + rise; // // // targetY += Mathf.Sin(Time.time * noiseFreq) * noiseAmp; // 微扰模拟波浪 // } // else // { // // 净浮力为负 → 说明浮漂整体被拉下,沉入水中 // float sinkDistance = Mathf.Abs(netBuoyancy) * 0.03f; // targetY = waterLevel - sinkDistance; // } // // // 顿口/顶漂力 // if (impulseForce != 0f) // { // targetY += impulseForce * Time.deltaTime; // impulseForce = Mathf.Lerp(impulseForce, 0, Time.deltaTime * impulseDecay); // } // // // ----------------------------- // // ③ 上浮 / 下沉差速 // // ----------------------------- // float y = transform.position.y; // float diff = targetY - y; // // if (diff > 0) // 上浮 // y += diff * Time.deltaTime * riseSpeed; // else // y += diff * Time.deltaTime * fallSpeed; // // transform.position = new Vector3(transform.position.x, y, transform.position.z); // } // // // // ---------------------------------------- // // 外部控制接口 // // ---------------------------------------- // // public void TriggerDownPulse(float s = 0.8f) // { // impulseForce -= Mathf.Abs(s); // } // // public void TriggerUpPulse(float s = 0.8f) // { // impulseForce += Mathf.Abs(s); // } // // public void AddFishPull(float v) // { // sinkerWeight += v; // } // // public void ReleaseFishPull(float v) // { // sinkerWeight -= v; // } // }