diff --git a/Assets/FishingRigDefinition.asset b/Assets/FishingRigDefinition.asset new file mode 100644 index 0000000..5f11459 --- /dev/null +++ b/Assets/FishingRigDefinition.asset @@ -0,0 +1,36 @@ +%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} diff --git a/Assets/FishingRigDefinition.asset.meta b/Assets/FishingRigDefinition.asset.meta new file mode 100644 index 0000000..1da9eeb --- /dev/null +++ b/Assets/FishingRigDefinition.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a48cb68bf14ac8c429abc96a2cdb7821 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 1c63aa8..43d365f 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -38,12 +38,12 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 12 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 m_GISettings: serializedVersion: 2 m_BounceScale: 1 @@ -206,7 +206,7 @@ Transform: m_GameObject: {fileID: 330585543} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalPosition: {x: 0, y: -0.03, z: -0.66} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -248,14 +248,14 @@ MonoBehaviour: m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} m_RequiresDepthTexture: 0 m_RequiresColorTexture: 0 - m_Version: 2 m_TaaSettings: - quality: 3 - frameInfluence: 0.1 - jitterScale: 1 - mipBias: 0 - varianceClampScale: 0.9 - contrastAdaptiveSharpening: 0 + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 + m_Version: 2 --- !u!1 &410087039 GameObject: m_ObjectHideFlags: 0 @@ -282,14 +282,14 @@ Light: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 410087039} m_Enabled: 1 - serializedVersion: 11 + serializedVersion: 13 m_Type: 1 m_Color: {r: 1, g: 1, b: 1, a: 1} m_Intensity: 2 m_Range: 10 m_SpotAngle: 30 m_InnerSpotAngle: 21.80208 - m_CookieSize: 10 + m_CookieSize2D: {x: 10, y: 10} m_Shadows: m_Type: 2 m_Resolution: -1 @@ -334,8 +334,11 @@ Light: m_UseBoundingSphereOverride: 0 m_UseViewFrustumForShadowCasterCull: 1 m_ForceVisible: 0 - m_ShadowRadius: 0 + m_ShapeRadius: 0 m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 --- !u!4 &410087041 Transform: m_ObjectHideFlags: 0 @@ -363,17 +366,255 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} m_Name: m_EditorClassIdentifier: - m_Version: 3 m_UsePipelineSettings: 1 m_AdditionalLightsShadowResolutionTier: 2 - m_LightLayerMask: 1 - m_RenderingLayers: 1 m_CustomShadowLayers: 0 - m_ShadowLayerMask: 1 - m_ShadowRenderingLayers: 1 m_LightCookieSize: {x: 1, y: 1} m_LightCookieOffset: {x: 0, y: 0} m_SoftShadowQuality: 1 + m_RenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_ShadowRenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_Version: 4 + m_LightLayerMask: 1 + m_ShadowLayerMask: 1 + m_RenderingLayers: 1 + m_ShadowRenderingLayers: 1 +--- !u!1 &445360720 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 445360722} + - component: {fileID: 445360721} + - component: {fileID: 445360723} + - component: {fileID: 445360725} + - component: {fileID: 445360724} + m_Layer: 0 + m_Name: FishingLineRoot + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + 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 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 445360720} + 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: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !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: + 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: 0af449a2493cf8c4281c9d5ae01c2a92, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::FishingLineRendererBinder + solver: {fileID: 445360723} + lineRenderer: {fileID: 445360725} + attachmentBindings: [] +--- !u!120 &445360725 +LineRenderer: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 445360720} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 0 + m_LightProbeUsage: 0 + m_ReflectionProbeUsage: 0 + m_RayTracingMode: 0 + 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: 0} + 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_Positions: + - {x: 0, y: 0, z: 0} + - {x: 0, y: -0.01, z: 1} + m_Parameters: + serializedVersion: 3 + widthMultiplier: 1 + widthCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0.01 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + colorGradient: + serializedVersion: 2 + key0: {r: 1, g: 1, b: 1, a: 1} + key1: {r: 1, g: 1, b: 1, a: 1} + key2: {r: 0, g: 0, b: 0, a: 0} + key3: {r: 0, g: 0, b: 0, a: 0} + key4: {r: 0, g: 0, b: 0, a: 0} + key5: {r: 0, g: 0, b: 0, a: 0} + key6: {r: 0, g: 0, b: 0, a: 0} + key7: {r: 0, g: 0, b: 0, a: 0} + ctime0: 0 + ctime1: 65535 + ctime2: 0 + ctime3: 0 + ctime4: 0 + ctime5: 0 + ctime6: 0 + ctime7: 0 + atime0: 0 + atime1: 65535 + atime2: 0 + atime3: 0 + atime4: 0 + atime5: 0 + atime6: 0 + atime7: 0 + m_Mode: 0 + m_ColorSpace: -1 + m_NumColorKeys: 2 + m_NumAlphaKeys: 2 + numCornerVertices: 0 + numCapVertices: 0 + alignment: 0 + textureMode: 0 + textureScale: {x: 1, y: 1} + shadowBias: 0.5 + generateLightingData: 0 + m_UseWorldSpace: 1 + m_Loop: 0 + m_ApplyActiveColorSpace: 1 --- !u!1 &832575517 GameObject: m_ObjectHideFlags: 0 @@ -423,6 +664,118 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1538371799 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1538371803} + - component: {fileID: 1538371802} + - component: {fileID: 1538371801} + - component: {fileID: 1538371800} + m_Layer: 0 + m_Name: Tip + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!135 &1538371800 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1538371799} + 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_Radius: 0.5 + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &1538371801 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1538371799} + 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 &1538371802 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1538371799} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1538371803 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1538371799} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0.02, y: 0.02, z: 0.02} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 @@ -430,3 +783,5 @@ SceneRoots: - {fileID: 330585546} - {fileID: 410087041} - {fileID: 832575519} + - {fileID: 1538371803} + - {fileID: 445360722} diff --git a/Assets/Scripts/FishingGameplayController.cs b/Assets/Scripts/FishingGameplayController.cs index e3e3fe8..256105e 100644 --- a/Assets/Scripts/FishingGameplayController.cs +++ b/Assets/Scripts/FishingGameplayController.cs @@ -3,7 +3,7 @@ using UnityEngine; public class FishingGameplayController : MonoBehaviour { [SerializeField] private FishingLineSolver solver; - [SerializeField] [Min(0.01f)] private float reelSpeed = 0.5f; + [SerializeField] [Min(0.01f)] private float reelSpeed = 1.5f; [SerializeField] private KeyCode reelInKey = KeyCode.E; [SerializeField] private KeyCode reelOutKey = KeyCode.Q; @@ -38,7 +38,7 @@ public class FishingGameplayController : MonoBehaviour if (Mathf.Abs(direction) > Mathf.Epsilon) { - solver.AdjustLengthScale(direction * reelSpeed * Time.deltaTime); + solver.AdjustLineLength(direction * reelSpeed * Time.deltaTime); } } } diff --git a/Assets/Scripts/FishingLineNode.cs b/Assets/Scripts/FishingLineNode.cs index d7dae75..c8b479d 100644 --- a/Assets/Scripts/FishingLineNode.cs +++ b/Assets/Scripts/FishingLineNode.cs @@ -6,10 +6,8 @@ public enum FishingLineNodeType { Start, Float, - Sinker, - Hook, - Lure, Weight, + Terminal, Normal } diff --git a/Assets/Scripts/FishingLineSolver.cs b/Assets/Scripts/FishingLineSolver.cs index 7361735..4118582 100644 --- a/Assets/Scripts/FishingLineSolver.cs +++ b/Assets/Scripts/FishingLineSolver.cs @@ -20,9 +20,9 @@ public class FishingLineSolver : MonoBehaviour [SerializeField] private bool simulateInFixedUpdate = false; [Header("Line Length")] - [SerializeField] [Min(0.1f)] private float lengthScale = 1f; - [SerializeField] [Min(0.1f)] private float minLengthScale = 0.35f; - [SerializeField] [Min(0.1f)] private float maxLengthScale = 2.5f; + [SerializeField] [Min(0.01f)] private float currentLineLength = 3.4f; + [SerializeField] [Min(0.01f)] private float minLineLength = 1.2f; + [SerializeField] [Min(0.01f)] private float maxLineLength = 8.5f; [Header("Debug")] [SerializeField] private bool drawDebug = true; @@ -34,14 +34,19 @@ public class FishingLineSolver : MonoBehaviour private readonly List logicalNodes = new List(); private readonly List baseSegmentLengths = new List(); private readonly List linePositions = new List(); + private readonly List fallbackLogicalNodes = new List(); private bool runtimeBuilt; private float currentTensionNormalized; + private float previousLineLength; + private float baseTotalLength; public IReadOnlyList LinePositions => linePositions; public int PointCount => points.Count; public int LogicalNodeCount => logicalNodes.Count; public float CurrentTensionNormalized => currentTensionNormalized; + public float CurrentLineLength => currentLineLength; + public float BaseTotalLength => baseTotalLength; public IReadOnlyList LogicalNodeDefinitions => GetActiveDefinitions(); @@ -52,38 +57,15 @@ public class FishingLineSolver : MonoBehaviour private void Reset() { - if (inlineLogicalNodes.Count > 0) - { - return; - } - - 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 sinkerNode = new FishingLineLogicalNodeDefinition(); - sinkerNode.ConfigurePrototype("Sinker", FishingLineNodeType.Sinker, 1.4f, 4, 1.6f, 0.02f, new Color(0.7f, 0.85f, 1f, 1f)); - - FishingLineLogicalNodeDefinition hookNode = new FishingLineLogicalNodeDefinition(); - hookNode.ConfigurePrototype("Hook", FishingLineNodeType.Hook, 0.8f, 2, 1.2f, 0.03f, new Color(1f, 0.35f, 0.35f, 1f)); - - inlineLogicalNodes = new List - { - startNode, - floatNode, - sinkerNode, - hookNode - }; + EnsureInlinePrototypeIfNeeded(); } private void OnValidate() { solverIterations = Mathf.Max(1, solverIterations); - minLengthScale = Mathf.Max(0.1f, minLengthScale); - maxLengthScale = Mathf.Max(minLengthScale, maxLengthScale); - lengthScale = Mathf.Clamp(lengthScale, minLengthScale, maxLengthScale); + minLineLength = Mathf.Max(MinSegmentLength, minLineLength); + maxLineLength = Mathf.Max(minLineLength, maxLineLength); + currentLineLength = Mathf.Clamp(currentLineLength, minLineLength, maxLineLength); } private void Update() @@ -105,19 +87,31 @@ public class FishingLineSolver : MonoBehaviour [ContextMenu("Rebuild Solver")] public void Rebuild() { + EnsureInlinePrototypeIfNeeded(); BuildRuntime(); SnapToAnchors(); RefreshLinePositions(); + previousLineLength = currentLineLength; } - public void SetLengthScale(float newScale) + public void SetLineLength(float newLength) { - lengthScale = Mathf.Clamp(newScale, minLengthScale, maxLengthScale); + float clampedLength = Mathf.Clamp(newLength, minLineLength, maxLineLength); + bool expanded = clampedLength > currentLineLength + Mathf.Epsilon; + currentLineLength = clampedLength; + + if (expanded) + { + ExpandLineToCurrentLength(); + RefreshLinePositions(); + } + + previousLineLength = currentLineLength; } - public void AdjustLengthScale(float delta) + public void AdjustLineLength(float delta) { - SetLengthScale(lengthScale + delta); + SetLineLength(currentLineLength + delta); } public bool TryGetLogicalNodePosition(int logicalNodeIndex, out Vector3 position) @@ -160,11 +154,17 @@ public class FishingLineSolver : MonoBehaviour } SyncAnchors(); + if (currentLineLength > previousLineLength + Mathf.Epsilon) + { + ExpandLineToCurrentLength(); + } + Integrate(deltaTime); SolveConstraints(); SyncAnchors(); RefreshLinePositions(); UpdateTension(); + previousLineLength = currentLineLength; } private void BuildRuntime() @@ -173,14 +173,23 @@ public class FishingLineSolver : MonoBehaviour logicalNodes.Clear(); baseSegmentLengths.Clear(); linePositions.Clear(); + baseTotalLength = 0f; - IReadOnlyList definitions = GetActiveDefinitions(); + EnsureInlinePrototypeIfNeeded(); + IReadOnlyList definitions = GetResolvedDefinitions(); if (definitions.Count == 0) { runtimeBuilt = false; return; } + if (definitions.Count < 2) + { + Debug.LogWarning("FishingLineSolver needs at least 2 logical nodes to build a valid line.", this); + runtimeBuilt = false; + return; + } + if (definitions[0].NodeType != FishingLineNodeType.Start) { Debug.LogWarning("FishingLineSolver expects the first logical node to be Start. The first node is still treated as the anchor.", this); @@ -213,11 +222,13 @@ public class FishingLineSolver : MonoBehaviour 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); @@ -231,9 +242,119 @@ public class FishingLineSolver : MonoBehaviour linePositions.Add(cursor); } + InitializeLineLengthDefaults(); runtimeBuilt = points.Count > 0; } + private void InitializeLineLengthDefaults() + { + if (baseTotalLength <= MinSegmentLength) + { + 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; + } + + if (IsDefinitionListUsable(inlineLogicalNodes)) + { + return; + } + + inlineLogicalNodes.Clear(); + PopulatePrototypeNodes(inlineLogicalNodes); + } + + private IReadOnlyList 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 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 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++) @@ -298,7 +419,7 @@ public class FishingLineSolver : MonoBehaviour Vector3 delta = pointB.Position - pointA.Position; float distance = delta.magnitude; - float restLength = baseSegmentLengths[segmentIndex] * lengthScale; + float restLength = GetTargetSegmentLength(segmentIndex); if (distance <= restLength || distance <= Mathf.Epsilon) { continue; @@ -339,6 +460,48 @@ public class FishingLineSolver : MonoBehaviour } } + private void ExpandLineToCurrentLength() + { + if (points.Count < 2 || baseSegmentLengths.Count == 0) + { + return; + } + + for (int segmentIndex = 0; segmentIndex < baseSegmentLengths.Count; segmentIndex++) + { + 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; + } + + Vector3 offset = targetPosition - pointB.Position; + pointB.Position += offset; + pointB.PreviousPosition += offset; + } + } + + private float GetTargetSegmentLength(int segmentIndex) + { + if (segmentIndex < 0 || segmentIndex >= baseSegmentLengths.Count || baseTotalLength <= MinSegmentLength) + { + return MinSegmentLength; + } + + float normalizedLength = baseSegmentLengths[segmentIndex] / baseTotalLength; + return Mathf.Max(MinSegmentLength, currentLineLength * normalizedLength); + } + private void UpdateTension() { if (baseSegmentLengths.Count == 0) @@ -351,7 +514,7 @@ public class FishingLineSolver : MonoBehaviour for (int segmentIndex = 0; segmentIndex < baseSegmentLengths.Count; segmentIndex++) { float currentLength = Vector3.Distance(points[segmentIndex].Position, points[segmentIndex + 1].Position); - float restLength = Mathf.Max(MinSegmentLength, baseSegmentLengths[segmentIndex] * lengthScale); + float restLength = GetTargetSegmentLength(segmentIndex); maxStretch = Mathf.Max(maxStretch, Mathf.Clamp01((currentLength - restLength) / restLength)); } @@ -360,12 +523,7 @@ public class FishingLineSolver : MonoBehaviour private IReadOnlyList GetActiveDefinitions() { - if (rigDefinition != null && rigDefinition.LogicalNodes.Count > 0) - { - return rigDefinition.LogicalNodes; - } - - return inlineLogicalNodes; + return GetResolvedDefinitions(); } private void OnDrawGizmos() diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index f651ec1..bd901ef 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -920,7 +920,7 @@ PlayerSettings: qnxGraphicConfPath: apiCompatibilityLevel: 6 captureStartupLogs: {} - activeInputHandler: 1 + activeInputHandler: 2 windowsGamepadBackendHint: 0 enableDirectStorage: 0 cloudProjectId: diff --git a/ProjectSettings/SceneTemplateSettings.json b/ProjectSettings/SceneTemplateSettings.json new file mode 100644 index 0000000..ede5887 --- /dev/null +++ b/ProjectSettings/SceneTemplateSettings.json @@ -0,0 +1,121 @@ +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "defaultInstantiationMode": 0 + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "defaultInstantiationMode": 1 + }, + "newSceneOverride": 0 +} \ No newline at end of file diff --git a/UserSettings/Search.settings b/UserSettings/Search.settings index f8c76cb..a2f014c 100644 --- a/UserSettings/Search.settings +++ b/UserSettings/Search.settings @@ -11,6 +11,56 @@ showStatusBar = false scopes = { } providers = { + asset = { + active = true + priority = 25 + defaultAction = null + } + scene = { + active = true + priority = 50 + defaultAction = null + } + adb = { + active = false + priority = 2500 + defaultAction = null + } + presets_provider = { + active = false + priority = -10 + defaultAction = null + } + find = { + active = true + priority = 25 + defaultAction = null + } + packages = { + active = false + priority = 90 + defaultAction = null + } + performance = { + active = false + priority = 100 + defaultAction = null + } + store = { + active = false + priority = 100 + defaultAction = null + } + profilermarkers = { + active = false + priority = 100 + defaultAction = null + } + log = { + active = false + priority = 210 + defaultAction = null + } } objectSelectors = { }