1
\$\begingroup\$

I'm building upon roll a ball tutorial in unity, and I have managed to rotate the camera around the ball just fine with the code below. But there's one problem.

In this code, when I add force to the ball, with W for example, the force will be added in the exact direction the camera is looking at the ball, meaning pushing the ball onto the ground, increasing friction and lowering speed. And if I press S the force will push the ball to the air, lowering friction and making the ball move fast.

So my question is, how can I achieve rotation without this issue?

Code for Camera (CameraController):

public GameObject Player;
private Vector3 offset;
public float cameraSpeed;
private Vector3 point;
static public Vector3 finalMovement;


void Start () {
    offset = transform.position - Player.transform.position;

    point = Player.transform.position;
    transform.LookAt(point);
}

void LateUpdate()
{
    point = Player.transform.position;

    offset = Quaternion.AngleAxis(Input.GetAxis("Mouse X") * cameraSpeed, Vector3.up) * offset;
    transform.position = point + offset;
    transform.LookAt(point);
}

Vector3 movement;
void FixedUpdate()
{
    movement = new Vector3();
    float moveHorizontal = Input.GetAxis("Horizontal");
    float moveVertical = Input.GetAxis("Vertical");
    movement = new Vector3(moveHorizontal, 0f, moveVertical);

    finalMovement = GetComponent<Camera>().transform.TransformDirection(movement);
}

Code for ball:

public float speed;
void FixedUpdate()
{
    rb.AddForce((CameraController.finalMovement) * speed);
}
\$\endgroup\$
  • \$\begingroup\$ Unrelated to your question; your variable scoping seems off- I seems you're declaring private class level variables that actually should be method level (local) variables. Either point is at method level or point is a property Vector3 point { get: return Player.transform.position; } similar movement should be declared inside the FixedUpdate() method. \$\endgroup\$ – Felsir Jul 22 '16 at 11:24
  • \$\begingroup\$ Thanks for your input, but the thing is that i'm really new to programming in general, and declaring variables as private was just testing. now I cant follow you on "{ get: return Player.transform.position; }" and i'm really intrigued to know. could you elaborate? \$\endgroup\$ – MrChips Jul 22 '16 at 12:02
  • \$\begingroup\$ There are a few other problems here. You're calculating movement in the camera's FixedUpdate, which could run after your ball uses it in FixedUpdate, introducing a frame of input latency. If the ball needs this info, why not calculate it in its script instead, so you don't have any execution order complications? Also, GetComponent<Camera>().transform is equivalent to transform alone if this script is on your camera, and throws a NullReferenceException otherwise, so probably better to use the simpler version. As for what Felsir mentioned, it's a C# Property. \$\endgroup\$ – DMGregory Jul 22 '16 at 12:42
1
\$\begingroup\$

I often like to give myself a little convenience method:

Vector3 CameraRelativeFlatten(Vector3 input, Vector3 localUp)
{
    // If this script is on your camera object, you can use this.transform instead.
    Transform cam = Camera.main.transform;

    // The first part creates a rotation looking into the ground, with
    // "up" matching the camera's look direction as closely as it can. 
    // The second part rotates this 90 degrees, so "forward" input matches 
    // the camera's look direction as closely as it can in the horizontal plane.
    Quaternion flatten = Quaternion.LookRotation(
                                        -localUp, 
                                        cam.forward
                                   )
                                    * Quaternion.Euler(Vector3.right * -90f);

    // Now we rotate our input vector into this frame of reference
    return flatten * input;
}

When I have an input in controller space, I can use this convert it to a direction in the world horizontal plane, relative to the camera:

Vector3 input = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));

Vector3 worldSpaceInput = CameraRelativeFlatten(input, Vector3.up);

If we wanted, we could also use a local surface normal instead of Vector3.up to get our input parallel to an arbitrary tilted surface, instead of always in the XZ plane.

Since we're rotating the input vector, the length is preserved, so we won't just chop off the portion of a vector that was digging into the surface, slowing our forward-back movement relative to left-right.

\$\endgroup\$
  • \$\begingroup\$ It took me longer that it should've to understand and use it, but i finally managed to use it. and it worked very well. thanks. sorry both of you for not upvoting, im currently low on rep. \$\endgroup\$ – MrChips Jul 22 '16 at 17:58
  • \$\begingroup\$ No worries, I've got enough votes/rep for my present purposes. I'm glad it's working for you! :) \$\endgroup\$ – DMGregory Jul 23 '16 at 2:34
1
\$\begingroup\$

So the problem you're having is that the Y movement is also used when applying the force. Easiest solution is to simply set the Y movement to 0 when adding the force:

rb.AddForce((new Vector3(
    CameraController.finalMovement.X,
    0,
    CameraController.finalMovement.Z).Normalize() * speed);
\$\endgroup\$
  • \$\begingroup\$ thanks, this worked very well. but just for learning purposes, is there any other way of achieving orbiting the camera? (except RotateAround) \$\endgroup\$ – MrChips Jul 22 '16 at 12:05
  • \$\begingroup\$ Note that discarding the y component this way will tend to reduce movement along the forward/backward axis. As an extreme example: if your camera was looking down at a 60-degree angle to the horizontal, you'd move at only half speed forward/back, relative to you movement left/right which doesn't get truncated in this way. \$\endgroup\$ – DMGregory Jul 22 '16 at 12:14
  • \$\begingroup\$ Since it is a direction vector- the normalized vector should then have all the info needed. \$\endgroup\$ – Felsir Jul 22 '16 at 12:42
  • \$\begingroup\$ Unfortunately it is not a direction vector. Its length carries information about the analog value of the two input axes (for instance, it could be a zero vector when the player is providing no input, or have a ramp-up as the player holds a direction key / gradually tilts an analog stick), so normalizing would discard information. \$\endgroup\$ – DMGregory Jul 22 '16 at 12:50
  • \$\begingroup\$ @DMGregory i could reach an equal speed by multiplying the x to a >1 number, but yes, that's a problem. do you have any other idea? \$\endgroup\$ – MrChips Jul 22 '16 at 12:52

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.