Proper item carry by player respecting physics (collisions)

Hi guys,
I’m trying to figure out how to properly carry items by player while respecting collisions between the item and obstacles.
Approaches I tried:

  • Update method approach - the movement is pretty smooth, no jittering but of course transform is often out of sync of physics, therefore it’s possible to push an object fast throught a wall or worse ground. Tried Physics.SyncTransforms() as well.
  • Fixed update method - the movement jitters considerably (I would need to significantly decrease fixed update time and it won’t completely fix the issue).
  • Spring joint or fixed joint method - the same issue as above - physics is automatically updated in fixed interval set in fixed update, so any joint jitters significantly when moved by player.
    Also I’m afraid fixed update won’t help the situation when player carries an item an makes very fast movement (rotation) agains a wall. The collider will go through the wall despite the rigidbody is set to continuous dynamic.
    Any ideas, tips, how to achieve this? I really cannot figure this out myself.
    One idea was to force item drop when delta position of the carried item between last 2 frames exceeds some limit. Doesn’t work well, too bad experience for player.
    I can provide more details or test scene or video if needed.

I assume you’re only needing this for large objects like crates and boxes but not small single handed objects. So what I have done in the past is use an empty child object in front of the character and have held objects chase that empty. If the object fails to remain within a set distance of the empty then it is simply dropped. This can be caused by collisions or the player moving too fast. Hope this helps.

You are right. I now tuned it in the way of a good compromise. Movement smoothness is acceptable.
Sharing the code for those interested / struggling with this as well.

Related video here:


using UnityEngine;

public class ItemCarrying1 : MonoBehaviour
{
    [SerializeField] private float _grabReach = 5f;
    [SerializeField] private float _followSpeed = 10f;
    [SerializeField] private float _carriedItemAngularDrag = 10f;
    [SerializeField] private Transform _camera;
    [SerializeField] private LayerMask _layerMask;

    private bool _carrying;
    private Rigidbody _carriedRigidbody;
    private Transform _grabHolder;
    private RigidbodyValues _originalRigidbodyValues;

    private void Awake()
    {
        _grabHolder = new GameObject().transform;
        _grabHolder.SetParent(_camera);
    }

    private void Update()
    {
        Physics.SyncTransforms();
        if (Input.GetMouseButtonDown(0) && Physics.Raycast(_camera.position, _camera.forward, out RaycastHit hit, _grabReach, _layerMask))
        {
            _carriedRigidbody = hit.collider.GetComponent<Rigidbody>();
            if (_carriedRigidbody)
            {
                _grabHolder.position = _carriedRigidbody.transform.position;
                _carrying = true;
                StoreOriginalRigidbodyValues();
                SetCarryRigidbodyValues();
                //_carriedRigidbody.transform.SetParent(_grabHolder);
            }
        }
        else if (Input.GetMouseButtonUp(0) && _carrying)
        {
            Drop();
        }

        if (_carrying)
        {
            _carriedRigidbody.velocity = (_grabHolder.position - _carriedRigidbody.transform.position) * _followSpeed;
            if (PushesItemThroughObstacleTooHard())
            {
                Drop();
            }
        }
    }

    private bool PushesItemThroughObstacleTooHard()
    {
        return Vector3.Distance(_carriedRigidbody.transform.position, _grabHolder.position) > 3f;
    }

    private void Drop()
    {
        _carrying = false;
        RestoreOriginalRigidbodyValues();
        _carriedRigidbody.transform.SetParent(null);
        _carriedRigidbody = null;
    }

    private void SetCarryRigidbodyValues()
    {
        _carriedRigidbody.angularDrag = _carriedItemAngularDrag;
        _carriedRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
        _carriedRigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
        _carriedRigidbody.useGravity = false;
    }

    private void StoreOriginalRigidbodyValues()
    {
        _originalRigidbodyValues.angularDrag = _carriedRigidbody.angularDrag;
        _originalRigidbodyValues.interpolation = _carriedRigidbody.interpolation;
        _originalRigidbodyValues.collisionDetectionMode = _carriedRigidbody.collisionDetectionMode;
        _originalRigidbodyValues.useGravity = _carriedRigidbody.useGravity;
    }

    private void RestoreOriginalRigidbodyValues()
    {
        _carriedRigidbody.angularDrag = _originalRigidbodyValues.angularDrag;
        _carriedRigidbody.interpolation = _originalRigidbodyValues.interpolation;
        _carriedRigidbody.collisionDetectionMode = _originalRigidbodyValues.collisionDetectionMode;
        _carriedRigidbody.useGravity = _originalRigidbodyValues.useGravity;
    }

    private struct RigidbodyValues
    {
        internal RigidbodyInterpolation interpolation;
        internal float angularDrag;
        internal bool useGravity;
        internal CollisionDetectionMode collisionDetectionMode;
    }
}