提交修改

This commit is contained in:
2026-04-06 21:12:32 +08:00
parent 75aa83677f
commit 1389a9ec7f
21 changed files with 1329 additions and 612 deletions

View File

@@ -1,36 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
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: 9d66919bfa33a7445b32a45410e0ccfe, type: 3}
m_Name: FishingRigDefinition
m_EditorClassIdentifier: Assembly-CSharp::FishingRigDefinition
logicalNodes:
- id:
nodeType: 0
distanceFromPrevious: 0
virtualNodeCount: 0
gravityScale: 0
damping: 0
debugColor: {r: 0.17723638, g: 1, b: 0, a: 1}
- id:
nodeType: 2
distanceFromPrevious: 0
virtualNodeCount: 0
gravityScale: 0
damping: 0
debugColor: {r: 0, g: 0.61897755, b: 1, a: 1}
- id:
nodeType: 3
distanceFromPrevious: 0
virtualNodeCount: 0
gravityScale: 0
damping: 0
debugColor: {r: 1, g: 0, b: 0, a: 1}

BIN
Assets/New Terrain.asset Normal file

Binary file not shown.

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a48cb68bf14ac8c429abc96a2cdb7821 guid: 353d79ea0bc081b4c8f234204cbdcf46
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 11400000 mainObjectFileID: 15600000
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

@@ -392,33 +392,17 @@ GameObject:
serializedVersion: 6 serializedVersion: 6
m_Component: m_Component:
- component: {fileID: 445360722} - component: {fileID: 445360722}
- component: {fileID: 445360721} - component: {fileID: 445360726}
- component: {fileID: 445360723}
- component: {fileID: 445360725} - component: {fileID: 445360725}
- component: {fileID: 445360724} - component: {fileID: 445360724}
- component: {fileID: 445360723}
m_Layer: 0 m_Layer: 0
m_Name: FishingLineRoot m_Name: LineRoot
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
m_IsActive: 1 m_IsActive: 1
--- !u!114 &445360721
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 445360720}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5194be581ec15764eab72311e62182eb, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingGameplayController
solver: {fileID: 445360723}
reelSpeed: 0.5
reelInKey: 101
reelOutKey: 113
--- !u!4 &445360722 --- !u!4 &445360722
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -428,69 +412,17 @@ Transform:
m_GameObject: {fileID: 445360720} m_GameObject: {fileID: 445360720}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -0, y: 0, z: -0} m_LocalPosition: {x: -0, y: 1, z: -0}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children:
- {fileID: 1930940348}
- {fileID: 1230107384}
- {fileID: 1508371650}
- {fileID: 663592153}
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &445360723 --- !u!114 &445360723
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 445360720}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b8bc695f40e430b4588f7fb29f79a3e3, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingLineSolver
rigDefinition: {fileID: 11400000, guid: a48cb68bf14ac8c429abc96a2cdb7821, type: 2}
inlineLogicalNodes:
- id: Start
nodeType: 0
distanceFromPrevious: 0
virtualNodeCount: 0
gravityScale: 0
damping: 0
debugColor: {r: 0.4, g: 1, b: 0.6, a: 1}
- id: Float
nodeType: 1
distanceFromPrevious: 1.2
virtualNodeCount: 10
gravityScale: 0.15
damping: 0.08
debugColor: {r: 1, g: 0.75, b: 0.2, a: 1}
- id: Sinker
nodeType: 2
distanceFromPrevious: 1.4
virtualNodeCount: 10
gravityScale: 1.6
damping: 0.02
debugColor: {r: 0.7, g: 0.85, b: 1, a: 1}
- id: Hook
nodeType: 3
distanceFromPrevious: 0.8
virtualNodeCount: 10
gravityScale: 1.2
damping: 0.03
debugColor: {r: 1, g: 0.35, b: 0.35, a: 1}
startAnchor: {fileID: 1538371803}
initialDirection: {x: 0, y: -1, z: 0}
solverIterations: 8
gravity: 9.81
globalDamping: 0.01
tensionSmoothing: 0.18
simulateInFixedUpdate: 0
currentLineLength: 3.4
minLineLength: 1.2
maxLineLength: 8.5
drawDebug: 1
debugNodeRadius: 0.03
virtualNodeColor: {r: 0.4, g: 0.75, b: 1, a: 1}
segmentColor: {r: 0.85, g: 0.9, b: 1, a: 1}
--- !u!114 &445360724
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0} m_CorrespondingSourceObject: {fileID: 0}
@@ -502,10 +434,9 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 0af449a2493cf8c4281c9d5ae01c2a92, type: 3} m_Script: {fileID: 11500000, guid: 0af449a2493cf8c4281c9d5ae01c2a92, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingLineRendererBinder m_EditorClassIdentifier: Assembly-CSharp::FishingLineRendererBinder
solver: {fileID: 445360723} solver: {fileID: 445360725}
lineRenderer: {fileID: 445360725} lineRenderer: {fileID: 445360724}
attachmentBindings: [] --- !u!120 &445360724
--- !u!120 &445360725
LineRenderer: LineRenderer:
serializedVersion: 3 serializedVersion: 3
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -556,7 +487,7 @@ LineRenderer:
m_MaskInteraction: 0 m_MaskInteraction: 0
m_Positions: m_Positions:
- {x: 0, y: 0, z: 0} - {x: 0, y: 0, z: 0}
- {x: 0, y: -0.01, z: 1} - {x: 0, y: 0, z: 1}
m_Parameters: m_Parameters:
serializedVersion: 3 serializedVersion: 3
widthMultiplier: 1 widthMultiplier: 1
@@ -615,6 +546,334 @@ LineRenderer:
m_UseWorldSpace: 1 m_UseWorldSpace: 1
m_Loop: 0 m_Loop: 0
m_ApplyActiveColorSpace: 1 m_ApplyActiveColorSpace: 1
--- !u!114 &445360725
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 445360720}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b8bc695f40e430b4588f7fb29f79a3e3, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingLineSolver
lineRoot: {fileID: 445360722}
startAnchor: {fileID: 1538371803}
solverIterations: 8
gravity: 9.81
globalDamping: 0.01
initialDirection: {x: 0, y: -1, z: 0}
autoUseNodeDistances: 1
manualTotalLength: 3
minManualLength: 0.5
maxManualLength: 15
drawDebug: 1
debugPointRadius: 0.03
virtualPointColor: {r: 0.45, g: 0.75, b: 1, a: 1}
segmentColor: {r: 0.9, g: 0.95, b: 1, a: 1}
--- !u!114 &445360726
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 445360720}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 465a2f7c62ee04948b577ee52382c43e, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingLineTestController
solver: {fileID: 445360725}
reelSpeed: 1.5
reelInKey: 101
reelOutKey: 113
--- !u!1 &528985859
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 528985860}
- component: {fileID: 528985863}
- component: {fileID: 528985862}
- component: {fileID: 528985861}
m_Layer: 0
m_Name: Cube
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &528985860
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 528985859}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.1, y: 0.1, z: 0.1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1230107384}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!65 &528985861
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 528985859}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &528985862
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 528985859}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &528985863
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 528985859}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!1 &663592152
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 663592153}
- component: {fileID: 663592154}
- component: {fileID: 663592155}
m_Layer: 0
m_Name: TerminalNode
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &663592153
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 663592152}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 733128128}
m_Father: {fileID: 445360722}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &663592154
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 663592152}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6e0f6939c01292c4bb2adc26e7d6f24f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingLineNode
nodeId: Terminal
nodeType: 3
distanceFromPrevious: 0
virtualNodeCount: 3
gravityScale: 1
damping: 0.04
alignToLine: 1
localForwardAxis: {x: 0, y: 0, z: 1}
upAxis: {x: 0, y: 1, z: 0}
debugColor: {r: 1, g: 1, b: 1, a: 1}
--- !u!114 &663592155
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 663592152}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 953e78672cb7e2a4ebacb573fa9a7627, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingGroundProbeNodeBehaviour
behaviourEnabled: 1
probeShape: 2
collisionLayers:
serializedVersion: 2
m_Bits: 64
probeRadius: 0.08
capsuleHeight: 0.3
boxHalfExtents: {x: 0.08, y: 0.08, z: 0.08}
castDistance: 0.5
collisionPadding: 0.01
contactDamping: 0.2
contactFriction: 0.85
contactSnapDistance: 0.03
drawDebug: 1
castColor: {r: 1, g: 0.85, b: 0.2, a: 0.9}
hitColor: {r: 0.25, g: 1, b: 0.4, a: 1}
--- !u!1 &733128127
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 733128128}
- component: {fileID: 733128131}
- component: {fileID: 733128130}
m_Layer: 0
m_Name: Cube
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &733128128
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 733128127}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.1, y: 0.1, z: 0.1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 663592153}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!23 &733128130
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 733128127}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &733128131
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 733128127}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!1 &832575517 --- !u!1 &832575517
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -664,6 +923,144 @@ Transform:
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1230107383
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1230107384}
- component: {fileID: 1230107385}
- component: {fileID: 1230107386}
m_Layer: 0
m_Name: FloatNode
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1230107384
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1230107383}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 528985860}
m_Father: {fileID: 445360722}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1230107385
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1230107383}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6e0f6939c01292c4bb2adc26e7d6f24f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingLineNode
nodeId: Float
nodeType: 1
distanceFromPrevious: 0
virtualNodeCount: 3
gravityScale: 1
damping: 0.04
alignToLine: 1
localForwardAxis: {x: 0, y: 0, z: 1}
upAxis: {x: 0, y: 1, z: 0}
debugColor: {r: 1, g: 1, b: 1, a: 1}
--- !u!114 &1230107386
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1230107383}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 953e78672cb7e2a4ebacb573fa9a7627, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingGroundProbeNodeBehaviour
behaviourEnabled: 1
probeShape: 1
collisionLayers:
serializedVersion: 2
m_Bits: 64
probeRadius: 0.08
capsuleHeight: 0.3
boxHalfExtents: {x: 0.08, y: 0.08, z: 0.08}
castDistance: 0.5
collisionPadding: 0.01
contactDamping: 0.2
contactFriction: 0.85
contactSnapDistance: 0.03
drawDebug: 1
castColor: {r: 1, g: 0.85, b: 0.2, a: 0.9}
hitColor: {r: 0.25, g: 1, b: 0.4, a: 1}
--- !u!1 &1508371649
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1508371650}
- component: {fileID: 1508371651}
m_Layer: 0
m_Name: WeightNode
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1508371650
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1508371649}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 445360722}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1508371651
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1508371649}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6e0f6939c01292c4bb2adc26e7d6f24f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingLineNode
nodeId: Weight
nodeType: 2
distanceFromPrevious: 0
virtualNodeCount: 3
gravityScale: 1
damping: 0.04
alignToLine: 1
localForwardAxis: {x: 0, y: 0, z: 1}
upAxis: {x: 0, y: 1, z: 0}
debugColor: {r: 1, g: 1, b: 1, a: 1}
--- !u!1 &1538371799 --- !u!1 &1538371799
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -770,12 +1167,159 @@ Transform:
m_GameObject: {fileID: 1538371799} m_GameObject: {fileID: 1538371799}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 0, y: 1, z: 0}
m_LocalScale: {x: 0.02, y: 0.02, z: 0.02} m_LocalScale: {x: 0.02, y: 0.02, z: 0.02}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1603549249
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1603549252}
- component: {fileID: 1603549251}
- component: {fileID: 1603549250}
m_Layer: 6
m_Name: Terrain
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 2147483647
m_IsActive: 1
--- !u!154 &1603549250
TerrainCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1603549249}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 2
m_TerrainData: {fileID: 15600000, guid: 353d79ea0bc081b4c8f234204cbdcf46, type: 2}
m_EnableTreeColliders: 1
--- !u!218 &1603549251
Terrain:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1603549249}
m_Enabled: 1
serializedVersion: 6
m_TerrainData: {fileID: 15600000, guid: 353d79ea0bc081b4c8f234204cbdcf46, type: 2}
m_TreeDistance: 5000
m_TreeBillboardDistance: 50
m_TreeCrossFadeLength: 5
m_TreeMaximumFullLODCount: 50
m_DetailObjectDistance: 80
m_DetailObjectDensity: 1
m_HeightmapPixelError: 5
m_SplatMapDistance: 1000
m_HeightmapMinimumLODSimplification: 0
m_HeightmapMaximumLOD: 0
m_ShadowCastingMode: 2
m_DrawHeightmap: 1
m_DrawInstanced: 0
m_DrawTreesAndFoliage: 1
m_StaticShadowCaster: 0
m_IgnoreQualitySettings: 0
m_ReflectionProbeUsage: 1
m_MaterialTemplate: {fileID: 2100000, guid: 594ea882c5a793440b60ff72d896021e, type: 2}
m_BakeLightProbesForTrees: 1
m_PreserveTreePrototypeLayers: 0
m_DeringLightProbesForTrees: 1
m_ReceiveGI: 1
m_ScaleInLightmap: 0.0256
m_LightmapParameters: {fileID: 15203, guid: 0000000000000000f000000000000000, type: 0}
m_GroupingID: 0
m_RenderingLayerMask: 1
m_AllowAutoConnect: 1
m_EnableHeightmapRayTracing: 1
m_EnableTreesAndDetailsRayTracing: 0
m_TreeMotionVectorModeOverride: 3
--- !u!4 &1603549252
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1603549249}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -2.46, y: 0, z: -4.86}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1930940347
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1930940348}
- component: {fileID: 1930940349}
m_Layer: 0
m_Name: StartNode
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1930940348
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1930940347}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 445360722}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1930940349
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1930940347}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6e0f6939c01292c4bb2adc26e7d6f24f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FishingLineNode
nodeId: Start
nodeType: 0
distanceFromPrevious: 0
virtualNodeCount: 3
gravityScale: 1
damping: 0.04
alignToLine: 1
localForwardAxis: {x: 0, y: 0, z: 1}
upAxis: {x: 0, y: 1, z: 0}
debugColor: {r: 1, g: 1, b: 1, a: 1}
--- !u!1660057539 &9223372036854775807 --- !u!1660057539 &9223372036854775807
SceneRoots: SceneRoots:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -785,3 +1329,4 @@ SceneRoots:
- {fileID: 832575519} - {fileID: 832575519}
- {fileID: 1538371803} - {fileID: 1538371803}
- {fileID: 445360722} - {fileID: 445360722}
- {fileID: 1603549252}

