Search Unity

Diagonal Character Movement FPS

Discussion in 'Scripting' started by Iron-Warrior, Feb 7, 2010.

  1. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    838
    Howdy Unity,

    Big semester break coming up due to the Olympics, so I decided to get back to Unity3d-ing. Building a simple FPS, got most of the basic stuff down but I seem to have encountered a problem...when you move diagonally you actually move faster than just horizontally or vertically. This is because you have two Vectors that are equal (lets say they're equal to one) so on a triangle, the edges would be 1, 1 and root 2 (1.41)...meaning the character moves 1 and a half times as fast when he's moving diagonally.

    This seems to be a pretty broad problem and I'm guess someone else must have enountered it, but it's actually fairly tough to fix. I want my character to have a sense of 'weight', so I have a small amount of time required to accelerate and decellerate...he's what I had before noticing this problem.

    Code (csharp):
    1. var speed = 6.0;
    2. var acceleration = 1.0;
    3. var decceleration = 0.9;
    4. var gravity = 20.0;
    5.  
    6. private var moveDirection = Vector3.zero;
    7. private var grounded : boolean = false;
    8. private var currentSpeedX = 0.0;
    9. private var currentSpeedZ = 0.0;
    10.  
    11. function FixedUpdate () {
    12.     if (grounded) {
    13.         currentSpeedX += Input.GetAxis("Horizontal") * acceleration;
    14.         currentSpeedZ += Input.GetAxis("Vertical") * acceleration;
    15.         if (Input.GetAxis("Vertical") == 0)
    16.         {
    17.             currentSpeedZ = currentSpeedZ * decceleration;
    18.         }
    19.         if (Input.GetAxis("Horizontal") == 0)
    20.         {
    21.             currentSpeedX = currentSpeedX *decceleration;
    22.         }
    23.         currentSpeedZ = Mathf.Clamp(currentSpeedZ, -speed, speed);
    24.         currentSpeedX = Mathf.Clamp(currentSpeedX, -speed, speed);
    25.         moveDirection = new Vector3(currentSpeedX, 0, currentSpeedZ);
    26.         moveDirection = transform.TransformDirection(moveDirection);
    27.        
    28.  
    29.     }
    30.    
    31.     moveDirection.y -= gravity * Time.deltaTime;
    32.  
    33.     var controller : CharacterController = GetComponent(CharacterController);
    34.     var flags = controller.Move(moveDirection * Time.deltaTime);
    35.     grounded = (flags  CollisionFlags.CollidedBelow) != 0;
    36. }
    It's pretty similar to the FPS mover from the 3 part tutorial.

    Anyways, I can't figure out for the life of me how to make it work. I want the character to be able to move equal speeds in all directions, but the problem arises when I'm trying to make decceleration. I tried just using one speed value (rather than X and Z speeds) and just point the vector in the right direction, but it doesn't quite work...

    For example: if you're moving up-right and maximum speed, then let go of the up key and press down, it should only slow down your upwards movement...however, if you're only using one speed variable, this isn't possible.

    Anyways, long story short is that this was bugging me, and someone must have had this problem before (although I vaguely remember in GoldenEye 64 moving diagonally was faster than horz or vert...)
     
  2. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    That reminds me, I totally meant to add a "limit diagonal speed" setting to FPSWalkerEnhanced and totally forgot, so I just added it now. Basically I just multiply the movement rate by .7072 if both horizontal and vertical axes are used at the same time.

    By the way, the input manager already has settings for acceleration/deceleration of input, so you might be better off using those and not adding custom code to do the same thing.

    --Eric
     
  3. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    838
    Works great Eric, thanks very much!

    Just out of curiousity, where did you get the number 0.7072 from? I'm guessing it's root 2 divided by or something, but it would be handy to know for sure.
     
  4. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    1/sqrt(2). Although that's .7071; not sure where I got the extra .0001 from. Obviously that throws everything way, way off and would make anything that uses it completely unplayable, so I'll go fix it now. ;)

    --Eric
     
  5. Tinus

    Tinus

    Joined:
    Apr 6, 2009
    Posts:
    437
    Good call on the diagonal movement! I've just implemented your solution in my own code, thanks.

    Regarding jumpy movement on slopes: I've just prototyped a method in which the direction of translation applied for movement is always perpendicular to the slope normal, and this seems to completely solve the problem.

    Here's a snippet:
    Code (csharp):
    1.  
    2. /* Construct the target velocity so that it is aligned with the current surface normal this prevents bumping up and down slopes. */
    3. var localNormal : Vector3 = transform.InverseTransformDirection(groundSurfaceNormal);
    4. var alignedForward  : Vector3 = Vector3.Cross(Vector3.right, localNormal);
    5. var alignedHorizontal  : Vector3 = Vector3.Cross(-Vector3.forward, localNormal);
    6. var alignedTargetVelocity : Vector3 = (alignedForward * inputZ * diagonalFactor) + (alignedHorizontal * inputX * diagonalFactor);
    7.  
    I'm still in the process of optimising the full code, and I'll post it later. :)
     
  6. Ferdil

    Ferdil

    Joined:
    Jun 5, 2010
    Posts:
    7
    No, no, no, the 0.7072 solution is way rough, it does not work well with smoothed input (the one you basically get from the Unity Input Manager), since, cause of the smoothing, you do not always move in 45° directions.

    I have used my "m4th skillz" (JK :roll:) to develop a simple and code-efficient way that works well with smoothed input.

    Code (csharp):
    1.  
    2. static function Sphericize(v : Vector3)
    3. {      
    4.     // For readability
    5.     var x = Mathf.Abs(v.x);
    6.     var y = Mathf.Abs(v.y);
    7.     var z = Mathf.Abs(v.z);
    8.        
    9.     // Gives the maximum of the absolute value of the three coordinates
    10.     var mag = Mathf.Max(x, Mathf.Max(y,z));
    11.        
    12.     // Returns the vector with the set magnitude
    13.     return v.normalized * mag;
    14. }
    15.  
    Basically, it accepts a Vector3 and returns a Vector3 of the same direction with magnitude equal to the maximum of the absolute value of the three coordinates.

    You don't wanna use this witk joystick input, just with keyboard input and digital input.

    I recommend just clamping the magnitude between 0 and 1 in case of a joystick input.

    If you wanna know the math behind this, just ask; i warn you that it requires some drawings.
     
    marcospgp and Stardog like this.
  7. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    I'm going to have to be argumentative here and disagree, because, realistically speaking when using keyboard control, 99.99% of the time you either have 0% or 100% input even with the smoothing. The more correct solution is unnoticeable in practice, so IMO not worth bothering with in this case.

    --Eric
     
  8. Ferdil

    Ferdil

    Joined:
    Jun 5, 2010
    Posts:
    7
    1. There is no cost in implementing my solution instead of the another (it could also be faster, because it's simpler)

    2. It is more accurate.

    3. With smoothed input you stay pretty much all time pressing and relasing buttons, expecially when you pass from pressing UP to pressing UP+LEFT/UP+RIGHT and similar, the difference becomes noticeable.
    So it is not true that 99,99% of the time you stay on 0 or 1 values.


    Anyway, why bashing that way a solution that's simpler, more elegant, and truly accurate?
     
  9. PremiereBoris

    PremiereBoris

    Joined:
    Jun 26, 2013
    Posts:
    1
    >>>THREAD UPDATE 2013<<<
    I know this is an old thread, but this is the one that shows up on Google first if you are looking how to fix diagonal movement speed, so let me amend this:

    There is a Vector3.ClamMagnitude function that solves this problem beautifully.
     
    marcospgp likes this.
  10. RainManHorton

    RainManHorton

    Joined:
    Oct 24, 2012
    Posts:
    2
    Cloud this method be used to solve my problem....fps camera looks faster in corners
     
  11. RainManHorton

    RainManHorton

    Joined:
    Oct 24, 2012
    Posts:
    2
    I am building a fps game. my problem is almost the same thing just with rotational speeds instead.
    I want to rotate the view at the same speed in all directions (to get rid of the axial feel when looking around)

    <<in the corners of the joystick each axis reads 1.0x or 1.0y not 7.5x 7.5y or whatever it should be >>

    creating a vector3 and clamping the magnitude works good for movement but kills accuracy in a joystick look setup
    what math can I use to accurately determine the physical location of the joystick