1
\$\begingroup\$

I'm parenting the standard assets FPS controller during play with a simple script. The controller get's parented but also instantly inherits the rotation of the parent. This doesn't happen in Editor mode. This incorrect behavior happens regardless if I move the player with a script or by parenting him with my mouse cursor.

I suspect the error comes from the way the character controller script is set up.

In the FPS controller script the player rotates via mouse movement in the Update function in the following way:

private void Update()
{
    RotateView();
}

private void RotateView()
{
    m_MouseLook.LookRotation (transform, m_Camera.transform);
}

m_MouseLook get's its rotation from m_Camera.transform. It in turn uses transform.localPosition.

I think it's the use of localposition that causes the strange behavior.

How do I fix it?

I added the script that I used to re-parent the fps controller below:

    public Transform ParentTarget;
    public GameObject player;


     void OnTriggerEnter(Collider other)
    {
        player.transform.SetParent(ParentTarget, true);
    }

     void OnTriggerExit(Collider other)
    {
        player.transform.parent = null;
    }

As I said the script is fairly simple and the same error happens when I re-parent the controller by dragging it with my mouse during play.

If I set worldPositionStays to false then the player is offset and doesn't inherit the parent rotations at all. This isn't what I want.

Here is my scene before the player steps unto the trigger. The red box is the trigger. The blue box is the new parentHere is my scene before the player steps unto the trigger. The red box is the trigger. The blue box is the new parent (the parent is rotated 45 degrees)

Here is the original incorrect behavior. The player is instantly rotated 45 degrees as he steps unto the trigger. This is wrongHere is the original incorrect behavior. The player is instantly rotated 45 degrees as he steps unto the trigger. This is wrong

Here is the behavior when worldPositionStays is set to false. Rotating the parent has no effect. This is wrong.Here is the behavior when worldPositionStays is set to false. Rotating the parent has no effect. This is wrong.

The desired behavior is to keep the player rotation, and then change it as the parent is rotating after the re-parenting event. This is what would happen if I re-parented any ordinary GameObject.

It is the character controller mouse look part that is most likely causing all the trouble.

I suspect I have to abandon the re-parenting solution and simply add the transforms to the camera mouse look part of the character controller script.

I would be very grateful is someone more knowledgeable would help me out with achieving this. Hopefully by getting re-parenting to work, since it is simpler, and simplicity is always king. I would settle for help with writing a script to add rotations and positions correctly to the controller.

\$\endgroup\$
  • 1
    \$\begingroup\$ You said "I'm parenting the standard assets FPS controller during play with a simple script." Can we see that script? Also, you suspect "the use of localPosition cauces the strange behavior". Can we see how that's being used if you thing it's the problem. \$\endgroup\$ – Foggzie Dec 19 '15 at 22:56
  • 1
    \$\begingroup\$ For starters, you never need to call GetComponet<Transform>() since it's a public property of GameObject (player.transform). Also, when changing a parent, use SetParent() because it has the optional worldPositionStays argument: player.transform.SetParent(ParentTarget, true); \$\endgroup\$ – Foggzie Dec 20 '15 at 23:47
  • \$\begingroup\$ So, to try and clarify: You want the player object to become a child of the blue thing, while maintaining the player's look vector, but moving (translating) as the parent rotates? Like standing on a merry go round and keeping your eyes fixed on a stationary object? \$\endgroup\$ – Draco18s Dec 21 '15 at 22:37
  • 1
    \$\begingroup\$ So you actually want if player trigger and get re-parented then player has to keep its original rotation and after that it follows the parent rotation respectively? \$\endgroup\$ – Hamza Hasan Dec 22 '15 at 7:40
  • 1
    \$\begingroup\$ I'm trying to understand. Why is it absolutely necessary that it be parented? I found the default FPSController script to be too limiting, and thus wrote my own. \$\endgroup\$ – Ben BearFish Dec 22 '15 at 16:02
4
+200
\$\begingroup\$

I appreciate that you'd prefer to solve your problem using parenting but the truth is you're going to be fighting Unity more than utilizing its simplicity if you attempt this.