View File

@@ -0,0 +1,32 @@
using UnityEngine;
public class FishingFloatNodeBehaviour : FishingLineNodeBehaviour
{
[SerializeField] private Transform waterSurface;
[SerializeField] private float fallbackWaterHeight = 0f;
[SerializeField] private float surfaceOffset = 0f;
[SerializeField] [Min(0f)] private float buoyancy = 16f;
[SerializeField] [Range(0f, 1f)] private float waterDrag = 0.22f;
[SerializeField] [Min(0.01f)] private float fullBuoyancyDepth = 0.35f;
public override void Evaluate(ref FishingLineNodeInfluence influence, in FishingLineNodeContext context)
{
if (!BehaviourEnabled)
{
return;
}
float surfaceY = waterSurface != null ? waterSurface.position.y : fallbackWaterHeight;
surfaceY += surfaceOffset;
float depth = surfaceY - context.Position.y;
if (depth <= 0f)
{
return;
}
float normalizedDepth = Mathf.Clamp01(depth / fullBuoyancyDepth);
influence.AdditionalAcceleration += Vector3.up * (buoyancy * normalizedDepth);
influence.ExtraDamping = Mathf.Max(influence.ExtraDamping, waterDrag);
}
}

View File

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

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5194be581ec15764eab72311e62182eb

View File

