简体   繁体   中英

Unity3D - Preventing 3D Character from Changing Direction Mid-air

I'm a second-year student studying Video Game Programming and I've been struggling with this problem for a little while now, I was wondering if you guys could offer your best suggestion on how to fix this issue.

I have a 3D character who is able to walk, run, jump, double jump, and rotate to face the cursor. However, I noticed a sort of bug where my character is able to control its movement in mid-air.

For example, if you run by holding left Shift + W and then jump, you're able to stop moving forward and then start strafing left in mid-air.

Here is my code:

void Update ()
{
    // Turns off all animations while in the air
    if (isInAir)
    {
        animator.SetBool("Running", false);
        animator.SetBool("Walking", false);
    }

    if (Input.GetKey(KeyCode.W))
    {
        // If the character is not in the air then turn on the walk animation
        if (!isInAir)
        {
            animator.SetBool("Walking", true);
        }
        transform.Translate(Vector3.forward * movementSpeed * Time.deltaTime);
    }
    else if (Input.GetKey(KeyCode.S))
    {
        // If the character is not in the air then turn on the walk animation
        if (!isInAir)
        {
            animator.SetBool("Walking", true);
        }
        transform.Translate(Vector3.back * movementSpeed * Time.deltaTime);
    }

    if (Input.GetKey(KeyCode.A))
    {
        // If the character is not in the air then turn on the walk animation
        if (!isInAir)
        {
            animator.SetBool("Walking", true);
        }
        transform.Translate(Vector3.left * movementSpeed * Time.deltaTime);
    }
    else if (Input.GetKey(KeyCode.D))
    {
        // If the character is not in the air then turn on the walk animation
        if (!isInAir)
        {
            animator.SetBool("Walking", true);
        }
        transform.Translate(Vector3.right * movementSpeed * Time.deltaTime);
    }

    if (Input.GetKey(KeyCode.LeftShift))
    {
        // If the character is not in the air then turn on the run animation 
        //  and change the character's movementSpeed
        if (!isInAir)
        {
            movementSpeed = runSpeed;
            animator.SetBool("Running", true);
        }
    }
    else
    {
        // If the character is not in the air then reset their movement speed
        //  and turn off the run animation.
        if (!isInAir)
        {
            movementSpeed = walkSpeed;
            animator.SetBool("Running", false);
        }
    }

    // When a key is released, turn off the movement animations
    // This does not cause an issue since each movement if statement turns the animation back on
    if (Input.GetKeyUp(KeyCode.W) || Input.GetKeyUp(KeyCode.S) || Input.GetKeyUp(KeyCode.A) || Input.GetKeyUp(KeyCode.D))
    {
        animator.SetBool("Walking", false);
        animator.SetBool("Running", false);
    }
    #endregion

    // Jumping
    if (Input.GetButtonDown("Jump") && jumpCounter != 2)
    {
        jumpCounter++;
        isInAir = true;
        myRigidbody.AddForce(new Vector3(0, jumpForce));
        if (jumpCounter == 1)
        {
            firstJumpStamp = Time.time;
        }
    }

    if(Physics.Raycast(groundCheckRay, groundCheckRayDistance) && Time.time > firstJumpStamp + 0.5f)
    {
        jumpCounter = 0;
        isInAir = false;
    }
}

I have removed any code that is not relating to the character movement.

Is anybody able to give me a suggestion on a method I'd be able to use to make this work?

I'm not asking for code to fix this problem , just a suggestion...

I feel like I need to learn this on my own, I just need somebody to point me in the right direction.

If you do not understand part of the code, feel free to ask me and I'll gladly show you what I'm attempting to do with it. :) Maybe you know a better way to write certain lines for example.

(I do try to avoid using the Input.GetAxis though because I find it harder to make movement when I don't have 100% control over it)

I can suggest you re-evaluate your requirements and compare that to what you've written. There are slight differences between what you've described and what you've done in the code. First of which is that you want that character to be able to move either left or right and not these two at the same time, same with forward and backward.

To help with this you can use Input.GetAxis instead of Input.GetKey which you're using right now.


A second thing I can suggest is to use local Vector3 to calculate the force and then apply this to your character. You can then, based on the Vector3 value, set your animator variables.


Your main problem consists of the movement technique used. Since you've decided to use Transform.Translate instead of using Rigidbody to move the character, you have to calculate position delta over frames.
Assume your character will start at a position [ x:1.00f, y: 0.00f, z: 1.00f ] and move in the direction [ x: 0.30f, y: 0.00f, z: 0.70f ] on the frame 1 and it decided to jump. What you have to do then is to grab the previous position ( the one before frame changed ) which would be [ x:1.00f, y: 0.00f, z: 1.00f ] and subtract it from your current position which is [ x:1.30f, y: 0.00f, z: 1.70f ] . This will return a vector equals to the previous position delta ( [ x: 0.30f, y: 0.00f, z: 0.70f ] ). Now just simply add this position to the current position and it should remain the same course ( direction ).
This does not include friction so your character will move with the same speed in the same direction all the time when in mid air.

even though you haven't asked for the code, it's much easier to describe it in it then in plain English ( at least for me :) ) so, here's an example code:

// value indicating what was the position before change
Vector3 previousPosition;

void Update ()
{
    // get user input as a vector
    Vector3 horizontal = Vector3.right * Input.GetAxis("horizontal");
    Vector3 vertical = Vector3.forward * Input.GetAxis("vertical");

    bool isRunning = Input.GetKey(KeyCode.LeftShift);
    float speedModifier = isRunning ? runSpeed : walkSpeed;

    // add these inputs together
    Vector3 currentVelocity = (horizontal + vertical);

    // check if player is in mid air
    if (IsInAir)
    {
        // if it is, get the previous and current positions
        // add calculate the distance between frames
        // and normalize it to get the movement direction
        Vector3 currentPosition = transfor.position;
        Vector3 direction = (currentPosition - previousPosition).Normalize();
        // apply movement direction to the "currentVelocity"
        currentVelocity = direction * speedModifier * Time.deltaTime;
    }
    else
    {
        // user is not in mid air so you can just calculate
        // the position change
        currentVelocity = currentVelocity.Normalize() * speedModifier * Time.deltaTime;

        if ( currentVelocity != Vector3.zero )
        {
            animator.SetBool("walking", !isRunning);
            animator.SetBool("running", isRunning);
        }
    }

    // current position ( before changes )
    // should be copied to a previous position
    // to calculate it in the next frame update
    previousPosition = currentPosition;

    // do the translation
    transform.Translate(currentVelocity);
}

additional info: I would still strongly recommend you to switch from Transform.Translate to Rigidbody and just "re-use" it's velocity when character is in mid air. This will not require from you to calculate position delta each frame, making it much more easier to use.

I think what you need is to move the transform into the if . because now you are moving the object either way, just not updating the bool.
for example:

if (Input.GetKey(KeyCode.W))
    {
        // If the character is not in the air then turn on the walk animation
        if (!isInAir)
        {
            animator.SetBool("Walking", true);
            transform.Translate(Vector3.forward * movementSpeed * Time.deltaTime); //<-- New line
        }
        //transform.Translate(Vector3.forward * movementSpeed * Time.deltaTime); //<-- Removed line
    }

You will need to do this for all movements if s

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM