It’s a good idea!
I had a bit of a go with a spring joint 2D and couldn’t quite find out how to make it work, but I found that a fixed joint with the damping ratio set high works really well. There’s a few optimisations to do, but I think it works pretty well.
I’ve included a testing class below as well if anyone wants to try it out. It’s a dragging class that lets you apply a force and torque (not calculated perfectly, but good enough for testing)
Thanks for the hint, I really appreciate it 
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))]
public class StickingObject : MonoBehaviour {
public float StuckDrag = 20;
public float StuckAngularDrag = 20;
Rigidbody2D rb;
ObjectStuckButMoving dynamicFriction;
ObjectStuckAndStill staticFriction;
float drag;
float angularDrag;
float gravity;
void Start() {
rb = GetComponent<Rigidbody2D>();
dynamicFriction = GetComponent<ObjectStuckButMoving>();
staticFriction = GetComponent<ObjectStuckAndStill>();
if (dynamicFriction == null)
dynamicFriction = gameObject.AddComponent<ObjectStuckButMoving>();
if (staticFriction == null)
staticFriction = gameObject.AddComponent<ObjectStuckAndStill>();
// Save normal values
drag = rb.drag;
angularDrag = rb.angularDrag;
gravity = rb.gravityScale;
// Disable both types of friction
dynamicFriction.enabled = false;
staticFriction.enabled = false;
}
// When entering a GroundObject
public void EnterObject() {
rb.drag = StuckDrag;
rb.angularDrag = StuckAngularDrag;
rb.gravityScale = 0;
// Enable dynamic friction to start with
TransitionToDynamicFriction();
}
// On exitting a GroundObject
public void ExitObject() {
rb.drag = drag;
rb.angularDrag = angularDrag;
rb.gravityScale = gravity;
// Disable both types of friction
dynamicFriction.enabled = false;
staticFriction.enabled = false;
}
public void TransitionToStaticFriction() {
dynamicFriction.DisableDynamicFriction();
staticFriction.EnableStaticFriction();
Debug.Log("Static");
}
public void TransitionToDynamicFriction() {
staticFriction.DisableStaticFriction();
dynamicFriction.EnableDynamicFriction();
Debug.Log("Dynamic");
}
}
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))]
public class GroundObject : MonoBehaviour {
void Start() {
Collider2D collider = GetComponent<Collider2D>();
collider.isTrigger = true;
}
// If a StickingObject collides with this, tell it
void OnTriggerEnter2D(Collider2D other) {
StickingObject obj = other.GetComponent<StickingObject>();
if (obj != null)
obj.EnterObject();
}
// If a StickingObject exits this, tell it
void OnTriggerExit2D(Collider2D other) {
StickingObject obj = other.GetComponent<StickingObject>();
if (obj != null)
obj.ExitObject();
}
}
using UnityEngine;
[RequireComponent(typeof(StickingObject))]
public class ObjectStuckButMoving : MonoBehaviour {
public float StoppedPositionalThreshold = 0.001f; // World units
public float StoppedAngularThreshold = 0.1f; // Angular degrees
public float RequiredStoppedFrames = 10;
StickingObject StickingObject;
Vector2 lastPos;
float lastRot;
bool hasStopped = false;
int framesStopped = 0;
void Start() {
StickingObject = GetComponent<StickingObject>();
lastPos = transform.position;
lastRot = transform.eulerAngles.z;
}
void Update() {
float distanceMoved = Vector2.Distance(lastPos, transform.position);
lastPos = transform.position;
float angleMoved = Mathf.DeltaAngle(transform.eulerAngles.z, lastRot);
lastRot = transform.eulerAngles.z;
if (distanceMoved <= StoppedPositionalThreshold && angleMoved <= StoppedAngularThreshold) { // Is stopped
if (hasStopped) { // Has been stopped for at least one frame now
framesStopped++;
if (framesStopped >= RequiredStoppedFrames)
StickingObject.TransitionToStaticFriction();
} else
hasStopped = true;
} else if (hasStopped) { // Was stopped, but started moving again
hasStopped = false;
framesStopped = 0;
}
}
public void EnableDynamicFriction() {
enabled = true;
hasStopped = false;
framesStopped = 0;
}
public void DisableDynamicFriction() {
enabled = false;
}
}
using UnityEngine;
[RequireComponent(typeof(StickingObject))]
public class ObjectStuckAndStill : MonoBehaviour {
public float breakingForce = 4;
public float breakingTorque = 4;
StickingObject StickingObject;
FixedJoint2D fixedJoint;
void Start() {
StickingObject = GetComponent<StickingObject>();
}
void OnJointBreak2D(Joint2D brokenJoint) {
Debug.Log("The broken joint exerted a reaction force of " + brokenJoint.reactionForce.magnitude);
Debug.Log("The broken joint exerted a reaction torque of " + brokenJoint.reactionTorque);
StickingObject.TransitionToDynamicFriction();
}
public void EnableStaticFriction() {
enabled = true;
// Find or create a Fixed Joint
fixedJoint = GetComponent<FixedJoint2D>();
if (fixedJoint == null)
fixedJoint = gameObject.AddComponent<FixedJoint2D>();
// Set the appropriate force thresholds
fixedJoint.enabled = true;
fixedJoint.breakForce = breakingForce;
fixedJoint.breakTorque = breakingTorque;
fixedJoint.dampingRatio = 1;
fixedJoint.frequency = 7;
}
public void DisableStaticFriction() {
enabled = false;
if (fixedJoint != null)
fixedJoint.enabled = false;
}
}
Testing class (Mouse offset calculation not perfect but whatever):
using UnityEngine;
public class MouseDragger : MonoBehaviour {
Transform obj;
Rigidbody2D rb;
Vector3 offset;
Vector3 lastPos;
void Update() {
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Input.GetMouseButtonDown(0)) {
if (obj == null) {
RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction);
if (hit.collider != null) {
obj = hit.transform;
rb = obj.GetComponent<Rigidbody2D>();
lastPos = ray.origin;
//offset = obj.position - lastPos;
offset = 0.2f * obj.InverseTransformPoint(hit.point);
}
}
} else if (Input.GetMouseButtonUp(0)) {
obj = null;
}
if (obj != null) {
Vector2 diff = ray.origin - lastPos;
rb.AddForceAtPosition(diff, offset);
Debug.DrawLine(offset + obj.position, ray.origin);
Debug.DrawRay(obj.position, offset, Color.magenta);
}
}
}