@@ -0,0 +1,152 @@
using UnityEngine;
public enum FishingGroundProbeShape
{
Sphere,
Capsule,
Box
}
public class FishingGroundProbeNodeBehaviour : FishingLineNodeBehaviour
{
[Header("Probe")]
[SerializeField] private FishingGroundProbeShape probeShape = FishingGroundProbeShape.Sphere;
[SerializeField] private LayerMask collisionLayers = ~0;
[SerializeField] [Min(0.01f)] private float probeRadius = 0.08f;
[SerializeField] [Min(0.01f)] private float capsuleHeight = 0.3f;
[SerializeField] private Vector3 boxHalfExtents = new Vector3(0.08f, 0.08f, 0.08f);
[SerializeField] [Min(0.05f)] private float castDistance = 0.5f;
[SerializeField] [Min(0f)] private float collisionPadding = 0.01f;
[SerializeField] [Range(0f, 1f)] private float contactDamping = 0.2f;
[SerializeField] [Range(0f, 1f)] private float contactFriction = 0.85f;
[SerializeField] [Min(0f)] private float contactSnapDistance = 0.03f;
[Header("Debug")]
[SerializeField] private bool drawDebug = true;
[SerializeField] private Color castColor = new Color(1f, 0.85f, 0.2f, 0.9f);
[SerializeField] private Color hitColor = new Color(0.25f, 1f, 0.4f, 1f);
private bool hadHitLastFrame;
private RaycastHit lastHit;
public override void Evaluate(ref FishingLineNodeInfluence influence, in FishingLineNodeContext context)
{
if (!BehaviourEnabled)
{
return;
}
if (!TryProbe(context.Position, out RaycastHit hit, out float supportOffset))
{
hadHitLastFrame = false;
return;
}
hadHitLastFrame = true;
lastHit = hit;
float minimumY = hit.point.y + supportOffset + collisionPadding;
bool needsSnap = context.Position.y < minimumY || minimumY - context.Position.y <= contactSnapDistance;
if (needsSnap)
{
influence.PositionOffset += Vector3.up * (minimumY - context.Position.y);
Vector3 groundVelocity = Vector3.ProjectOnPlane(context.Velocity, hit.normal) * (1f - contactFriction);
influence.VelocityOffset += groundVelocity - context.Velocity;
influence.ExtraDamping = Mathf.Max(influence.ExtraDamping, contactDamping);
}
}
private bool TryProbe(Vector3 position, out RaycastHit hit, out float supportOffset)
{
Vector3 direction = Vector3.down;
float distance = castDistance * 2f;
switch (probeShape)
{
case FishingGroundProbeShape.Capsule:
{
float halfSegment = Mathf.Max(0f, capsuleHeight * 0.5f - probeRadius);
Vector3 center = position + Vector3.up * castDistance;
Vector3 top = center + Vector3.up * halfSegment;
Vector3 bottom = center - Vector3.up * halfSegment;
supportOffset = probeRadius;
return Physics.CapsuleCast(top, bottom, probeRadius, direction, out hit, distance, collisionLayers, QueryTriggerInteraction.Ignore);
}
case FishingGroundProbeShape.Box:
{
Vector3 center = position + Vector3.up * castDistance;
supportOffset = boxHalfExtents.y;
return Physics.BoxCast(center, boxHalfExtents, direction, out hit, transform.rotation, distance, collisionLayers, QueryTriggerInteraction.Ignore);
}
default:
{
Vector3 origin = position + Vector3.up * castDistance;
supportOffset = probeRadius;
return Physics.SphereCast(origin, probeRadius, direction, out hit, distance, collisionLayers, QueryTriggerInteraction.Ignore);
}
}
}
private void OnDrawGizmos()
{
if (!drawDebug)
{
return;
}
Vector3 start = transform.position + Vector3.up * castDistance;
Vector3 end = start + Vector3.down * (castDistance * 2f);
Gizmos.color = castColor;
switch (probeShape)
{
case FishingGroundProbeShape.Capsule:
DrawCapsuleCastGizmo(start, end);
break;
case FishingGroundProbeShape.Box:
DrawBoxCastGizmo(start, end);
break;
default:
Gizmos.DrawWireSphere(start, probeRadius);
Gizmos.DrawWireSphere(end, probeRadius);
Gizmos.DrawLine(start, end);
break;
}
if (!hadHitLastFrame)
{
return;
}
Gizmos.color = hitColor;
Gizmos.DrawSphere(lastHit.point, 0.03f);
Gizmos.DrawLine(lastHit.point, lastHit.point + lastHit.normal * 0.2f);
}
private void DrawCapsuleCastGizmo(Vector3 start, Vector3 end)
{
float halfSegment = Mathf.Max(0f, capsuleHeight * 0.5f - probeRadius);
Vector3 startTop = start + Vector3.up * halfSegment;
Vector3 startBottom = start - Vector3.up * halfSegment;
Vector3 endTop = end + Vector3.up * halfSegment;
Vector3 endBottom = end - Vector3.up * halfSegment;
Gizmos.DrawWireSphere(startTop, probeRadius);
Gizmos.DrawWireSphere(startBottom, probeRadius);
Gizmos.DrawWireSphere(endTop, probeRadius);
Gizmos.DrawWireSphere(endBottom, probeRadius);
Gizmos.DrawLine(startTop, endTop);
Gizmos.DrawLine(startBottom, endBottom);
}
private void DrawBoxCastGizmo(Vector3 start, Vector3 end)
{
Matrix4x4 previous = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.TRS(start, transform.rotation, Vector3.one);
Gizmos.DrawWireCube(Vector3.zero, boxHalfExtents * 2f);
Gizmos.matrix = Matrix4x4.TRS(end, transform.rotation, Vector3.one);
Gizmos.DrawWireCube(Vector3.zero, boxHalfExtents * 2f);
Gizmos.matrix = previous;
Gizmos.DrawLine(start, end);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 953e78672cb7e2a4ebacb573fa9a7627

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
@@ -11,56 +10,79 @@ public enum FishingLineNodeType
Normal Normal
} }
[Serializable] public class FishingLineNode : MonoBehaviour
public class FishingLineLogicalNodeDefinition
{ {
[SerializeField] private string id = "Node"; [SerializeField] private string nodeId = "Node";
[SerializeField] private FishingLineNodeType nodeType = FishingLineNodeType.Normal; [SerializeField] private FishingLineNodeType nodeType = FishingLineNodeType.Normal;
[SerializeField] [Min(0f)] private float distanceFromPrevious = 1f; [SerializeField] [Min(0f)] private float distanceFromPrevious = 0f;
[SerializeField] [Min(0)] private int virtualNodeCount = 3; [SerializeField] [Min(0)] private int virtualNodeCount = 3;
[SerializeField] [Min(0f)] private float gravityScale = 1f; [SerializeField] [Min(0f)] private float gravityScale = 1f;
[SerializeField] [Range(0f, 1f)] private float damping = 0.04f; [SerializeField] [Range(0f, 1f)] private float damping = 0.04f;
[SerializeField] private Color debugColor = Color.white;
public string Id => id;
public FishingLineNodeType NodeType => nodeType;
public float DistanceFromPrevious => distanceFromPrevious;
public int VirtualNodeCount => virtualNodeCount;
public float GravityScale => gravityScale;
public float Damping => damping;
public Color DebugColor => debugColor;
public void ConfigurePrototype(
string newId,
FishingLineNodeType newNodeType,
float newDistanceFromPrevious,
int newVirtualNodeCount,
float newGravityScale,
float newDamping,
Color newDebugColor)
{
id = newId;
nodeType = newNodeType;
distanceFromPrevious = Mathf.Max(0f, newDistanceFromPrevious);
virtualNodeCount = Mathf.Max(0, newVirtualNodeCount);
gravityScale = Mathf.Max(0f, newGravityScale);
damping = Mathf.Clamp01(newDamping);
debugColor = newDebugColor;
}
}
[Serializable]
public class FishingLineAttachmentBinding
{
[SerializeField] [Min(0)] private int logicalNodeIndex;
[SerializeField] private List<Transform> targets = new List<Transform>();
[SerializeField] private bool alignToLine = true; [SerializeField] private bool alignToLine = true;
[SerializeField] private Vector3 localForwardAxis = Vector3.forward; [SerializeField] private Vector3 localForwardAxis = Vector3.forward;
[SerializeField] private Vector3 upAxis = Vector3.up; [SerializeField] private Vector3 upAxis = Vector3.up;
[SerializeField] private Color debugColor = Color.white;
public int LogicalNodeIndex => logicalNodeIndex; private readonly List<FishingLineNodeBehaviour> behaviours = new List<FishingLineNodeBehaviour>();
public IReadOnlyList<Transform> Targets => targets;
public string NodeId => nodeId;
public FishingLineNodeType NodeType => nodeType;
public int VirtualNodeCount => virtualNodeCount;
public float GravityScale => gravityScale;
public float Damping => damping;
public bool AlignToLine => alignToLine; public bool AlignToLine => alignToLine;
public Vector3 LocalForwardAxis => localForwardAxis; public Vector3 LocalForwardAxis => localForwardAxis;
public Vector3 UpAxis => upAxis; public Vector3 UpAxis => upAxis;
public Color DebugColor => debugColor;
public IReadOnlyList<FishingLineNodeBehaviour> Behaviours => behaviours;
private void Reset()
{
if (string.IsNullOrWhiteSpace(nodeId))
{
nodeId = gameObject.name;
}
RefreshBehaviours();
}
private void OnValidate()
{
if (string.IsNullOrWhiteSpace(nodeId))
{
nodeId = gameObject.name;
}
}
[ContextMenu("Refresh Node Behaviours")]
public void RefreshBehaviours()
{
behaviours.Clear();
FishingLineNodeBehaviour[] found = GetComponents<FishingLineNodeBehaviour>();
for (int index = 0; index < found.Length; index++)
{
FishingLineNodeBehaviour behaviour = found[index];
if (behaviour == null)
{
continue;
}
behaviours.Add(behaviour);
}
}
public float ResolveDistanceFromPrevious(FishingLineNode previousNode)
{
if (distanceFromPrevious > 0f)
{
return distanceFromPrevious;
}
if (previousNode == null)
{
return 0f;
}
return Vector3.Distance(previousNode.transform.position, transform.position);
}
} }

View File

