I want to apologize in advance for some mistakes, I used a translator.
After a month of searching for the perfect and physically correct mechanics for picking up and releasing objects, I finally found it. Initially I had a negative opinion about making an object child and kinematic, because it would not take collisions into account and could be easily thrown under the world map. At first I tried to go this way:
private void MovePickedUpObject()
{
Vector3 directionToTarget = _pickupTarget.position - PickedUpObjectRigidbody.position;
PickedUpObjectRigidbody.AddForce(directionToTarget * _forceSpeed, ForceMode.VelocityChange);
PickedUpObjectRigidbody.linearVelocity *= Mathf.Min(1.0f, Vector3.Distance(_pickupTarget.position, PickedUpObjectRigidbody.position));
}
But it did not give the right result, because when the player moves the object started to twitch and it looked bad.
I also tried the method of changing the velocity of the object:
private void MovePickedUpObject()
{
Vector3 directionToTarget = _pickupTarget.position - PickedUpObjectRigidbody.position;
PickedUpObjectRigidbody.linearVelocity = directionToTarget * _forceSpeed;
}
But the result was the same.
These were not the only ways to solve the problems, but they were the basis for many others.
After a few weeks break, I decided to come back to the project and try to make a symbiosis of what I had and what I didn’t want to do in the beginning.
After only a few hours of testing, I got the code I needed:
[SerializeField] private Camera _playerCamera;
[Space]
[SerializeField] private float _interactDistance = 3.0f;
[SerializeField] private Transform _pickupTarget;
[SerializeField] private float _forceSpeed = 25.0f;
[SerializeField] private PhysicsMaterial _zeroFrictionMaterial;
[SerializeField] private LayerMask _interactableLayer;
[SerializeField] private LayerMask _raycastExcludeLayers;
private PhysicsMaterial _originalFrictionMaterial;
public Rigidbody PickedUpObjectRigidbody { get; private set; }
private void Update()
{
if (PickedUpObjectRigidbody != null)
{
MovePickedUpObject();
}
}
private void PickupObject(InputAction.CallbackContext callbackContext)
{
if (PickedUpObjectRigidbody == null)
{
TryPickupObject();
}
else
{
ReleasePickedUpObject();
}
}
private void TryPickupObject()
{
if (Physics.Raycast(_playerCamera.transform.position, _playerCamera.transform.forward, out RaycastHit hit, _interactDistance, ~_raycastExcludeLayers))
{
Rigidbody rb = hit.collider.GetComponent<Rigidbody>();
if (rb != null)
{
PickedUpObjectRigidbody = rb;
if (PickedUpObjectRigidbody.TryGetComponent(out Collider collider))
{
_originalFrictionMaterial = collider.material;
collider.material = _zeroFrictionMaterial;
}
PickedUpObjectRigidbody.useGravity = false;
PickedUpObjectRigidbody.transform.parent = _pickupTarget;
PickedUpObjectRigidbody.interpolation = RigidbodyInterpolation.None;
PickedUpObjectRigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
PickedUpObjectRigidbody.linearVelocity = Vector3.zero;
PickedUpObjectRigidbody.excludeLayers = _interactableLayer;
PickedUpObjectRigidbody.constraints = RigidbodyConstraints.FreezeRotation;
Physics.IgnoreCollision(PickedUpObjectRigidbody.GetComponent<Collider>(), GetComponentInParent<Collider>(), true);
}
}
}
private void ReleasePickedUpObject()
{
if (PickedUpObjectRigidbody != null)
{
if (PickedUpObjectRigidbody.TryGetComponent(out Collider collider))
{
collider.material = _originalFrictionMaterial;
}
PickedUpObjectRigidbody.useGravity = true;
PickedUpObjectRigidbody.transform.parent = null;
PickedUpObjectRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
PickedUpObjectRigidbody.collisionDetectionMode = CollisionDetectionMode.Discrete;
PickedUpObjectRigidbody.excludeLayers = 0;
PickedUpObjectRigidbody.constraints = RigidbodyConstraints.None;
Physics.IgnoreCollision(PickedUpObjectRigidbody.GetComponent<Collider>(), GetComponentInParent<Collider>(), false);
PickedUpObjectRigidbody = null;
}
}
private void MovePickedUpObject()
{
Vector3 directionToTarget = _pickupTarget.position - PickedUpObjectRigidbody.position;
PickedUpObjectRigidbody.linearVelocity = directionToTarget * _forceSpeed;
}
It’s based on the most primitive pick up and release method, but with a few changes:
- I don’t make the object kinematic, as this disables collision handling;
- I disable gravity so that the object can move freely;
- in the Update() method I use a method to make the object strive for the desired position.
Using this approach we get a behavior where the object will always stay exactly at a given point, collision handling will also take place, which will prevent the object from moving outside the game world. After a collision with another object, it will aim at the desired point.
If you have any questions or suggestions on how to better optimize this code, I would be very happy to message you.