Background
I’m trying to create a 2D character out of a few sprites. Each sprite has a Rigidbody2D and a PolygonCollider2D. The head and hands are tied to the body with RelativeJoint2Ds. What I want to create is a character that moves left and right, while the head rotates so that it’s always looking at the mouse.
The problem I’m having is that when I add the code to make the head rotate to look at the mouse, it seems to break the Relative Joint’s behaviour, and as the character moves back and forth, the head slowly slides downwards until it collides with the ground.
Visuals
You can see what movement should look like in this GIF: https://imgur.com/a/tnPUGnq. This is what the movement looks like before I add the code that rotates the head to look at the mouse (The last 5 lines of the Head’s code below). Ideally, movement should continue to look like this, except that the head should constantly rotate to look at the mouse.
You can see the bug occurring in this GIF: https://imgur.com/a/8Tq13ro. This is after adding the head rotation code. You’ll notice that the code seems to work and that the rest of the character moves regularly, but the head now moves downwards slowly as the character moves left and right.
What I’ve Tried
- Changing the rotation code to affect the head’s RigidBody2D instead of the head’s transform. This instead just made the head rotate around the body as the mouse moved.
- Moving the rotation code between Update() and ForcedUpdate(). No change.
- Moving the AddForce code that moves the character from the head to the body. No change.
- Trying different 2D Joints. None of them seemed to produce the effect I wanted.
- Trying different colliders on the body and head. As long as the body is able to rotate even a bit, the bug seems to occur.
- Messing around with the Relative Joint’s properties to see if Force, Torque, or Correction might fix the problem. They didn’t.
- Freezing the body’s Z rotation in its RigidBody settings. This made the body stand perfectly upright at all times which did solve the problem, since the head remained stationary above the body. However, I don’t want the body to be perfectly upright at all times, I want it to be able to rotate, and any time it’s able to rotate, the bug occurs.
My hunch is that the problem is tied to the rotation code, the head’s Relative Joint, and/or the way the body rotates when it moves. Maybe the clamping of the body’s rotation isn’t translating properly to the head? Or maybe the rotation code can’t keep up with the head’s changing position?
Any suggestions of how to fix this? I also welcome suggestions for different ways to tie the sprites together that would avoid this problem all together.
Additional Code and Images
The head’s Components:
The body’s Components:
The head’s Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerHeadMovementController : MonoBehaviour
{
private float moveForce = 100;
private float maxSpeed = 5;
private Rigidbody2D rb2d;
// Use this for initialization
void Start()
{
//Get and store a reference to the Rigidbody2D component so that we can access it.
rb2d = GetComponent<Rigidbody2D>();
}
//FixedUpdate is called at a fixed interval and is independent of frame rate. Put physics code here.
void FixedUpdate()
{
//Store the current horizontal input in the float moveHorizontal.
float moveHorizontal = Input.GetAxis("Horizontal");
// Add force if current movement is less than maxSpeed
if (moveHorizontal * rb2d.velocity.x < maxSpeed)
{
rb2d.AddForce(Vector2.right * moveHorizontal * moveForce);
}
// Cap force if current movement is greater than maxSpeed
if (Mathf.Abs(rb2d.velocity.x) > maxSpeed)
{
rb2d.velocity = new Vector2(Mathf.Sign(rb2d.velocity.x) * maxSpeed, rb2d.velocity.y);
}
// Make the head track the mouse position
// THE PROBLEM CODE, adding this causes the bug
Vector3 mouse = Input.mousePosition;
Vector3 screenPoint = Camera.main.WorldToScreenPoint(transform.localPosition);
Vector2 offset = new Vector2(mouse.x - screenPoint.x, mouse.y - screenPoint.y);
float angle = Mathf.Atan2(offset.y, offset.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0, 0, angle);
}
}
The body’s code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerBodyMovementController : MonoBehaviour
{
private float minRot = -5;
private float maxRot = 5;
private Rigidbody2D rb2d;
// Use this for initialization
void Start()
{
//Get and store a reference to the Rigidbody2D component so that we can access it.
rb2d = GetComponent<Rigidbody2D>();
}
//FixedUpdate is called at a fixed interval and is independent of frame rate. Put physics code here.
void FixedUpdate()
{
// Clamp rotation
if (rb2d.rotation < minRot || rb2d.rotation > maxRot)
{
rb2d.rotation = Mathf.Clamp(rb2d.rotation, minRot, maxRot);
}
// If we're not trying to move, rotate back to upright
if (Input.GetAxis("Horizontal") == 0f)
{
if (rb2d.rotation > 1)
{
// Rotate upwards
rb2d.rotation = rb2d.rotation * 0.2f;
}
else
{
// Set directly to 0 degrees to save needless arithmetic
rb2d.rotation = 0;
}
}
// Kill any low velocity
if (Mathf.Abs(rb2d.velocity.x) < 0.1) rb2d.velocity = new Vector2(0, rb2d.velocity.y);
}
}


I'm going to study this to help, just thought I'd give a heads up in case you otherwise get no response for a while. Kudos on providing a complete description, code and discussion of what brought you to this point. I may have some questions, but so far as I can see you've detailed all that is required. More in a while....
– JVene