@@ -0,0 +1,30 @@
using UnityEngine;
public struct FishingLineNodeContext
{
public FishingLineSolver Solver;
public FishingLineNode Node;
public Vector3 Position;
public Vector3 PreviousPosition;
public Vector3 Velocity;
public Vector3 Tangent;
public float DeltaTime;
public float Time;
}
public struct FishingLineNodeInfluence
{
public Vector3 PositionOffset;
public Vector3 VelocityOffset;
public Vector3 AdditionalAcceleration;
public float ExtraDamping;
}
public abstract class FishingLineNodeBehaviour : MonoBehaviour
{
[SerializeField] private bool behaviourEnabled = true;
public bool BehaviourEnabled => behaviourEnabled;
public abstract void Evaluate(ref FishingLineNodeInfluence influence, in FishingLineNodeContext context);
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 17d8d651410bae146843019f74bc987b

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using UnityEngine; using UnityEngine;
[RequireComponent(typeof(LineRenderer))] [RequireComponent(typeof(LineRenderer))]
@@ -6,74 +5,26 @@ public class FishingLineRendererBinder : MonoBehaviour
{ {
[SerializeField] private FishingLineSolver solver; [SerializeField] private FishingLineSolver solver;
[SerializeField] private LineRenderer lineRenderer; [SerializeField] private LineRenderer lineRenderer;
[SerializeField] private List<FishingLineAttachmentBinding> attachmentBindings = new List<FishingLineAttachmentBinding>();
private void Reset() private void Reset()
{ {
lineRenderer = GetComponent<LineRenderer>();
solver = GetComponent<FishingLineSolver>(); solver = GetComponent<FishingLineSolver>();
lineRenderer = GetComponent<LineRenderer>();
} }
private void LateUpdate() private void LateUpdate()
{ {
if (solver == null) if (solver == null || lineRenderer == null)
{ {
return; return;
} }
UpdateLineRenderer(); var positions = solver.LinePositions;
UpdateAttachments();
}
private void UpdateLineRenderer()
{
if (lineRenderer == null)
{
return;
}
IReadOnlyList<Vector3> positions = solver.LinePositions;
lineRenderer.useWorldSpace = true; lineRenderer.useWorldSpace = true;
lineRenderer.positionCount = positions.Count; lineRenderer.positionCount = positions.Count;
for (int index = 0; index < positions.Count; index++) for (int index = 0; index < positions.Count; index++)
{ {
lineRenderer.SetPosition(index, positions[index]); lineRenderer.SetPosition(index, positions[index]);
} }
} }
private void UpdateAttachments()
{
for (int bindingIndex = 0; bindingIndex < attachmentBindings.Count; bindingIndex++)
{
FishingLineAttachmentBinding binding = attachmentBindings[bindingIndex];
if (!solver.TryGetLogicalNodePosition(binding.LogicalNodeIndex, out Vector3 position))
{
continue;
}
Quaternion rotation = Quaternion.identity;
if (binding.AlignToLine && solver.TryGetLogicalNodeTangent(binding.LogicalNodeIndex, out Vector3 tangent))
{
rotation = Quaternion.LookRotation(tangent, binding.UpAxis.sqrMagnitude > 0f ? binding.UpAxis.normalized : Vector3.up);
}
IReadOnlyList<Transform> targets = binding.Targets;
for (int targetIndex = 0; targetIndex < targets.Count; targetIndex++)
{
Transform target = targets[targetIndex];
if (target == null)
{
continue;
}
target.position = position;
if (binding.AlignToLine)
{
Quaternion axisOffset = Quaternion.FromToRotation(binding.LocalForwardAxis.normalized, Vector3.forward);
target.rotation = rotation * Quaternion.Inverse(axisOffset);
}
}
}
}
} }

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
@@ -6,139 +5,106 @@ public class FishingLineSolver : MonoBehaviour
{ {
private const float MinSegmentLength = 0.01f; private const float MinSegmentLength = 0.01f;
[Header("Rig")] [Header("Scene Nodes")]
[SerializeField] private FishingRigDefinition rigDefinition; [SerializeField] private Transform lineRoot;
[SerializeField] private List<FishingLineLogicalNodeDefinition> inlineLogicalNodes = new List<FishingLineLogicalNodeDefinition>();
[SerializeField] private Transform startAnchor; [SerializeField] private Transform startAnchor;
[SerializeField] private Vector3 initialDirection = Vector3.down;
[Header("Simulation")] [Header("Simulation")]
[SerializeField] [Min(1)] private int solverIterations = 8; [SerializeField] [Min(1)] private int solverIterations = 8;
[SerializeField] [Min(0f)] private float gravity = 9.81f; [SerializeField] [Min(0f)] private float gravity = 9.81f;
[SerializeField] [Range(0f, 1f)] private float globalDamping = 0.01f; [SerializeField] [Range(0f, 1f)] private float globalDamping = 0.01f;
[SerializeField] [Range(0f, 1f)] private float tensionSmoothing = 0.18f; [SerializeField] private Vector3 initialDirection = Vector3.down;
[SerializeField] private bool simulateInFixedUpdate = false; [SerializeField] [Min(0.05f)] private float maxSolverSegmentLength = 0.25f;
[Header("Line Length")] [Header("Start Segment")]
[SerializeField] [Min(0.01f)] private float currentLineLength = 3.4f; [SerializeField] private bool useSceneDistanceForStartSegment = true;
[SerializeField] [Min(0.01f)] private float minLineLength = 1.2f; [SerializeField] [Min(0.1f)] private float startSegmentLength = 3f;
[SerializeField] [Min(0.01f)] private float maxLineLength = 8.5f; [SerializeField] [Min(0.1f)] private float minStartSegmentLength = 0.5f;
[SerializeField] [Min(0.1f)] private float maxStartSegmentLength = 15f;
[Header("Debug")] [Header("Debug")]
[SerializeField] private bool drawDebug = true; [SerializeField] private bool drawDebug = true;
[SerializeField] [Min(0.005f)] private float debugNodeRadius = 0.03f; [SerializeField] [Min(0.005f)] private float debugPointRadius = 0.03f;
[SerializeField] private Color virtualNodeColor = new Color(0.4f, 0.75f, 1f, 1f); [SerializeField] private Color virtualPointColor = new Color(0.45f, 0.75f, 1f, 1f);
[SerializeField] private Color segmentColor = new Color(0.85f, 0.9f, 1f, 1f); [SerializeField] private Color segmentColor = new Color(0.9f, 0.95f, 1f, 1f);
private readonly List<FishingLineNode> logicalNodes = new List<FishingLineNode>();
private readonly List<RuntimePoint> points = new List<RuntimePoint>(); private readonly List<RuntimePoint> points = new List<RuntimePoint>();
private readonly List<RuntimeLogicalNode> logicalNodes = new List<RuntimeLogicalNode>();
private readonly List<float> baseSegmentLengths = new List<float>(); private readonly List<float> baseSegmentLengths = new List<float>();
private readonly List<Vector3> linePositions = new List<Vector3>(); private readonly List<Vector3> linePositions = new List<Vector3>();
private readonly List<FishingLineLogicalNodeDefinition> fallbackLogicalNodes = new List<FishingLineLogicalNodeDefinition>(); private readonly List<int> logicalPointIndices = new List<int>();
private bool runtimeBuilt; private bool runtimeBuilt;
private float currentTensionNormalized; private float fixedRigLength;
private float previousLineLength; private int startSegmentSubsegments;
private float baseTotalLength;
public IReadOnlyList<Vector3> LinePositions => linePositions; public IReadOnlyList<Vector3> LinePositions => linePositions;
public int PointCount => points.Count; public IReadOnlyList<FishingLineNode> LogicalNodes => logicalNodes;
public int LogicalNodeCount => logicalNodes.Count; public float FixedRigLength => fixedRigLength;
public float CurrentTensionNormalized => currentTensionNormalized; public float StartSegmentLength => startSegmentLength;
public float CurrentLineLength => currentLineLength; public float TotalLineLength => fixedRigLength + startSegmentLength;
public float BaseTotalLength => baseTotalLength;
public IReadOnlyList<FishingLineLogicalNodeDefinition> LogicalNodeDefinitions => GetActiveDefinitions(); private void Reset()
{
lineRoot = transform;
startAnchor = transform;
}
private void OnEnable() private void OnEnable()
{ {
Rebuild(); Rebuild();
} }
private void Reset()
{
EnsureInlinePrototypeIfNeeded();
}
private void OnValidate() private void OnValidate()
{ {
solverIterations = Mathf.Max(1, solverIterations); solverIterations = Mathf.Max(1, solverIterations);
minLineLength = Mathf.Max(MinSegmentLength, minLineLength); minStartSegmentLength = Mathf.Max(0.1f, minStartSegmentLength);
maxLineLength = Mathf.Max(minLineLength, maxLineLength); maxStartSegmentLength = Mathf.Max(minStartSegmentLength, maxStartSegmentLength);
currentLineLength = Mathf.Clamp(currentLineLength, minLineLength, maxLineLength); startSegmentLength = Mathf.Clamp(startSegmentLength, minStartSegmentLength, maxStartSegmentLength);
} }
private void Update() private void Update()
{ {
if (!simulateInFixedUpdate) Simulate(Time.deltaTime);
{
Simulate(Time.deltaTime);
}
}
private void FixedUpdate()
{
if (simulateInFixedUpdate)
{
Simulate(Time.fixedDeltaTime);
}
} }
[ContextMenu("Rebuild Solver")] [ContextMenu("Rebuild Solver")]
public void Rebuild() public void Rebuild()
{ {
EnsureInlinePrototypeIfNeeded(); CollectLogicalNodes();
BuildRuntime(); BuildRuntime();
SnapToAnchors(); SnapAnchors();
RefreshLinePositions(); RefreshLinePositions();
previousLineLength = currentLineLength; SyncLogicalNodeTransforms();
} }
public void SetLineLength(float newLength) public void SetStartSegmentLength(float newLength)
{ {
float clampedLength = Mathf.Clamp(newLength, minLineLength, maxLineLength); useSceneDistanceForStartSegment = false;
bool expanded = clampedLength > currentLineLength + Mathf.Epsilon; startSegmentLength = Mathf.Clamp(newLength, minStartSegmentLength, maxStartSegmentLength);
currentLineLength = clampedLength; }
if (expanded) public void AdjustStartSegmentLength(float delta)
{
SetStartSegmentLength(startSegmentLength + delta);
}
public bool TryGetLogicalNodePosition(string nodeId, out Vector3 position)
{
for (int index = 0; index < logicalNodes.Count; index++)
{ {
ExpandLineToCurrentLength(); FishingLineNode node = logicalNodes[index];
RefreshLinePositions(); if (node == null || !string.Equals(node.NodeId, nodeId, System.StringComparison.OrdinalIgnoreCase))
{
continue;
}
position = points[GetLogicalPointIndex(index)].Position;
return true;
} }
previousLineLength = currentLineLength; position = default;
} return false;
public void AdjustLineLength(float delta)
{
SetLineLength(currentLineLength + delta);
}
public bool TryGetLogicalNodePosition(int logicalNodeIndex, out Vector3 position)
{
if (logicalNodeIndex < 0 || logicalNodeIndex >= logicalNodes.Count)
{
position = default;
return false;
}
position = logicalNodes[logicalNodeIndex].Point.Position;
return true;
}
public bool TryGetLogicalNodeTangent(int logicalNodeIndex, out Vector3 tangent)
{
if (logicalNodeIndex < 0 || logicalNodeIndex >= logicalNodes.Count)
{
tangent = Vector3.forward;
return false;
}
int pointIndex = logicalNodes[logicalNodeIndex].PointIndex;
Vector3 previous = pointIndex > 0 ? points[pointIndex - 1].Position : points[pointIndex].Position;
Vector3 next = pointIndex < points.Count - 1 ? points[pointIndex + 1].Position : points[pointIndex].Position;
tangent = (next - previous).normalized;
return tangent.sqrMagnitude > 0f;
} }
private void Simulate(float deltaTime) private void Simulate(float deltaTime)
@@ -148,247 +114,166 @@ public class FishingLineSolver : MonoBehaviour
Rebuild(); Rebuild();
} }
if (points.Count == 0 || deltaTime <= 0f) if (!runtimeBuilt || deltaTime <= 0f)
{ {
return; return;
} }
SyncAnchors(); EnsureRuntimeTopologyMatchesLength();
if (currentLineLength > previousLineLength + Mathf.Epsilon) SyncStartAnchor();
{
ExpandLineToCurrentLength();
}
Integrate(deltaTime); Integrate(deltaTime);
ApplyNodeBehaviours(deltaTime);
SolveConstraints(); SolveConstraints();
SyncAnchors(); ApplyNodeBehaviours(0f);
SolveConstraints();
SyncStartAnchor();
RefreshLinePositions(); RefreshLinePositions();
UpdateTension(); SyncLogicalNodeTransforms();
previousLineLength = currentLineLength; }
private void CollectLogicalNodes()
{
logicalNodes.Clear();
Transform root = lineRoot != null ? lineRoot : transform;
for (int childIndex = 0; childIndex < root.childCount; childIndex++)
{
Transform child = root.GetChild(childIndex);
if (child == null)
{
continue;
}
FishingLineNode node = child.GetComponent<FishingLineNode>();
if (node == null)
{
continue;
}
node.RefreshBehaviours();
logicalNodes.Add(node);
}
} }
private void BuildRuntime() private void BuildRuntime()
{ {
List<Vector3> previousPolyline = linePositions.Count > 1 ? new List<Vector3>(linePositions) : null;
points.Clear(); points.Clear();
logicalNodes.Clear();
baseSegmentLengths.Clear(); baseSegmentLengths.Clear();
linePositions.Clear(); linePositions.Clear();
baseTotalLength = 0f; logicalPointIndices.Clear();
fixedRigLength = 0f;
startSegmentSubsegments = 0;
runtimeBuilt = false;
EnsureInlinePrototypeIfNeeded(); if (logicalNodes.Count < 1)
IReadOnlyList<FishingLineLogicalNodeDefinition> definitions = GetResolvedDefinitions();
if (definitions.Count == 0)
{ {
runtimeBuilt = false;
return; return;
} }
if (definitions.Count < 2) Vector3 anchor = GetStartAnchorPosition();
if (useSceneDistanceForStartSegment)
{ {
Debug.LogWarning("FishingLineSolver needs at least 2 logical nodes to build a valid line.", this); startSegmentLength = Mathf.Clamp(
runtimeBuilt = false; Mathf.Max(MinSegmentLength, Vector3.Distance(anchor, logicalNodes[0].transform.position)),
return; minStartSegmentLength,
maxStartSegmentLength);
}
else
{
startSegmentLength = Mathf.Clamp(startSegmentLength, minStartSegmentLength, maxStartSegmentLength);
} }
if (definitions[0].NodeType != FishingLineNodeType.Start) RuntimePoint anchorPoint = new RuntimePoint(anchor, true, -1, logicalNodes[0].GravityScale, logicalNodes[0].Damping);
points.Add(anchorPoint);
int currentPointIndex = 1;
FishingLineNode firstNode = logicalNodes[0];
startSegmentSubsegments = Mathf.Max(1, Mathf.Max(firstNode.VirtualNodeCount + 1, Mathf.CeilToInt(startSegmentLength / maxSolverSegmentLength)));
float startSubsegmentLength = startSegmentLength / startSegmentSubsegments;
for (int virtualIndex = 1; virtualIndex < startSegmentSubsegments; virtualIndex++)
{ {
Debug.LogWarning("FishingLineSolver expects the first logical node to be Start. The first node is still treated as the anchor.", this); RuntimePoint virtualPoint = new RuntimePoint(
anchor,
false,
-1,
Mathf.Lerp(0f, firstNode.GravityScale, virtualIndex / (float)startSegmentSubsegments),
Mathf.Lerp(0f, firstNode.Damping, virtualIndex / (float)startSegmentSubsegments));
points.Add(virtualPoint);
baseSegmentLengths.Add(startSubsegmentLength);
currentPointIndex++;
} }
Vector3 cursor = startAnchor != null ? startAnchor.position : transform.position; RuntimePoint firstLogicalPoint = new RuntimePoint(anchor, false, 0, firstNode.GravityScale, firstNode.Damping);
Vector3 direction = initialDirection.sqrMagnitude > 0f ? initialDirection.normalized : Vector3.down; points.Add(firstLogicalPoint);
baseSegmentLengths.Add(startSubsegmentLength);
logicalPointIndices.Add(currentPointIndex);
currentPointIndex++;
for (int logicalIndex = 0; logicalIndex < definitions.Count; logicalIndex++) for (int logicalIndex = 1; logicalIndex < logicalNodes.Count; logicalIndex++)
{ {
FishingLineLogicalNodeDefinition definition = definitions[logicalIndex]; FishingLineNode node = logicalNodes[logicalIndex];
bool anchored = logicalIndex == 0; FishingLineNode previousNode = logicalNodes[logicalIndex - 1];
float baseDistance = Mathf.Max(MinSegmentLength, node.ResolveDistanceFromPrevious(previousNode));
fixedRigLength += baseDistance;
if (logicalIndex > 0) int requiredSubsegments = Mathf.Max(1, node.VirtualNodeCount + 1);
float baseSubsegmentLength = baseDistance / requiredSubsegments;
for (int virtualIndex = 1; virtualIndex < requiredSubsegments; virtualIndex++)
{ {
FishingLineLogicalNodeDefinition previousDefinition = definitions[logicalIndex - 1]; RuntimePoint virtualPoint = new RuntimePoint(
float logicalDistance = Mathf.Max(MinSegmentLength, definition.DistanceFromPrevious); anchor,
int virtualNodeCount = Mathf.Max(0, definition.VirtualNodeCount); false,
float subSegmentLength = logicalDistance / (virtualNodeCount + 1); -1,
Mathf.Lerp(previousNode.GravityScale, node.GravityScale, virtualIndex / (float)requiredSubsegments),
Mathf.Lerp(previousNode.Damping, node.Damping, virtualIndex / (float)requiredSubsegments));
for (int virtualIndex = 0; virtualIndex < virtualNodeCount; virtualIndex++) points.Add(virtualPoint);
{ baseSegmentLengths.Add(baseSubsegmentLength);
cursor += direction * subSegmentLength; currentPointIndex++;
RuntimePoint virtualPoint = new RuntimePoint(
cursor,
false,
-1,
Mathf.Lerp(previousDefinition.GravityScale, definition.GravityScale, (virtualIndex + 1f) / (virtualNodeCount + 1f)),
Mathf.Lerp(previousDefinition.Damping, definition.Damping, (virtualIndex + 1f) / (virtualNodeCount + 1f)));
points.Add(virtualPoint);
baseSegmentLengths.Add(subSegmentLength);
baseTotalLength += subSegmentLength;
linePositions.Add(cursor);
}
cursor += direction * subSegmentLength;
baseSegmentLengths.Add(subSegmentLength);
baseTotalLength += subSegmentLength;
}
RuntimePoint logicalPoint = new RuntimePoint(cursor, anchored, logicalIndex, definition.GravityScale, definition.Damping);
if (anchored)
{
logicalPoint.Anchor = startAnchor;
} }
RuntimePoint logicalPoint = new RuntimePoint(anchor, false, logicalIndex, node.GravityScale, node.Damping);
points.Add(logicalPoint); points.Add(logicalPoint);
logicalNodes.Add(new RuntimeLogicalNode(logicalIndex, points.Count - 1, logicalPoint)); baseSegmentLengths.Add(baseSubsegmentLength);
linePositions.Add(cursor); logicalPointIndices.Add(currentPointIndex);
currentPointIndex++;
} }
InitializeLineLengthDefaults(); InitializeRuntimePointPositions(previousPolyline);
runtimeBuilt = points.Count > 0; runtimeBuilt = true;
} }
private void InitializeLineLengthDefaults() private void SnapAnchors()
{ {
if (baseTotalLength <= MinSegmentLength) if (points.Count == 0)
{
minLineLength = Mathf.Max(MinSegmentLength, minLineLength);
maxLineLength = Mathf.Max(minLineLength, maxLineLength);
currentLineLength = Mathf.Clamp(Mathf.Max(currentLineLength, minLineLength), minLineLength, maxLineLength);
return;
}
if (currentLineLength <= MinSegmentLength)
{
currentLineLength = baseTotalLength;
}
if (minLineLength <= MinSegmentLength)
{
minLineLength = Mathf.Max(MinSegmentLength, baseTotalLength * 0.35f);
}
if (maxLineLength <= MinSegmentLength || maxLineLength < currentLineLength)
{
maxLineLength = Mathf.Max(currentLineLength, baseTotalLength * 2.5f);
}
minLineLength = Mathf.Min(minLineLength, maxLineLength);
currentLineLength = Mathf.Clamp(currentLineLength, minLineLength, maxLineLength);
}
private void EnsureInlinePrototypeIfNeeded()
{
if (rigDefinition != null && IsDefinitionListUsable(rigDefinition.LogicalNodes))
{ {
return; return;
} }
if (IsDefinitionListUsable(inlineLogicalNodes)) RuntimePoint startPoint = points[0];
Vector3 anchorPosition = GetStartAnchorPosition();
startPoint.Position = anchorPosition;
startPoint.PreviousPosition = anchorPosition;
}
private void SyncStartAnchor()
{
if (points.Count == 0)
{ {
return; return;
} }
inlineLogicalNodes.Clear(); Vector3 anchorPosition = GetStartAnchorPosition();
PopulatePrototypeNodes(inlineLogicalNodes); points[0].Position = anchorPosition;
} points[0].PreviousPosition = anchorPosition;
private IReadOnlyList<FishingLineLogicalNodeDefinition> GetResolvedDefinitions()
{
if (rigDefinition != null && IsDefinitionListUsable(rigDefinition.LogicalNodes))
{
return rigDefinition.LogicalNodes;
}
if (IsDefinitionListUsable(inlineLogicalNodes))
{
return inlineLogicalNodes;
}
fallbackLogicalNodes.Clear();
PopulatePrototypeNodes(fallbackLogicalNodes);
return fallbackLogicalNodes;
}
private static bool IsDefinitionListUsable(IReadOnlyList<FishingLineLogicalNodeDefinition> definitions)
{
if (definitions == null || definitions.Count < 2)
{
return false;
}
if (definitions[0] == null || definitions[0].NodeType != FishingLineNodeType.Start)
{
return false;
}
float totalDistance = 0f;
for (int index = 1; index < definitions.Count; index++)
{
FishingLineLogicalNodeDefinition definition = definitions[index];
if (definition == null)
{
return false;
}
totalDistance += definition.DistanceFromPrevious;
}
return totalDistance > MinSegmentLength;
}
private static void PopulatePrototypeNodes(ICollection<FishingLineLogicalNodeDefinition> target)
{
FishingLineLogicalNodeDefinition startNode = new FishingLineLogicalNodeDefinition();
startNode.ConfigurePrototype("Start", FishingLineNodeType.Start, 0f, 0, 0f, 0f, new Color(0.4f, 1f, 0.6f, 1f));
FishingLineLogicalNodeDefinition floatNode = new FishingLineLogicalNodeDefinition();
floatNode.ConfigurePrototype("Float", FishingLineNodeType.Float, 1.2f, 5, 0.15f, 0.08f, new Color(1f, 0.75f, 0.2f, 1f));
FishingLineLogicalNodeDefinition weightNode = new FishingLineLogicalNodeDefinition();
weightNode.ConfigurePrototype("Weight", FishingLineNodeType.Weight, 1.4f, 4, 1.6f, 0.02f, new Color(0.7f, 0.85f, 1f, 1f));
FishingLineLogicalNodeDefinition terminalNode = new FishingLineLogicalNodeDefinition();
terminalNode.ConfigurePrototype("Terminal", FishingLineNodeType.Terminal, 0.8f, 2, 1.2f, 0.03f, new Color(1f, 0.35f, 0.35f, 1f));
target.Add(startNode);
target.Add(floatNode);
target.Add(weightNode);
target.Add(terminalNode);
}
private void SnapToAnchors()
{
for (int index = 0; index < points.Count; index++)
{
RuntimePoint point = points[index];
if (!point.IsAnchored)
{
continue;
}
Vector3 anchorPosition = point.Anchor != null ? point.Anchor.position : transform.position;
point.Position = anchorPosition;
point.PreviousPosition = anchorPosition;
}
}
private void SyncAnchors()
{
for (int index = 0; index < points.Count; index++)
{
RuntimePoint point = points[index];
if (!point.IsAnchored)
{
continue;
}
Vector3 anchorPosition = point.Anchor != null ? point.Anchor.position : transform.position;
point.Position = anchorPosition;
}
} }
private void Integrate(float deltaTime) private void Integrate(float deltaTime)
{ {
float deltaTimeSqr = deltaTime * deltaTime; float deltaTimeSquared = deltaTime * deltaTime;
for (int index = 0; index < points.Count; index++) for (int index = 0; index < points.Count; index++)
{ {
@@ -401,13 +286,65 @@ public class FishingLineSolver : MonoBehaviour
Vector3 velocity = point.Position - point.PreviousPosition; Vector3 velocity = point.Position - point.PreviousPosition;
float dampingFactor = Mathf.Clamp01(1f - globalDamping - point.Damping); float dampingFactor = Mathf.Clamp01(1f - globalDamping - point.Damping);
Vector3 nextPosition = point.Position + velocity * dampingFactor + Vector3.down * (gravity * point.GravityScale * deltaTimeSqr); Vector3 nextPosition = point.Position + velocity * dampingFactor + Vector3.down * (gravity * point.GravityScale * deltaTimeSquared);
point.PreviousPosition = point.Position; point.PreviousPosition = point.Position;
point.Position = nextPosition; point.Position = nextPosition;
} }
} }
private void ApplyNodeBehaviours(float deltaTime)
{
for (int logicalIndex = 0; logicalIndex < logicalNodes.Count; logicalIndex++)
{
FishingLineNode node = logicalNodes[logicalIndex];
RuntimePoint point = points[GetLogicalPointIndex(logicalIndex)];
if (point.IsAnchored)
{
continue;
}
Vector3 tangent = GetLogicalNodeTangent(logicalIndex);
Vector3 velocity = point.Position - point.PreviousPosition;
FishingLineNodeContext context = new FishingLineNodeContext
{
Solver = this,
Node = node,
Position = point.Position,
PreviousPosition = point.PreviousPosition,
Velocity = velocity,
Tangent = tangent,
DeltaTime = deltaTime,
Time = Time.time
};
FishingLineNodeInfluence influence = default;
IReadOnlyList<FishingLineNodeBehaviour> behaviours = node.Behaviours;
for (int behaviourIndex = 0; behaviourIndex < behaviours.Count; behaviourIndex++)
{
FishingLineNodeBehaviour behaviour = behaviours[behaviourIndex];
if (behaviour == null || !behaviour.BehaviourEnabled)
{
continue;
}
behaviour.Evaluate(ref influence, context);
}
ApplyInfluence(point, influence, deltaTime);
}
}
private static void ApplyInfluence(RuntimePoint point, FishingLineNodeInfluence influence, float deltaTime)
{
Vector3 velocity = point.Position - point.PreviousPosition + influence.VelocityOffset;
velocity *= Mathf.Clamp01(1f - influence.ExtraDamping);
point.PreviousPosition = point.Position - velocity;
point.Position += influence.PositionOffset + influence.AdditionalAcceleration * (deltaTime * deltaTime);
}
private void SolveConstraints() private void SolveConstraints()
{ {
for (int iteration = 0; iteration < solverIterations; iteration++) for (int iteration = 0; iteration < solverIterations; iteration++)
@@ -419,19 +356,13 @@ public class FishingLineSolver : MonoBehaviour
Vector3 delta = pointB.Position - pointA.Position; Vector3 delta = pointB.Position - pointA.Position;
float distance = delta.magnitude; float distance = delta.magnitude;
float restLength = GetTargetSegmentLength(segmentIndex); float restLength = GetSegmentTargetLength(segmentIndex);
if (distance <= restLength || distance <= Mathf.Epsilon) if (distance <= restLength || distance <= Mathf.Epsilon)
{ {
continue; continue;
} }
Vector3 correction = delta * ((distance - restLength) / distance); Vector3 correction = delta * ((distance - restLength) / distance);
if (pointA.IsAnchored && pointB.IsAnchored)
{
continue;
}
if (pointA.IsAnchored) if (pointA.IsAnchored)
{ {
pointB.Position -= correction; pointB.Position -= correction;
@@ -444,13 +375,28 @@ public class FishingLineSolver : MonoBehaviour
continue; continue;
} }
Vector3 halfCorrection = correction * 0.5f; Vector3 half = correction * 0.5f;
pointA.Position += halfCorrection; pointA.Position += half;
pointB.Position -= halfCorrection; pointB.Position -= half;
} }
} }
} }
private float GetSegmentTargetLength(int segmentIndex)
{
if (segmentIndex < 0 || segmentIndex >= baseSegmentLengths.Count)
{
return MinSegmentLength;
}
if (segmentIndex < startSegmentSubsegments)
{
return Mathf.Max(MinSegmentLength, startSegmentLength / Mathf.Max(1, startSegmentSubsegments));
}
return Mathf.Max(MinSegmentLength, baseSegmentLengths[segmentIndex]);
}
private void RefreshLinePositions() private void RefreshLinePositions()
{ {
linePositions.Clear(); linePositions.Clear();
@@ -460,70 +406,62 @@ public class FishingLineSolver : MonoBehaviour
} }
} }
private void ExpandLineToCurrentLength() private void SyncLogicalNodeTransforms()
{ {
if (points.Count < 2 || baseSegmentLengths.Count == 0) for (int logicalIndex = 0; logicalIndex < logicalNodes.Count; logicalIndex++)
{ {
return; FishingLineNode node = logicalNodes[logicalIndex];
} RuntimePoint point = points[GetLogicalPointIndex(logicalIndex)];
node.transform.position = point.Position;
for (int segmentIndex = 0; segmentIndex < baseSegmentLengths.Count; segmentIndex++) if (!node.AlignToLine)
{
RuntimePoint pointA = points[segmentIndex];
RuntimePoint pointB = points[segmentIndex + 1];
float targetLength = GetTargetSegmentLength(segmentIndex);
Vector3 delta = pointB.Position - pointA.Position;
Vector3 direction = delta.sqrMagnitude > Mathf.Epsilon
? delta.normalized
: (initialDirection.sqrMagnitude > 0f ? initialDirection.normalized : Vector3.down);
Vector3 targetPosition = pointA.Position + direction * targetLength;
if (pointB.IsAnchored)
{ {
continue; continue;
} }
Vector3 offset = targetPosition - pointB.Position; Vector3 tangent = GetLogicalNodeTangent(logicalIndex);
pointB.Position += offset; if (tangent.sqrMagnitude <= Mathf.Epsilon)
pointB.PreviousPosition += offset; {
continue;
}
Quaternion lineRotation = Quaternion.LookRotation(tangent.normalized, node.UpAxis.sqrMagnitude > 0f ? node.UpAxis.normalized : Vector3.up);
Quaternion axisOffset = Quaternion.FromToRotation(node.LocalForwardAxis.normalized, Vector3.forward);
node.transform.rotation = lineRotation * Quaternion.Inverse(axisOffset);
} }
} }
private float GetTargetSegmentLength(int segmentIndex) private Vector3 GetStartAnchorPosition()
{ {
if (segmentIndex < 0 || segmentIndex >= baseSegmentLengths.Count || baseTotalLength <= MinSegmentLength) if (startAnchor != null)
{ {
return MinSegmentLength; return startAnchor.position;
} }
float normalizedLength = baseSegmentLengths[segmentIndex] / baseTotalLength; if (logicalNodes.Count > 0 && logicalNodes[0] != null)
return Mathf.Max(MinSegmentLength, currentLineLength * normalizedLength); {
return logicalNodes[0].transform.position;
}
return transform.position;
} }
private void UpdateTension() private int GetLogicalPointIndex(int logicalNodeIndex)
{ {
if (baseSegmentLengths.Count == 0) if (logicalNodeIndex < 0 || logicalNodeIndex >= logicalPointIndices.Count)
{ {
currentTensionNormalized = 0f; return Mathf.Clamp(startSegmentSubsegments, 0, points.Count - 1);
return;
} }
float maxStretch = 0f; return logicalPointIndices[logicalNodeIndex];
for (int segmentIndex = 0; segmentIndex < baseSegmentLengths.Count; segmentIndex++)
{
float currentLength = Vector3.Distance(points[segmentIndex].Position, points[segmentIndex + 1].Position);
float restLength = GetTargetSegmentLength(segmentIndex);
maxStretch = Mathf.Max(maxStretch, Mathf.Clamp01((currentLength - restLength) / restLength));
}
currentTensionNormalized = Mathf.Lerp(currentTensionNormalized, maxStretch, tensionSmoothing);
} }
private IReadOnlyList<FishingLineLogicalNodeDefinition> GetActiveDefinitions() private Vector3 GetLogicalNodeTangent(int logicalNodeIndex)
{ {
return GetResolvedDefinitions(); int pointIndex = GetLogicalPointIndex(logicalNodeIndex);
Vector3 previous = pointIndex > 0 ? points[pointIndex - 1].Position : points[pointIndex].Position;
Vector3 next = pointIndex < points.Count - 1 ? points[pointIndex + 1].Position : points[pointIndex].Position;
return next - previous;
} }
private void OnDrawGizmos() private void OnDrawGizmos()
@@ -533,54 +471,133 @@ public class FishingLineSolver : MonoBehaviour
return; return;
} }
IReadOnlyList<FishingLineLogicalNodeDefinition> definitions = GetActiveDefinitions();
Gizmos.color = segmentColor; Gizmos.color = segmentColor;
for (int segmentIndex = 0; segmentIndex < points.Count - 1; segmentIndex++) for (int index = 0; index < points.Count - 1; index++)
{ {
Gizmos.DrawLine(points[segmentIndex].Position, points[segmentIndex + 1].Position); Gizmos.DrawLine(points[index].Position, points[index + 1].Position);
} }
for (int pointIndex = 0; pointIndex < points.Count; pointIndex++) for (int pointIndex = 0; pointIndex < points.Count; pointIndex++)
{ {
RuntimePoint point = points[pointIndex]; RuntimePoint point = points[pointIndex];
if (point.IsLogicalNode) if (point.LogicalNodeIndex >= 0 && point.LogicalNodeIndex < logicalNodes.Count)
{ {
FishingLineLogicalNodeDefinition definition = definitions[point.LogicalNodeIndex]; Gizmos.color = logicalNodes[point.LogicalNodeIndex].DebugColor;
Gizmos.color = definition.DebugColor; Gizmos.DrawSphere(point.Position, debugPointRadius * 1.35f);
Gizmos.DrawSphere(point.Position, debugNodeRadius * 1.35f);
} }
else else
{ {
Gizmos.color = virtualNodeColor; Gizmos.color = virtualPointColor;
Gizmos.DrawSphere(point.Position, debugNodeRadius); Gizmos.DrawSphere(point.Position, debugPointRadius);
} }
} }
} }
[Serializable] private void EnsureRuntimeTopologyMatchesLength()
private class RuntimeLogicalNode
{ {
public RuntimeLogicalNode(int logicalIndex, int pointIndex, RuntimePoint point) if (logicalNodes.Count < 1)
{ {
LogicalIndex = logicalIndex; return;
PointIndex = pointIndex;
Point = point;
} }
public int LogicalIndex { get; } int minSubsegments = Mathf.Max(1, logicalNodes[0].VirtualNodeCount + 1);
public int PointIndex { get; } int requiredSubsegments = Mathf.Max(minSubsegments, Mathf.CeilToInt(startSegmentLength / maxSolverSegmentLength));
public RuntimePoint Point { get; } if (requiredSubsegments != startSegmentSubsegments)
{
BuildRuntime();
SnapAnchors();
RefreshLinePositions();
}
}
private void InitializeRuntimePointPositions(IReadOnlyList<Vector3> previousPolyline)
{
if (points.Count == 0)
{
return;
}
Vector3 anchor = GetStartAnchorPosition();
points[0].Position = anchor;
points[0].PreviousPosition = anchor;
float targetTotalLength = TotalLineLength;
if (previousPolyline != null && previousPolyline.Count > 1)
{
Vector3 oldAnchor = previousPolyline[0];
Vector3 offset = anchor - oldAnchor;
float cumulativeDistance = 0f;
for (int pointIndex = 1; pointIndex < points.Count; pointIndex++)
{
cumulativeDistance += GetSegmentTargetLength(pointIndex - 1);
float normalized = targetTotalLength > MinSegmentLength ? cumulativeDistance / targetTotalLength : 0f;
Vector3 sampled = SamplePolylineNormalized(previousPolyline, normalized) + offset;
points[pointIndex].Position = sampled;
points[pointIndex].PreviousPosition = sampled;
}
return;
}
Vector3 direction = initialDirection.sqrMagnitude > 0f ? initialDirection.normalized : Vector3.down;
Vector3 cursor = anchor;
for (int pointIndex = 1; pointIndex < points.Count; pointIndex++)
{
cursor += direction * GetSegmentTargetLength(pointIndex - 1);
points[pointIndex].Position = cursor;
points[pointIndex].PreviousPosition = cursor;
}
}
private static Vector3 SamplePolylineNormalized(IReadOnlyList<Vector3> polyline, float normalized)
{
if (polyline == null || polyline.Count == 0)
{
return Vector3.zero;
}
if (polyline.Count == 1)
{
return polyline[0];
}
float totalLength = 0f;
for (int index = 0; index < polyline.Count - 1; index++)
{
totalLength += Vector3.Distance(polyline[index], polyline[index + 1]);
}
if (totalLength <= Mathf.Epsilon)
{
return polyline[0];
}
float targetDistance = Mathf.Clamp01(normalized) * totalLength;
float traversed = 0f;
for (int index = 0; index < polyline.Count - 1; index++)
{
Vector3 from = polyline[index];
Vector3 to = polyline[index + 1];
float segmentLength = Vector3.Distance(from, to);
if (traversed + segmentLength >= targetDistance)
{
float t = segmentLength <= Mathf.Epsilon ? 0f : (targetDistance - traversed) / segmentLength;
return Vector3.Lerp(from, to, t);
}
traversed += segmentLength;
}
return polyline[polyline.Count - 1];
} }
[Serializable]
private class RuntimePoint private class RuntimePoint
{ {
public RuntimePoint(Vector3 position, bool isAnchored, int logicalNodeIndex, float gravityScale, float damping) public RuntimePoint(Vector3 position, bool anchored, int logicalNodeIndex, float gravityScale, float damping)
{ {
Position = position; Position = position;
PreviousPosition = position; PreviousPosition = position;
IsAnchored = isAnchored; IsAnchored = anchored;
LogicalNodeIndex = logicalNodeIndex; LogicalNodeIndex = logicalNodeIndex;
GravityScale = gravityScale; GravityScale = gravityScale;
Damping = damping; Damping = damping;
@@ -589,11 +606,8 @@ public class FishingLineSolver : MonoBehaviour
public Vector3 Position; public Vector3 Position;
public Vector3 PreviousPosition; public Vector3 PreviousPosition;
public bool IsAnchored; public bool IsAnchored;
public Transform Anchor;
public int LogicalNodeIndex; public int LogicalNodeIndex;
public float GravityScale; public float GravityScale;
public float Damping; public float Damping;
public bool IsLogicalNode => LogicalNodeIndex >= 0;
} }
} }

