diff --git a/Assets/Resources/gfx/test.meta b/Assets/Resources/gfx/test.meta
new file mode 100644
index 000000000..13f6fc572
--- /dev/null
+++ b/Assets/Resources/gfx/test.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9b082403ccfaf6a4e97e2f5ef9ec10e0
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Resources/gfx/test/bob_25002-bob_25002_emissive.mat b/Assets/Resources/gfx/test/bob_25002-bob_25002_emissive.mat
new file mode 100644
index 000000000..d6893dae3
--- /dev/null
+++ b/Assets/Resources/gfx/test/bob_25002-bob_25002_emissive.mat
@@ -0,0 +1,140 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &-3102228912620000641
+MonoBehaviour:
+ m_ObjectHideFlags: 11
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ version: 10
+--- !u!21 &2100000
+Material:
+ serializedVersion: 8
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: bob_25002-bob_25002_emissive
+ m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
+ m_Parent: {fileID: 0}
+ m_ModifiedSerializedProperties: 0
+ m_ValidKeywords:
+ - _SPECULAR_SETUP
+ m_InvalidKeywords: []
+ m_LightmapFlags: 4
+ m_EnableInstancingVariants: 0
+ m_DoubleSidedGI: 0
+ m_CustomRenderQueue: -1
+ stringTagMap:
+ RenderType: Opaque
+ disabledShaderPasses:
+ - MOTIONVECTORS
+ m_LockedProperties:
+ m_SavedProperties:
+ serializedVersion: 3
+ m_TexEnvs:
+ - _BaseMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _BumpMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailAlbedoMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailMask:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailNormalMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _EmissionMap:
+ m_Texture: {fileID: 2800000, guid: ba7837965ad0fcc448dbdf2a1ada9942, type: 3}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _MainTex:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _MetallicGlossMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _OcclusionMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _ParallaxMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _SpecGlossMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - unity_Lightmaps:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - unity_LightmapsInd:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - unity_ShadowMasks:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ m_Ints: []
+ m_Floats:
+ - _AddPrecomputedVelocity: 0
+ - _AlphaClip: 0
+ - _AlphaToMask: 0
+ - _Blend: 0
+ - _BlendModePreserveSpecular: 1
+ - _BumpScale: 1
+ - _ClearCoatMask: 0
+ - _ClearCoatSmoothness: 0
+ - _Cull: 2
+ - _Cutoff: 0.5
+ - _DetailAlbedoMapScale: 1
+ - _DetailNormalMapScale: 1
+ - _DstBlend: 0
+ - _DstBlendAlpha: 0
+ - _EnvironmentReflections: 1
+ - _GlossMapScale: 1
+ - _Glossiness: 0.5
+ - _GlossyReflections: 1
+ - _Metallic: 0
+ - _Mode: 0
+ - _OcclusionStrength: 1
+ - _Parallax: 0.02
+ - _QueueOffset: 0
+ - _ReceiveShadows: 1
+ - _Smoothness: 0.5
+ - _SmoothnessTextureChannel: 0
+ - _SpecularHighlights: 1
+ - _SrcBlend: 1
+ - _SrcBlendAlpha: 1
+ - _Surface: 0
+ - _UVSec: 0
+ - _WorkflowMode: 0
+ - _XRMotionVectorsPass: 1
+ - _ZWrite: 1
+ m_Colors:
+ - _BaseColor: {r: 0, g: 0, b: 0, a: 1}
+ - _Color: {r: 0, g: 0, b: 0, a: 1}
+ - _EmissionColor: {r: 0.6985294, g: 0.48542836, b: 0.21572232, a: 1}
+ - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
+ m_BuildTextureStacks: []
+ m_AllowLocking: 1
diff --git a/Assets/Resources/gfx/test/bob_25002-bob_25002_emissive.mat.meta b/Assets/Resources/gfx/test/bob_25002-bob_25002_emissive.mat.meta
new file mode 100644
index 000000000..46653e713
--- /dev/null
+++ b/Assets/Resources/gfx/test/bob_25002-bob_25002_emissive.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6f221df6c6ebdb0499361f1418f55f7a
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 2100000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Resources/gfx/test/bob_25002-bob_25002_mat.mat b/Assets/Resources/gfx/test/bob_25002-bob_25002_mat.mat
new file mode 100644
index 000000000..adccc0519
--- /dev/null
+++ b/Assets/Resources/gfx/test/bob_25002-bob_25002_mat.mat
@@ -0,0 +1,144 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+ serializedVersion: 8
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: bob_25002-bob_25002_mat
+ m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
+ m_Parent: {fileID: 0}
+ m_ModifiedSerializedProperties: 0
+ m_ValidKeywords:
+ - _SPECGLOSSMAP
+ m_InvalidKeywords: []
+ m_LightmapFlags: 4
+ m_EnableInstancingVariants: 0
+ m_DoubleSidedGI: 0
+ m_CustomRenderQueue: -1
+ stringTagMap:
+ RenderType: Opaque
+ disabledShaderPasses:
+ - MOTIONVECTORS
+ m_LockedProperties:
+ m_SavedProperties:
+ serializedVersion: 3
+ m_TexEnvs:
+ - _BaseMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _BumpMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailAlbedoMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailMask:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailNormalMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _EmissionMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _MainTex:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _MetallicGlossMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _OcclusionMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _ParallaxMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _SpecGlossMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - unity_Lightmaps:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - unity_LightmapsInd:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - unity_ShadowMasks:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ m_Ints: []
+ m_Floats:
+ - _AddPrecomputedVelocity: 0
+ - _AlphaClip: 0
+ - _AlphaToMask: 0
+ - _Blend: 0
+ - _BlendModePreserveSpecular: 1
+ - _BumpMapScale: 1
+ - _BumpScale: 1
+ - _ClearCoatMask: 0
+ - _ClearCoatSmoothness: 0
+ - _Cull: 2
+ - _Cutoff: 0.5
+ - _DetailAlbedoMapScale: 1
+ - _DetailNormalMapScale: 1
+ - _DstBlend: 0
+ - _DstBlendAlpha: 0
+ - _EnvironmentReflections: 1
+ - _Fresnel: 0
+ - _GlossMapScale: 1
+ - _Glossiness: 0.7
+ - _GlossyReflections: 1
+ - _Metallic: 0.15
+ - _Mode: 0
+ - _OcclusionStrength: 1
+ - _Parallax: 0.02
+ - _QueueOffset: 0
+ - _ReceiveShadows: 1
+ - _Shininess: 6
+ - _Smoothness: 0.271
+ - _SmoothnessTextureChannel: 0
+ - _SpecInt: 1
+ - _SpecularHighlights: 1
+ - _SrcBlend: 1
+ - _SrcBlendAlpha: 1
+ - _Surface: 0
+ - _UVSec: 0
+ - _WorkflowMode: 1
+ - _XRMotionVectorsPass: 1
+ - _ZWrite: 1
+ m_Colors:
+ - _BaseColor: {r: 0, g: 0, b: 0, a: 1}
+ - _Color: {r: 0, g: 0, b: 0, a: 1}
+ - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
+ - _SpecColor: {r: 1, g: 1, b: 1, a: 1}
+ m_BuildTextureStacks: []
+ m_AllowLocking: 1
+--- !u!114 &7261695233209220307
+MonoBehaviour:
+ m_ObjectHideFlags: 11
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ version: 10
diff --git a/Assets/Resources/gfx/test/bob_25002-bob_25002_mat.mat.meta b/Assets/Resources/gfx/test/bob_25002-bob_25002_mat.mat.meta
new file mode 100644
index 000000000..6c5108f66
--- /dev/null
+++ b/Assets/Resources/gfx/test/bob_25002-bob_25002_mat.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: be41c511450533646b6a0485f18d26db
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 2100000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scenes/BobberTest.unity b/Assets/Scenes/BobberTest.unity
index 3791f1cd8..4ee5230a0 100644
--- a/Assets/Scenes/BobberTest.unity
+++ b/Assets/Scenes/BobberTest.unity
@@ -514,7 +514,7 @@ BoxCollider:
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 0.005, y: 0.005, z: 0.005}
- m_Center: {x: 0, y: -0.01, z: 0}
+ m_Center: {x: 0, y: 0, z: 0}
--- !u!1 &3062767698477047043
GameObject:
m_ObjectHideFlags: 0
@@ -627,6 +627,7 @@ GameObject:
- component: {fileID: 3372919389234347908}
- component: {fileID: 2709071706889968141}
- component: {fileID: 3372919389234347909}
+ - component: {fileID: 3372919389234347910}
m_Layer: 0
m_Name: bob_25002
m_TagString: Untagged
@@ -770,8 +771,8 @@ MeshRenderer:
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- - {fileID: 2100000, guid: 7881e4598f2626c4a8ffb3e956070a4b, type: 2}
- - {fileID: 2100000, guid: 940b6068f71807340a836245c4bbcb7e, type: 2}
+ - {fileID: 2100000, guid: be41c511450533646b6a0485f18d26db, type: 2}
+ - {fileID: 2100000, guid: 6f221df6c6ebdb0499361f1418f55f7a, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
@@ -872,21 +873,48 @@ MonoBehaviour:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3063281911495263383}
- m_Enabled: 1
+ m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f0b74408dbbfe44aee75e1ddf784d3, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FloatBobberController
- useKWS: 0
- waterLayer:
- serializedVersion: 2
- m_Bits: 0
- waterRaycastHeight: 10
- stiffness: 0.35
- damping: 0.82
- noiseStrength: 0.02
- lineAttachPoint: {fileID: 2113762193}
- tiltStrength: 0
+ waterLevel: 0
+ bobberVolume: 30
+ bobberMass: 1
+ bobberHeight: 0.25
+ sinkerWeight: 2
+ baitWeight: 0.5
+ hookWeight: 0.2
+ fallSpeed: 8
+ riseSpeed: 3
+ angleLaySpeed: 2
+ uprightSpeed: 2
+ bottomDrag: 1.2
+ maxLayAngle: 85
+ noiseAmp: 0.001
+ noiseFreq: 1.5
+--- !u!114 &3372919389234347910
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 3063281911495263383}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: c0e5ba2adc224f279b89a0adf2bd97a9, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::FloatBobberController
+ waterLevel: 0
+ bobberVolume: 4
+ bobberMass: 1
+ bobberHeight: 0.25
+ sinkerWeight: 2
+ baitWeight: 0.5
+ hookWeight: 0.2
+ fallSpeed: 8
+ riseSpeed: 3
+ smoothDamping: 8
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
diff --git a/Assets/Scripts/Editor/FloatBobberControllerEditor.cs b/Assets/Scripts/Editor/FloatBobberControllerEditor.cs
new file mode 100644
index 000000000..87b998144
--- /dev/null
+++ b/Assets/Scripts/Editor/FloatBobberControllerEditor.cs
@@ -0,0 +1,40 @@
+using UnityEditor;
+using UnityEngine;
+
+namespace NBF
+{
+ [CustomEditor(typeof(FloatBobberController))]
+ public class FloatBobberControllerEditor : Editor
+ {
+ private FloatBobberController _target;
+ void OnEnable()
+ {
+ _target = target as FloatBobberController;
+ // lookAtPoint = serializedObject.FindProperty("lookAtPoint");
+ }
+
+ public override void OnInspectorGUI()
+ {
+ base.OnInspectorGUI();
+ if (GUILayout.Button("TriggerDownPulse"))
+ {
+ _target.TriggerDownPulse();
+ }
+ if (GUILayout.Button("TriggerUpPulse"))
+ {
+ _target.TriggerUpPulse();
+ }
+ if (GUILayout.Button("AddFishPull"))
+ {
+ _target.AddFishPull(0.5f);
+ }
+ if (GUILayout.Button("AddFishPull"))
+ {
+ _target.ReleaseFishPull(0.5f);
+ }
+ // serializedObject.Update();
+ // EditorGUILayout.PropertyField(lookAtPoint);
+ // serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/Editor/FloatBobberControllerEditor.cs.meta b/Assets/Scripts/Editor/FloatBobberControllerEditor.cs.meta
new file mode 100644
index 000000000..fd51277cf
--- /dev/null
+++ b/Assets/Scripts/Editor/FloatBobberControllerEditor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d2e2de2e2fef459895d6c89eb7b66a34
+timeCreated: 1764169703
\ No newline at end of file
diff --git a/Assets/Scripts/Editor/FloatBobberControllerProEditor.cs b/Assets/Scripts/Editor/FloatBobberControllerProEditor.cs
new file mode 100644
index 000000000..725fce82b
--- /dev/null
+++ b/Assets/Scripts/Editor/FloatBobberControllerProEditor.cs
@@ -0,0 +1,40 @@
+using UnityEditor;
+using UnityEngine;
+
+namespace NBF
+{
+ [CustomEditor(typeof(FloatBobberControllerPro))]
+ public class FloatBobberControllerProEditor : Editor
+ {
+ private FloatBobberControllerPro _target;
+ void OnEnable()
+ {
+ _target = target as FloatBobberControllerPro;
+ // lookAtPoint = serializedObject.FindProperty("lookAtPoint");
+ }
+
+ public override void OnInspectorGUI()
+ {
+ base.OnInspectorGUI();
+ if (GUILayout.Button("TriggerDownPulse"))
+ {
+ _target.TriggerDownPulse();
+ }
+ if (GUILayout.Button("TriggerUpPulse"))
+ {
+ _target.TriggerUpPulse();
+ }
+ if (GUILayout.Button("AddFishPull"))
+ {
+ _target.AddFishPull(0.5f);
+ }
+ if (GUILayout.Button("AddFishPull"))
+ {
+ _target.ReleaseFishPull(0.5f);
+ }
+ // serializedObject.Update();
+ // EditorGUILayout.PropertyField(lookAtPoint);
+ // serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/Editor/FloatBobberControllerProEditor.cs.meta b/Assets/Scripts/Editor/FloatBobberControllerProEditor.cs.meta
new file mode 100644
index 000000000..6125ebdea
--- /dev/null
+++ b/Assets/Scripts/Editor/FloatBobberControllerProEditor.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a90396d330f942dea5b6ac1aff681614
+timeCreated: 1764167227
\ No newline at end of file
diff --git a/Assets/Scripts/FloatBobberController.cs b/Assets/Scripts/FloatBobberController.cs
index 507193656..6fd7f79e7 100644
--- a/Assets/Scripts/FloatBobberController.cs
+++ b/Assets/Scripts/FloatBobberController.cs
@@ -1,170 +1,361 @@
-using UnityEngine;
+using UnityEngine;
-public class FloatBobberControllerPro : MonoBehaviour
+public class FloatBobberController : MonoBehaviour
{
- [Header("Water")]
+ [Header("水属性")]
public float waterLevel = 0f;
- public float waterDensity = 1f;
-
- [Header("Bobber Physical")]
- public float bobberVolume = 30f; // 浮漂最大浮力 (cm³)
- public float bobberMass = 1f; // 浮漂自重 (g)
- public float bobberHeight = 0.25f; // 浮漂长度,用来决定躺漂角度
-
- [Header("Tackle Weight")]
+
+ [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("Behaviour")]
+ [Header("行为参数")]
public float fallSpeed = 8f;
public float riseSpeed = 3f;
- public float angleLaySpeed = 2f; // 躺漂速度
- public float uprightSpeed = 2f; // 立漂速度
- public float bottomDrag = 1.2f; // 铅坠触底阻力(越大越难被浮漂拉起)
+ 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;
- [Header("Angles")]
- public float maxLayAngle = 75f; // 最大躺漂角度
- float currentAngle = 0f;
+ void Start()
+ {
+ InitializeBobber();
+ }
- [Header("Noise")]
- public float noiseAmp = 0.015f;
- public float noiseFreq = 1.5f;
+ void InitializeBobber()
+ {
+ // 计算浮体部分高度(能够产生浮力的部分)
+ bobberFloatPartHeight = bobberHeight * bobberFloatPartRatio;
+
+ // 最大浮力 = 浮体部分完全浸没时的排水量
+ maxBuoyancyForce = bobberFloatPartHeight * 100f; // 简化计算,可根据需要调整系数
+
+ // 初始位置调整:让浮漂底部刚好在水面
+ Vector3 pos = transform.position;
+ pos.y = waterLevel - (bobberHeight * 0.5f); // 假设原点在中心,调整到浮漂底部在水面
+ transform.position = pos;
+
+ RecalculateWeights();
+ }
- float impulseForce = 0f;
- float impulseDecay = 4f;
-
- void Update()
+ void FixedUpdate()
{
SimulateBobber();
}
+ void RecalculateWeights()
+ {
+ totalDownwardWeight = bobberMass + sinkerWeight + baitWeight + hookWeight;
+ }
+
void SimulateBobber()
{
- float totalWeight = bobberMass + sinkerWeight + baitWeight + hookWeight;
- float netBuoyancy = bobberVolume - totalWeight; // 正 → 上浮;负 → 下拉
-
- // -----------------------------
- // ① 计算浮漂底部 Y 的高度
- // -----------------------------
- float bobberBottomY = transform.position.y - bobberHeight * 0.5f;
- float bottomY = waterLevel - 0.02f; // 水底高度(可替换真实地形)
-
- bool sinkerOnBottom = (bobberBottomY <= bottomY);
-
- // -----------------------------
- // ② 计算 targetY
- // -----------------------------
- float targetY;
-
- if (!sinkerOnBottom)
+ 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) // 基本平衡
{
- // 铅坠悬浮 → 浮漂直立
- if (netBuoyancy > 0)
+ // 保持当前位置,微小波动可以在这里添加
+ 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)
{
- float rise = Mathf.Clamp01(netBuoyancy / bobberVolume) * 0.1f;
- targetY = waterLevel + rise;
- }
- else
- {
- float sink = Mathf.Abs(netBuoyancy) * 0.02f;
- targetY = waterLevel - sink;
+ targetY = maxBobberTopY - bobberHeight;
}
}
- else
+ else // 重力大于浮力,下沉
{
- // 铅坠触底 → 浮漂无法再被向下拉
- if (netBuoyancy > bottomDrag)
+ float sinkAmount = Mathf.Abs(netForce) * 0.001f * fallSpeed;
+ targetY -= sinkAmount * Time.deltaTime;
+
+ // 限制不能沉没太多(浮体部分完全浸没后浮力达到最大)
+ float minBobberBottomY = waterLevel - bobberFloatPartHeight;
+ if (bobberBottomY - sinkAmount < minBobberBottomY)
{
- // 浮漂浮力足够将其立起来
- float rise = Mathf.Clamp01((netBuoyancy - bottomDrag) / bobberVolume) * 0.1f;
- targetY = waterLevel + rise; // 轻轻立起
- }
- else
- {
- // 浮漂浮力不足 → 躺漂
- targetY = waterLevel + 0.01f; // 漂身贴水
+ targetY = minBobberBottomY;
}
}
-
- // 水波噪声
- targetY += Mathf.Sin(Time.time * noiseFreq) * noiseAmp;
-
- // 顿口/顶漂力
- if (impulseForce != 0f)
+
+ // -------------------------
+ // 3. 应用冲击力
+ // -------------------------
+ if (Mathf.Abs(impulseForce) > 0.01f)
{
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);
-
- // -----------------------------
- // ④ 浮漂角度控制
- // -----------------------------
- float targetAngle = 0f;
-
- if (sinkerOnBottom)
- {
- // 触底 → 判断是否能立漂
- if (netBuoyancy > bottomDrag)
- {
- targetAngle = 0f; // 立漂
- }
- else
- {
- // 躺漂
- targetAngle = maxLayAngle;
- }
- }
- else
- {
- // 铅坠在水中 → 漂直立
- targetAngle = 0f;
- }
-
- // 平滑角度
- currentAngle = Mathf.Lerp(
- currentAngle,
- targetAngle,
- Time.deltaTime * (targetAngle == 0 ? uprightSpeed : angleLaySpeed)
- );
-
- transform.rotation = Quaternion.Euler(currentAngle, 0, 0);
+
+ // -------------------------
+ // 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 s = 0.8f)
+
+ public void TriggerDownPulse(float strength = 0.8f)
{
- impulseForce -= Mathf.Abs(s);
+ impulseForce -= Mathf.Abs(strength);
}
- public void TriggerUpPulse(float s = 0.8f)
+ public void TriggerUpPulse(float strength = 0.8f)
{
- impulseForce += Mathf.Abs(s);
+ impulseForce += Mathf.Abs(strength);
}
- public void AddFishPull(float v)
+ public void AddFishPull(float pullForce)
{
- sinkerWeight += v;
+ // 鱼的拉力相当于增加向下的力
+ sinkerWeight += pullForce;
}
- public void ReleaseFishPull(float v)
+ public void ReleaseFishPull(float pullForce)
{
- sinkerWeight -= v;
+ 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;
+// }
+// }
\ No newline at end of file
diff --git a/Assets/Scripts/FloatBobberController.cs.meta b/Assets/Scripts/FloatBobberController.cs.meta
index 8f5b04fc7..cf5cda6d5 100644
--- a/Assets/Scripts/FloatBobberController.cs.meta
+++ b/Assets/Scripts/FloatBobberController.cs.meta
@@ -1,2 +1,3 @@
fileFormatVersion: 2
-guid: 59f0b74408dbbfe44aee75e1ddf784d3
\ No newline at end of file
+guid: c0e5ba2adc224f279b89a0adf2bd97a9
+timeCreated: 1764168783
\ No newline at end of file
diff --git a/Assets/Scripts/FloatBobberControllerPro.cs b/Assets/Scripts/FloatBobberControllerPro.cs
new file mode 100644
index 000000000..cf7d68c6f
--- /dev/null
+++ b/Assets/Scripts/FloatBobberControllerPro.cs
@@ -0,0 +1,169 @@
+using UnityEngine;
+
+public class FloatBobberControllerPro : MonoBehaviour
+{
+ [Header("Water")]
+ public float waterLevel = 0f;
+
+ [Header("Bobber Physical")]
+ public float bobberVolume = 30f; // 浮漂最大浮力 (cm³)
+ public float bobberMass = 1f; // 浮漂自重 (g)
+ public float bobberHeight = 0.25f; // 浮漂长度,用来决定躺漂角度
+
+ [Header("Tackle Weight")]
+ 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 angleLaySpeed = 2f; // 躺漂速度
+ public float uprightSpeed = 2f; // 立漂速度
+ public float bottomDrag = 1.2f; // 铅坠触底阻力(越大越难被浮漂拉起)
+
+ [Header("Angles")]
+ public float maxLayAngle = 75f; // 最大躺漂角度
+ float currentAngle = 0f;
+
+ [Header("Noise")]
+ public float noiseAmp = 0.015f;
+ public float noiseFreq = 1.5f;
+
+ float impulseForce = 0f;
+ float impulseDecay = 4f;
+
+ void Update()
+ {
+ SimulateBobber();
+ }
+
+ void SimulateBobber()
+ {
+ float totalWeight = bobberMass + sinkerWeight + baitWeight + hookWeight;
+ float netBuoyancy = bobberVolume - totalWeight; // 正 → 上浮;负 → 下拉
+
+ // -----------------------------
+ // ① 计算浮漂底部 Y 的高度
+ // -----------------------------
+ float bobberBottomY = transform.position.y - bobberHeight * 0.5f;
+ float bottomY = waterLevel - 0.02f; // 水底高度(可替换真实地形)
+
+ bool sinkerOnBottom = (bobberBottomY <= bottomY);
+
+ // -----------------------------
+ // ② 计算 targetY
+ // -----------------------------
+ float targetY;
+
+ if (!sinkerOnBottom)
+ {
+ // 铅坠悬浮 → 浮漂直立
+ if (netBuoyancy > 0)
+ {
+ float rise = Mathf.Clamp01(netBuoyancy / bobberVolume) * 0.1f;
+ targetY = waterLevel + rise;
+ }
+ else
+ {
+ float sink = Mathf.Abs(netBuoyancy) * 0.02f;
+ targetY = waterLevel - sink;
+ }
+ }
+ else
+ {
+ // 铅坠触底 → 浮漂无法再被向下拉
+ if (netBuoyancy > bottomDrag)
+ {
+ // 浮漂浮力足够将其立起来
+ float rise = Mathf.Clamp01((netBuoyancy - bottomDrag) / bobberVolume) * 0.1f;
+ targetY = waterLevel + rise; // 轻轻立起
+ }
+ else
+ {
+ // 浮漂浮力不足 → 躺漂
+ targetY = waterLevel + 0.01f; // 漂身贴水
+ }
+ }
+
+ // 水波噪声
+ targetY += Mathf.Sin(Time.time * noiseFreq) * noiseAmp;
+
+ // 顿口/顶漂力
+ 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);
+
+ // -----------------------------
+ // ④ 浮漂角度控制
+ // -----------------------------
+ float targetAngle = 0f;
+
+ if (sinkerOnBottom)
+ {
+ // 触底 → 判断是否能立漂
+ if (netBuoyancy > bottomDrag)
+ {
+ targetAngle = 0f; // 立漂
+ }
+ else
+ {
+ // 躺漂
+ targetAngle = maxLayAngle;
+ }
+ }
+ else
+ {
+ // 铅坠在水中 → 漂直立
+ targetAngle = 0f;
+ }
+
+ // 平滑角度
+ currentAngle = Mathf.Lerp(
+ currentAngle,
+ targetAngle,
+ Time.deltaTime * (targetAngle == 0 ? uprightSpeed : angleLaySpeed)
+ );
+
+ transform.rotation = Quaternion.Euler(currentAngle, 0, 0);
+ }
+
+ // ----------------------------------------
+ // 外部控制接口
+ // ----------------------------------------
+
+ 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;
+ }
+}
diff --git a/Assets/Scripts/FloatBobberControllerPro.cs.meta b/Assets/Scripts/FloatBobberControllerPro.cs.meta
new file mode 100644
index 000000000..8f5b04fc7
--- /dev/null
+++ b/Assets/Scripts/FloatBobberControllerPro.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 59f0b74408dbbfe44aee75e1ddf784d3
\ No newline at end of file
diff --git a/Assets/Scripts/FloatBobberControllerProV4.cs b/Assets/Scripts/FloatBobberControllerProV4.cs
new file mode 100644
index 000000000..e895c033c
--- /dev/null
+++ b/Assets/Scripts/FloatBobberControllerProV4.cs
@@ -0,0 +1,206 @@
+using UnityEngine;
+
+public enum BobberState
+{
+ LyingDown, // 躺漂
+ RisingUp, // 起漂
+ Standing, // 正常站立
+ Bottom, // 挂底
+ TopLift, // 顶漂
+ DownPull, // 黑漂
+ Nibble // 小顿口
+}
+
+public class FloatBobberControllerProV4 : MonoBehaviour
+{
+ [Header("Environment")]
+ public float waterLevel = 0f;
+
+ [Header("Float Settings")]
+ public float floatLength = 0.20f; // 漂总长度
+ public float buoyancyForce = 0.35f; // 浮力强度
+ public float mass = 0.015f; // 重量
+ public float tiltRecoverySpeed = 3f; // 漂恢复垂直速度
+ public float maxTiltAngle = 60f; // 躺漂最大角度
+
+ [Header("Line Settings")]
+ public float lineTension = 0f; // 动态线张力(由鱼影响)
+ public float bottomDrag = 1.5f; // 铅坠触底时的阻力
+
+ [Header("State Machine")]
+ public BobberState state = BobberState.LyingDown;
+
+ // internal vars
+ private float verticalSpeed = 0f;
+ private float angle = 60f; // 初始躺漂角度
+ private float lastHeight;
+ private float deltaY;
+
+ void Start()
+ {
+ lastHeight = transform.position.y;
+ state = BobberState.LyingDown;
+ }
+
+ void Update()
+ {
+ deltaY = transform.position.y - lastHeight;
+ lastHeight = transform.position.y;
+
+ ApplyPhysics();
+ ProcessStateMachine();
+ ApplyRotation();
+ }
+
+ // ---------------------------------------------------------------------
+ // 一切物理都由状态机控制
+ // ---------------------------------------------------------------------
+ void ProcessStateMachine()
+ {
+ switch (state)
+ {
+ case BobberState.LyingDown:
+ LyingDownBehaviour();
+ break;
+
+ case BobberState.RisingUp:
+ RisingUpBehaviour();
+ break;
+
+ case BobberState.Standing:
+ StandingBehaviour();
+ break;
+
+ case BobberState.Bottom:
+ BottomBehaviour();
+ break;
+
+ case BobberState.TopLift:
+ TopLiftBehaviour();
+ break;
+
+ case BobberState.DownPull:
+ DownPullBehaviour();
+ break;
+
+ case BobberState.Nibble:
+ NibbleBehaviour();
+ break;
+ }
+
+ AutoTransition();
+ }
+
+ // ---------------------------------------------------------------------
+ // 【状态 → 行为】
+ // ---------------------------------------------------------------------
+
+ void LyingDownBehaviour()
+ {
+ // 浮漂处于平躺,几乎无浮力
+ angle = Mathf.Lerp(angle, maxTiltAngle, Time.deltaTime * 1.5f);
+
+ // 水面浮力很小
+ verticalSpeed += (buoyancyForce * 0.1f - mass * 9.8f) * Time.deltaTime;
+ }
+
+ void RisingUpBehaviour()
+ {
+ // 浮漂正在被浮力慢慢扶正
+ angle = Mathf.Lerp(angle, 0, Time.deltaTime * (tiltRecoverySpeed * 0.7f));
+
+ // 浮力略加强
+ verticalSpeed += (buoyancyForce * 0.5f) * Time.deltaTime;
+ }
+
+ void StandingBehaviour()
+ {
+ // 完全立稳
+ angle = Mathf.Lerp(angle, 0, Time.deltaTime * tiltRecoverySpeed);
+
+ // 浮力与重力平衡,小幅波动
+ verticalSpeed += Mathf.Sin(Time.time * 4f) * 0.001f;
+ }
+
+ void BottomBehaviour()
+ {
+ // 铅坠或饵触底 → 浮漂立不起来
+ angle = Mathf.Lerp(angle, 20f, Time.deltaTime * 2f);
+
+ // 上浮被底部拖住
+ verticalSpeed *= 0.6f;
+ }
+
+ void TopLiftBehaviour()
+ {
+ // 顶漂 → 漂被往上托一点
+ verticalSpeed += 0.02f;
+ }
+
+ void DownPullBehaviour()
+ {
+ // 黑漂 → 往下猛拉
+ verticalSpeed -= 0.05f;
+ }
+
+ void NibbleBehaviour()
+ {
+ // 小顿口 → 轻微波动
+ verticalSpeed += Mathf.Sin(Time.time * 20f) * 0.0015f;
+ }
+
+ // ---------------------------------------------------------------------
+ // 状态自动切换
+ // ---------------------------------------------------------------------
+ void AutoTransition()
+ {
+ // 1. 浮漂露出 40% 以上 → 起漂或站漂
+ float headHeight = transform.position.y + floatLength;
+
+ if (state == BobberState.LyingDown && headHeight > waterLevel - 0.02f)
+ state = BobberState.RisingUp;
+
+ if (state == BobberState.RisingUp && angle < 12f)
+ state = BobberState.Standing;
+
+ // 2. 挂底判定
+ float tailHeight = transform.position.y - floatLength;
+ if (tailHeight < waterLevel - 0.03f)
+ state = BobberState.Bottom;
+
+ // 3. 顶漂
+ if (deltaY > 0.015f)
+ state = BobberState.TopLift;
+
+ // 4. 黑漂
+ if (deltaY < -0.02f)
+ state = BobberState.DownPull;
+
+ // 5. 小顿口
+ if (Mathf.Abs(deltaY) > 0.003f && Mathf.Abs(deltaY) < 0.015f)
+ state = BobberState.Nibble;
+ }
+
+ // ---------------------------------------------------------------------
+ // 垂直物理
+ // ---------------------------------------------------------------------
+ void ApplyPhysics()
+ {
+ float submerged = Mathf.Clamp01((waterLevel - transform.position.y) * 8f);
+ float upForce = buoyancyForce * submerged - mass * 9.8f;
+
+ verticalSpeed += upForce * Time.deltaTime;
+ verticalSpeed = Mathf.Clamp(verticalSpeed, -0.2f, 0.2f);
+
+ transform.position += new Vector3(0, verticalSpeed, 0);
+ }
+
+ // ---------------------------------------------------------------------
+ // 倾斜角控制
+ // ---------------------------------------------------------------------
+ void ApplyRotation()
+ {
+ Quaternion q = Quaternion.Euler(angle, 0, 0);
+ transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * 8f);
+ }
+}
diff --git a/Assets/Scripts/FloatBobberControllerProV4.cs.meta b/Assets/Scripts/FloatBobberControllerProV4.cs.meta
new file mode 100644
index 000000000..66be78d20
--- /dev/null
+++ b/Assets/Scripts/FloatBobberControllerProV4.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 725d1d7ecc254c8a980d997ac7a9d5d3
+timeCreated: 1764166393
\ No newline at end of file
diff --git a/Fishing2.sln.DotSettings.user b/Fishing2.sln.DotSettings.user
index ff05a6ef8..48db78f80 100644
--- a/Fishing2.sln.DotSettings.user
+++ b/Fishing2.sln.DotSettings.user
@@ -15,6 +15,7 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
@@ -23,6 +24,7 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded