diff --git a/Assets/Art/Textures/DivingBackground.png b/Assets/Art/Textures/DivingBackground.png new file mode 100644 index 00000000..8b0a5782 Binary files /dev/null and b/Assets/Art/Textures/DivingBackground.png differ diff --git a/Assets/Art/Textures/DivingBackground.png.meta b/Assets/Art/Textures/DivingBackground.png.meta new file mode 100644 index 00000000..b080923e --- /dev/null +++ b/Assets/Art/Textures/DivingBackground.png.meta @@ -0,0 +1,195 @@ +fileFormatVersion: 2 +guid: ad9b785acb09cb247ae2c8cd895863de +TextureImporter: + internalIDToNameTable: + - first: + 213: 5958968447627082961 + second: DivingBackground_0 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 2 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: iOS + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: DivingBackground_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1080 + height: 1981 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 1dcb29c2b3282b250800000000000000 + internalID: 5958968447627082961 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: + DivingBackground_0: 5958968447627082961 + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/MiniGames/DivingForPictures.unity b/Assets/Scenes/MiniGames/DivingForPictures.unity index 9f57b92b..e30a8a07 100644 --- a/Assets/Scenes/MiniGames/DivingForPictures.unity +++ b/Assets/Scenes/MiniGames/DivingForPictures.unity @@ -436,12 +436,10 @@ MonoBehaviour: ropeFollowSpeed: 5 ropeTrailing: 0.2 ropeGravityStrength: 9.8 - ropeMaxHangDistance: 2 ropeVerticalHangStrength: 2 - ropeOscillationAmplitude: 0.15 - ropeOscillationFrequency: 2 ropeDamping: 0.3 initialSeparationDistance: 0.1 + initialFallImpulse: 2 --- !u!1 &224729330 GameObject: m_ObjectHideFlags: 0 @@ -644,6 +642,93 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &461301695 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 461301697} + - component: {fileID: 461301696} + m_Layer: 0 + m_Name: DivingBackground_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!212 &461301696 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 461301695} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, 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: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 622133659 + m_SortingLayer: -1 + m_SortingOrder: 0 + m_Sprite: {fileID: 5958968447627082961, guid: ad9b785acb09cb247ae2c8cd895863de, type: 3} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 10.803711, y: 19.81} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 +--- !u!4 &461301697 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 461301695} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1.12, z: 0} + m_LocalScale: {x: 0.81438, y: 0.81438, z: 0.81438} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &730962732 GameObject: m_ObjectHideFlags: 0 @@ -1319,12 +1404,10 @@ MonoBehaviour: ropeFollowSpeed: 5 ropeTrailing: 0.2 ropeGravityStrength: 9.8 - ropeMaxHangDistance: 2 ropeVerticalHangStrength: 2 - ropeOscillationAmplitude: 0.15 - ropeOscillationFrequency: 2 ropeDamping: 0.3 initialSeparationDistance: 0.1 + initialFallImpulse: 2 --- !u!1 &1063641111 GameObject: m_ObjectHideFlags: 0 @@ -1337,6 +1420,7 @@ GameObject: - component: {fileID: 1063641113} - component: {fileID: 1063641112} - component: {fileID: 1063641115} + - component: {fileID: 1063641116} m_Layer: 0 m_Name: Main Camera m_TagString: MainCamera @@ -1361,8 +1445,8 @@ Camera: m_GameObject: {fileID: 1063641111} m_Enabled: 1 serializedVersion: 2 - m_ClearFlags: 1 - m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.5722232, g: 0.6321867, b: 0.7264151, a: 0} m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 @@ -1450,6 +1534,50 @@ MonoBehaviour: m_PostInfinity: 2 m_RotationOrder: 4 CustomBlends: {fileID: 0} +--- !u!114 &1063641116 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1063641111} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 --- !u!1 &1224833348 GameObject: m_ObjectHideFlags: 0 @@ -1816,12 +1944,10 @@ MonoBehaviour: ropeFollowSpeed: 5 ropeTrailing: 0.2 ropeGravityStrength: 9.8 - ropeMaxHangDistance: 2 ropeVerticalHangStrength: 2 - ropeOscillationAmplitude: 0.1 - ropeOscillationFrequency: 3 ropeDamping: 0.3 initialSeparationDistance: 0.1 + initialFallImpulse: 2 --- !u!1 &1679185997 GameObject: m_ObjectHideFlags: 0 @@ -2128,3 +2254,4 @@ SceneRoots: - {fileID: 116234201} - {fileID: 824396217} - {fileID: 323864665} + - {fileID: 461301697} diff --git a/Assets/Scripts/Minigames/DivingForPictures/RopeBreaker.cs b/Assets/Scripts/Minigames/DivingForPictures/RopeBreaker.cs index ee91c5c1..fa0b022c 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/RopeBreaker.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/RopeBreaker.cs @@ -40,9 +40,6 @@ public class RopeBreaker : MonoBehaviour [Tooltip("Initial downward impulse for falling rope end")] [SerializeField] private float initialFallImpulse = 2.0f; - - [Tooltip("Force Y position reset on start to ensure proper falling")] - [SerializeField] private bool forceYReset = true; // Private references private Rope originalRope; @@ -122,10 +119,9 @@ public class RopeBreaker : MonoBehaviour // Set specific transform to follow instead of using tag follower.SetTargetTransform(originalStartPoint); - + follower.canFall = false; // Player rope end doesn't fall follower.followSpeed = ropeFollowSpeed; follower.trailing = ropeTrailing; - follower.useGravity = true; // Player rope end doesn't use gravity // Create second break point (for the rock-attached end) GameObject secondBreakObj = new GameObject("RopeBreakPoint_Second"); @@ -138,15 +134,13 @@ public class RopeBreaker : MonoBehaviour // Set specific transform to follow instead of using tag secondFollower.SetTargetTransform(originalEndPoint); - + secondFollower.canFall = true; // Rock end can fall secondFollower.followSpeed = ropeFollowSpeed; secondFollower.trailing = ropeTrailing; - secondFollower.useGravity = true; // Enable gravity for hanging rope end secondFollower.gravityStrength = ropeGravityStrength; secondFollower.verticalHangStrength = ropeVerticalHangStrength; secondFollower.damping = ropeDamping; secondFollower.initialFallImpulse = initialFallImpulse; - secondFollower.forceYReset = forceYReset; // Create initial separation Vector3 direction = (originalEndPoint.position - breakPointPosition).normalized; diff --git a/Assets/Scripts/Minigames/DivingForPictures/RopeEndPhysicsFollower.cs b/Assets/Scripts/Minigames/DivingForPictures/RopeEndPhysicsFollower.cs index 2d832e54..a9549101 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/RopeEndPhysicsFollower.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/RopeEndPhysicsFollower.cs @@ -4,43 +4,196 @@ using UnityEngine; public class RopeEndPhysicsFollower : MonoBehaviour { [Header("Target Settings")] - [Tooltip("Tag of the object this endpoint should follow (used if targetTransform is not set)")] - public string targetTag; - [Tooltip("Transform this endpoint should follow (takes precedence over targetTag)")] + [Tooltip("Transform this endpoint should follow")] public Transform targetTransform; - [Tooltip("How quickly the endpoint follows the target")] + [Tooltip("Tag of the object this endpoint should follow (only used if targetTransform is not set)")] + public string targetTag; + [Tooltip("How quickly the endpoint follows the target when not using physics")] public float followSpeed = 5f; [Tooltip("How much trailing (0 = instant, 1 = very slow)")] public float trailing = 0.2f; [Header("Physics Simulation")] - [Tooltip("Enable/disable gravity effect")] - public bool useGravity = true; [Tooltip("Gravity strength")] public float gravityStrength = 9.8f; [Tooltip("How strongly the rope attempts to hang vertically")] public float verticalHangStrength = 2f; [Tooltip("Damping for physics movement (higher = less bouncy)")] public float damping = 0.3f; - [Tooltip("Initial downward impulse when gravity is enabled")] + [Tooltip("Initial downward impulse when enabled")] public float initialFallImpulse = 2.0f; - [Tooltip("Force a Y position reset on start to ensure falling occurs")] - public bool forceYReset = true; + [Tooltip("Whether this end can fall with gravity (false for player-attached ends)")] + public bool canFall = true; // Private variables private Transform target; - private Vector3 velocity; private Vector2 physicsVelocity; private Vector2 offset; private Vector3 lastTargetPosition; private bool initialized = false; private bool debugLog = true; - + // Rope reference to get the actual rope length private Rope attachedRope; private float maxDistance; void Start() + { + // Find the Rope component to determine the maximum distance + FindAttachedRope(); + + // Use targetTransform if set, otherwise try to find by tag + if (targetTransform != null) + { + target = targetTransform; + if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Using assigned target transform: {target.name}"); + } + else if (!string.IsNullOrEmpty(targetTag)) + { + GameObject found = GameObject.FindGameObjectWithTag(targetTag); + if (found) + { + target = found.transform; + if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Found target by tag '{targetTag}': {target.name}"); + } + } + + // Initialize offset and velocities + if (target) + { + // Only store horizontal offset, not vertical for physics simulation + Vector2 offsetVec = transform.position - target.position; + offset.x = offsetVec.x; + offset.y = 0; // Don't preserve vertical offset for gravity simulation + lastTargetPosition = target.position; + + // Apply initial falling impulse if this end can fall + if (canFall) + { + physicsVelocity = new Vector2(0, -initialFallImpulse); + if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Initialized with target: {target.name}, initial Y velocity: {physicsVelocity.y}"); + } + } + else + { + offset = Vector2.zero; + lastTargetPosition = transform.position; + if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] No target found"); + } + + initialized = true; + } + + void Update() + { + if (!target) return; + + // Calculate deltaTime for physics stability + float deltaTime = Time.deltaTime; + + // Get target velocity + Vector3 targetVelocity = (target.position - lastTargetPosition) / deltaTime; + lastTargetPosition = target.position; + + // Current position relative to target + Vector2 relativePos = new Vector2( + transform.position.x - target.position.x, + transform.position.y - target.position.y + ); + + // Get the straight-line distance between target and this transform + float currentDistance = relativePos.magnitude; + + // Normalized direction from target to this transform + Vector2 directionToTarget = relativePos.normalized; + + // Apply forces based on whether this end can fall + if (canFall) + { + // 1. Gravity - always pulls down + physicsVelocity.y -= gravityStrength * deltaTime; + } + + // 2. Vertical hanging force - try to align X with target when stationary + if (Mathf.Abs(targetVelocity.x) < 0.1f) + { + // Use the actual X position of the target as the desired X position + float xOffset = transform.position.x - target.position.x; + physicsVelocity.x -= xOffset * verticalHangStrength * deltaTime; + + // Debug log to track vertical hanging behavior + if (debugLog && Time.frameCount % 120 == 0) + { + Debug.Log($"[RopeEndPhysicsFollower] Vertical hanging: target X={target.position.x}, my X={transform.position.x}, offset={xOffset}"); + } + } + + // 3. Rope length constraint - apply a force toward the target if we're exceeding the rope length + if (currentDistance > maxDistance) + { + // Calculate constraint force proportional to how much we're exceeding the rope length + float exceededDistance = currentDistance - maxDistance; + + // Apply a stronger constraint force the more we exceed the max distance + Vector2 constraintForce = -directionToTarget * exceededDistance * 10f; + + // Apply to velocity + physicsVelocity += constraintForce * deltaTime; + + if (debugLog && Time.frameCount % 60 == 0) + { + Debug.Log($"[RopeEndPhysicsFollower] Exceeding max distance: {exceededDistance}, applying constraint"); + } + } + + // Apply damping to physics velocity + physicsVelocity *= (1f - damping * deltaTime); + + // Log physics state periodically for debugging + if (debugLog && Time.frameCount % 60 == 0) + { + Debug.Log($"[RopeEndPhysicsFollower] Y position: {transform.position.y}, Y velocity: {physicsVelocity.y}, Distance: {currentDistance}/{maxDistance}"); + } + + // Apply physics velocity to position + Vector3 newPos = transform.position; + newPos.x += physicsVelocity.x * deltaTime; + + // Only apply vertical movement if this end can fall + if (canFall) + { + newPos.y += physicsVelocity.y * deltaTime; + } + + transform.position = newPos; + + // Final distance check - hard constraint to ensure we never exceed the rope length + // This prevents numerical instability from causing the rope to stretch + float finalDistance = Vector2.Distance( + new Vector2(transform.position.x, transform.position.y), + new Vector2(target.position.x, target.position.y) + ); + + if (finalDistance > maxDistance) + { + // Calculate the direction from target to this transform + Vector2 direction = new Vector2( + transform.position.x - target.position.x, + transform.position.y - target.position.y + ).normalized; + + // Set position to be exactly at the maximum distance + Vector3 constrainedPos = new Vector3( + target.position.x + direction.x * maxDistance, + target.position.y + direction.y * maxDistance, + transform.position.z + ); + + transform.position = constrainedPos; + } + } + + private void FindAttachedRope() { // Find the Rope component on the same GameObject or parent attachedRope = GetComponent(); @@ -82,172 +235,6 @@ public class RopeEndPhysicsFollower : MonoBehaviour maxDistance = 2f; if (debugLog) Debug.Log("[RopeEndPhysicsFollower] No attached rope found, using default max distance"); } - - // Use targetTransform if set, otherwise try to find by tag - if (targetTransform != null) - { - target = targetTransform; - if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Using assigned target transform: {target.name}"); - } - else if (!string.IsNullOrEmpty(targetTag)) - { - GameObject found = GameObject.FindGameObjectWithTag(targetTag); - if (found) - { - target = found.transform; - if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Found target by tag '{targetTag}': {target.name}"); - } - } - - // Initialize offset and velocities - if (target) - { - // Only store horizontal offset, not vertical - Vector2 offsetVec = transform.position - target.position; - offset.x = offsetVec.x; - offset.y = 0; // Don't preserve vertical offset for gravity simulation - lastTargetPosition = target.position; - - // Apply initial falling impulse if using gravity - if (useGravity) - { - physicsVelocity = new Vector2(0, -initialFallImpulse); - - // Force an initial position change to ensure things start moving - if (forceYReset) - { - Vector3 pos = transform.position; - pos.y = target.position.y; // Reset to target's Y position - transform.position = pos; - } - - if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Initialized with target: {target.name}, gravity enabled, initial Y velocity: {physicsVelocity.y}"); - } - } - else - { - offset = Vector2.zero; - lastTargetPosition = transform.position; - if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] No target found"); - } - - velocity = Vector3.zero; - initialized = true; - } - - void Update() - { - if (!target) return; - - // Calculate deltaTime for physics stability - float deltaTime = Time.deltaTime; - - // Get target velocity - Vector3 targetVelocity = (target.position - lastTargetPosition) / deltaTime; - lastTargetPosition = target.position; - - // Basic position the endpoint should be at based on target position and horizontal offset - Vector3 basePosition = target.position + new Vector3(offset.x, 0, 0); - - // Apply physics simulation if enabled - if (useGravity) - { - // Get the straight-line distance between target and this transform - float currentDistance = Vector2.Distance( - new Vector2(transform.position.x, transform.position.y), - new Vector2(target.position.x, target.position.y) - ); - - // Current position relative to target - Vector2 relativePos = new Vector2( - transform.position.x - target.position.x, - transform.position.y - target.position.y - ); - - // Normalized direction from target to this transform - Vector2 directionToTarget = relativePos.normalized; - - // Apply forces: - // 1. Gravity - always pulls down - physicsVelocity.y -= gravityStrength * deltaTime; - - // 2. Vertical hanging force - try to align X with target (not with world origin) - if (Mathf.Abs(targetVelocity.x) < 0.1f) - { - // Use the actual X position of the target as the desired X position - float xOffset = transform.position.x - target.position.x; - physicsVelocity.x -= xOffset * verticalHangStrength * deltaTime; - - // Debug log to track vertical hanging behavior - if (debugLog && Time.frameCount % 120 == 0) - { - Debug.Log($"[RopeEndPhysicsFollower] Vertical hanging: target X={target.position.x}, my X={transform.position.x}, offset={xOffset}"); - } - } - - // 3. Rope length constraint - apply a force toward the target if we're exceeding the rope length - if (currentDistance > maxDistance) - { - // Calculate constraint force proportional to how much we're exceeding the rope length - float exceededDistance = currentDistance - maxDistance; - - // Apply a stronger constraint force the more we exceed the max distance - Vector2 constraintForce = -directionToTarget * exceededDistance * 10f; - - // Apply to velocity - physicsVelocity += constraintForce * deltaTime; - - if (debugLog && Time.frameCount % 60 == 0) - { - Debug.Log($"[RopeEndPhysicsFollower] Exceeding max distance: {exceededDistance}, applying constraint"); - } - } - - // Apply damping to physics velocity - physicsVelocity *= (1f - damping * deltaTime); - - // Log physics state periodically for debugging - if (debugLog && Time.frameCount % 60 == 0) - { - Debug.Log($"[RopeEndPhysicsFollower] Y position: {transform.position.y}, Y velocity: {physicsVelocity.y}, Distance: {currentDistance}/{maxDistance}"); - } - - // Apply physics velocity to position - Vector3 newPos = transform.position; - newPos.x += physicsVelocity.x * deltaTime; - newPos.y += physicsVelocity.y * deltaTime; - transform.position = newPos; - - // Final distance check - hard constraint to ensure we never exceed the rope length - // This prevents numerical instability from causing the rope to stretch - float finalDistance = Vector2.Distance( - new Vector2(transform.position.x, transform.position.y), - new Vector2(target.position.x, target.position.y) - ); - - if (finalDistance > maxDistance) - { - // Calculate the direction from target to this transform - Vector2 direction = new Vector2( - transform.position.x - target.position.x, - transform.position.y - target.position.y - ).normalized; - - // Set position to be exactly at the maximum distance - Vector3 constrainedPos = new Vector3( - target.position.x + direction.x * maxDistance, - target.position.y + direction.y * maxDistance, - transform.position.z - ); - - transform.position = constrainedPos; - } - } - else - { - // Original smooth follow behavior without physics - transform.position = Vector3.SmoothDamp(transform.position, basePosition, ref velocity, trailing, followSpeed); - } } public void SetTargetTransform(Transform newTarget) @@ -273,8 +260,8 @@ public class RopeEndPhysicsFollower : MonoBehaviour offset.y = 0; // Don't preserve vertical offset for gravity simulation } - // Apply initial falling impulse if using gravity - if (useGravity) + // Apply initial falling impulse if this end can fall + if (canFall) { physicsVelocity = new Vector2(physicsVelocity.x, -initialFallImpulse); if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Reset Y velocity to {physicsVelocity.y} after target change to {target.name}"); @@ -289,30 +276,7 @@ public class RopeEndPhysicsFollower : MonoBehaviour GameObject found = GameObject.FindGameObjectWithTag(targetTag); if (found) { - targetTransform = found.transform; - target = found.transform; - lastTargetPosition = target.position; - - // Only update horizontal offset to maintain current vertical position - if (initialized) - { - Vector2 newOffset = transform.position - target.position; - offset.x = newOffset.x; - // Don't update offset.y to allow gravity to work - } - else - { - Vector2 newOffset = transform.position - target.position; - offset.x = newOffset.x; - offset.y = 0; // Don't preserve vertical offset for gravity simulation - } - - // Apply initial falling impulse if using gravity - if (useGravity) - { - physicsVelocity = new Vector2(physicsVelocity.x, -initialFallImpulse); - if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Reset Y velocity to {physicsVelocity.y} after target change"); - } + SetTargetTransform(found.transform); } } @@ -323,16 +287,16 @@ public class RopeEndPhysicsFollower : MonoBehaviour { // Reset velocity with a strong downward impulse physicsVelocity = new Vector2(0, -initialFallImpulse * 2f); - + // Reset position to be at the same level as the target Vector3 pos = transform.position; pos.y = target.position.y; transform.position = pos; - + if (debugLog) Debug.Log($"[RopeEndPhysicsFollower] Physics forcibly reset, new Y velocity: {physicsVelocity.y}"); } } - + // Method to manually set the maximum distance public void SetMaxDistance(float distance) {