The problem is that MouseLook (which is used by FirstPersonController) stores its own value for rotation and uses that exclusively on the Transform that it owns. This is a problem in combination with how the GameObject hierarchy handles the rotation, scaling, and transforming of parent child relationships. This hierarchical "guidance" along with FirstPersonController's use of MouseLook and its persistent rotation storage causes mathematical complications when re-parenting occurs; it's not impossible to solve but will require hacking together some stupid math.

You're better off with an alternative solution of re-implementing that parental "guidance" and then making minor modifications to the FirstPersonController and MouseLook.

For these files that already exists, I've marked which likes are old, modified, or new.


MouseLook.cs

First, in MouseLook, we need a way to modify the rotation because it stores its own and forces it upon the Transform that it's working with. We'll need an extra float for rotating on the y axis:

private Quaternion m_CharacterTargetRot; // Old
private Quaternion m_CameraTargetRot; // Old
private float m_extraY; // New

We need to allow it to modify MouseLook's internal rotation in MouseLook.LookRotation():

float yRot = CrossPlatformInputManager.GetAxis("Mouse X") * XSensitivity; // Old
float xRot = CrossPlatformInputManager.GetAxis("Mouse Y") * YSensitivity; // Old

m_CharacterTargetRot *= Quaternion.Euler (0f, yRot + m_extraY, 0f); // Modified
m_extraY = 0.0f; // New
m_CameraTargetRot *= Quaternion.Euler (-xRot, 0f, 0f); // Old

and some public access to set it. Add this function:

public void AddYRotation(float y) { m_extraY = y; } // New

FirstPersonController.cs

This simply needs a method to call the new function you just made in MouseLook:

public void AddYRotation(float y) { m_MouseLook.AddYRotation(y); } // New

Now you need a new class to handle the hierarchical "guidance" you would have gotten with parenting:

Vehicle.cs

using UnityEngine;
using UnityStandardAssets.Characters.FirstPerson;

public class Vehicle : MonoBehaviour
{
    public FirstPersonController fps;

    private Vector3 m_lastPosition;
    private Vector3 m_positionDiff;

    private float m_lastYRot;
    private float m_yRotDiff;

    void Start() {
        m_lastPosition = transform.position;
        m_lastYRot = transform.rotation.eulerAngles.y;
    }

    void Update() {

        m_positionDiff = transform.position - m_lastPosition;
        m_lastPosition = transform.position;

        m_yRotDiff = transform.rotation.eulerAngles.y - m_lastYRot;
        m_lastYRot = transform.rotation.eulerAngles.y;
    }

    void LateUpdate() {
        if (fps != null) {
            fps.transform.position = fps.transform.position + m_positionDiff;
            fps.AddYRotation (m_yRotDiff);
        }
    }
}

Wrapping this all up, after your modifications and adding the Vehicle to whatever you need (a Train, elevator, sliding platform, that blue bar in your pictures), instead of calling SetParent it will look more like this:

void OnTriggerEnter(Collider other) {
    // If these GetComponent calls return null, you're doing something wrong
    var veh = ParentTarget.GetComponent<Vehicle>();
    veh.fps = player.GetComponent<FirstPersonController>();
}
\$\endgroup\$
1
\$\begingroup\$

I think the easiest way to do this would be to update your code so that when the FpsController is added as a parent it is first updated so that its rotation matches the current position of the child object.

I think this should give the effect required but I may be misunderstanding what you are trying to do!

This should be posible with:

transform.Rotate()

http://docs.unity3d.com/400/Documentation/ScriptReference/CharacterController.SimpleMove.html

\$\endgroup\$
0
\$\begingroup\$

Here is the quick and dirty way:

 void OnTriggerEnter(Collider other)
{
    Quaternion toSubs = ParentTarget.transform.rotation / player.transform.rotation;
    player.transform.SetParent(ParentTarget, true);
    player.transform.rotation /= toSubs;
}

Since i don't understand majority of the question, i can't provide you an appropiate way.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.