View File

@@ -1,6 +1,6 @@
using UnityEngine; using UnityEngine;
public class FishingGameplayController : MonoBehaviour public class FishingLineTestController : MonoBehaviour
{ {
[SerializeField] private FishingLineSolver solver; [SerializeField] private FishingLineSolver solver;
[SerializeField] [Min(0.01f)] private float reelSpeed = 1.5f; [SerializeField] [Min(0.01f)] private float reelSpeed = 1.5f;
@@ -38,7 +38,7 @@ public class FishingGameplayController : MonoBehaviour
if (Mathf.Abs(direction) > Mathf.Epsilon) if (Mathf.Abs(direction) > Mathf.Epsilon)
{ {
solver.AdjustLineLength(direction * reelSpeed * Time.deltaTime); solver.AdjustStartSegmentLength(direction * reelSpeed * Time.deltaTime);
} }
} }
} }

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 465a2f7c62ee04948b577ee52382c43e

View File

@@ -0,0 +1,34 @@
using UnityEngine;
public class FishingLureNodeBehaviour : FishingLineNodeBehaviour
{
[SerializeField] [Min(0f)] private float swimStrength = 2f;
[SerializeField] [Min(0f)] private float swimFrequency = 6f;
[SerializeField] [Range(0f, 1f)] private float waterDrag = 0.12f;
[SerializeField] private Transform waterSurface;
[SerializeField] private float fallbackWaterHeight = 0f;
public override void Evaluate(ref FishingLineNodeInfluence influence, in FishingLineNodeContext context)
{
if (!BehaviourEnabled)
{
return;
}
float surfaceY = waterSurface != null ? waterSurface.position.y : fallbackWaterHeight;
if (context.Position.y > surfaceY)
{
return;
}
Vector3 side = Vector3.Cross(context.Tangent.sqrMagnitude > 0f ? context.Tangent.normalized : Vector3.forward, Vector3.up);
if (side.sqrMagnitude <= Mathf.Epsilon)
{
side = Vector3.right;
}
float swim = Mathf.Sin(context.Time * swimFrequency) * swimStrength;
influence.AdditionalAcceleration += side.normalized * swim;
influence.ExtraDamping = Mathf.Max(influence.ExtraDamping, waterDrag);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3c801f9083845c14088f6db1fdbc6020

View File

@@ -1,10 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "FishingRigDefinition", menuName = "Fishing/Fishing Rig Definition")]
public class FishingRigDefinition : ScriptableObject
{
[SerializeField] private List<FishingLineLogicalNodeDefinition> logicalNodes = new List<FishingLineLogicalNodeDefinition>();
public IReadOnlyList<FishingLineLogicalNodeDefinition> LogicalNodes => logicalNodes;
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 9d66919bfa33a7445b32a45410e0ccfe

View File

@@ -2,7 +2,7 @@
%TAG !u! tag:unity3d.com,2011: %TAG !u! tag:unity3d.com,2011:
--- !u!78 &1 --- !u!78 &1
TagManager: TagManager:
serializedVersion: 2 serializedVersion: 3
tags: [] tags: []
layers: layers:
- Default - Default
@@ -11,7 +11,7 @@ TagManager:
- -
- Water - Water
- UI - UI
- - Terrain
- -
- -
- -
@@ -50,27 +50,4 @@ TagManager:
- Light Layer 5 - Light Layer 5
- Light Layer 6 - Light Layer 6
- Light Layer 7 - Light Layer 7
- m_MigratedRenderPipelines: []
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-