In-Air Character Controller Help

I am trying to put together a 3D platforming controller, but it’s not working out. There was the inability to change direction while in the air, and after some tinkering, I got as close as I could to do that, but the player hardly jumps. I feel I have all the right pieces, I just don’t know how to put them together. The script is very easy to use, just copy and past and put it on a cube to test it. Any help would be grateful!

var moveSpeed : float = 18.0;
var rotateSpeed : float = 200.0;
var jumpHeight : float = 15.0;
var gravity : float = 30.0;
var doubleHeight : float = 4.0;

private var moveDirection : Vector3 = Vector3.zero;
private var controller : CharacterController;
private var canJump = true;
private var canDoubleJump = true;
private var rotation : Vector3;
private var canMove = true;

function Awake(){
	controller = GetComponent(CharacterController);
}

function Update() {
    if(controller.velocity.sqrMagnitude < 0.1){
    	//animation.CrossFade("IdleAni");
    }
	    forward = Camera.main.transform.TransformDirection(Vector3.forward);
	    forward.y = 0;
	    forward = forward.normalized;
	    right = Vector3(forward.z,0,-forward.x);
	    h = Input.GetAxis("Horizontal");
	    v = Input.GetAxis("Vertical");

		moveDirection = (h * right + v * forward);
	if(controller.isGrounded){	
		moveDirection *= moveSpeed;
		canDoubleJump = true;	
		if(Input.GetButton("Jump")  canJump){	//JUMPING INPUT
	    	Jump();
		}			
	}	
	if(!controller.isGrounded){
		moveDirection *= moveSpeed;
		if(canDoubleJump  canJump  Input.GetButtonDown("Jump")){
			DoubleJump();
		}
	}
	if(moveDirection != Vector3.zero){
		var rotation0 = transform.rotation;
		rotation0.SetLookRotation(new Vector3(moveDirection.x,0,moveDirection.z) * rotateSpeed);
		transform.localRotation = rotation0;	
	}
	if(canMove){
		//animation.CrossFade("RunAni");
		moveDirection.y -= gravity * Time.deltaTime;
		controller.Move(moveDirection * Time.deltaTime);
	}
}
@script RequireComponent(CharacterController);

Any help at all…? :frowning:

You haven’t shown us your Jump() function!!! I suspect your gravity is overpowering your jump speed.

I am so sorry. Here ya go!:

function Jump(){
     moveDirection.y = jumpHeight;
}

Ok, I fixed the jumping part, he can jump now, but it’s the in-air part that I need help with. Is there any way I can change direction while in the air like any other 3D platformers, i.e. Super Mario, Sly Cooper, Jack Daxter,etc.

#pragma strict
#pragma implicit 
#pragma downcast
var moveSpeed : float = 18.0;
var rotateSpeed : float = 200.0;
var jumpHeight : float = 15.0;
var gravity : float = 30.0;
var doubleHeight : float = 4.0;

private var moveDirection : Vector3 = Vector3.zero;
private var controller : CharacterController;
private var canJump = true;
private var canDoubleJump = true;
private var rotation : Vector3;
function Awake(){
	controller = GetComponent(CharacterController);
}

function Update() {
    if(controller.velocity.sqrMagnitude < 0.1){
    	//animation.CrossFade("IdleAni");
    }
	if(controller.isGrounded){
	    forward = Camera.main.transform.TransformDirection(Vector3.forward);
	    forward.y = 0;
	    forward = forward.normalized;
	    right = Vector3(forward.z,0,-forward.x);
	    h = Input.GetAxis("Horizontal");
	    v = Input.GetAxis("Vertical");
	    
		moveDirection = (h * right + v * forward);	
		moveDirection *= moveSpeed;
		canDoubleJump = true;	
		if(Input.GetButton("Jump")  canJump){	//JUMPING INPUT
	    	Jump();
		}			
	}	
	if(!controller.isGrounded){
		if(canDoubleJump  canJump  Input.GetButtonDown("Jump")){
			DoubleJump();
		}
	}
	if(moveDirection != Vector3.zero){
		var rotation0 = transform.rotation;
		rotation0.SetLookRotation(new Vector3(moveDirection.x,0,moveDirection.z) * rotateSpeed);
		transform.localRotation = rotation0;	
	}
	if(canMove){
		//animation.CrossFade("RunAni");
		moveDirection.y -= gravity * Time.deltaTime;
		controller.Move(moveDirection * Time.deltaTime);
	}
}

function Jump(){
	moveDirection.y = jumpHeight;
}

function DoubleJump(){
 	moveDirection.y = jumpHeight + doubleHeight;
 	canDoubleJump = false;
}

@script RequireComponent(CharacterController);

for starters, moveDirection only gets updated if the character is “grounded”, so moving in air is very hard because of that.

So would

 moveDirection = (h * right + v * forward);

OR

 moveDirection *= moveSpeed;

needed to be moved outside all isGrounded statements?

Add both of those lines to the if(!isGrounded) statement and you should be able to move either way.

I’m working on a similar issue, but my problem is the reverse. I can already move in the air, but my solution for stopping the character when there is no input also kills your movement in the air, which I don’t want.

I tried both solutions, neither one worked. The player hardly jumps 1 in off the ground. If you don’t mind, I’d like to see your character script…I don’t mind if you use mine posted at the very top. :slight_smile:

Here’s my code. Note that I’m not using the provided Character Controller, but relying on physics. Maybe why mine works for me? Also, I’m writing in C#.

void Movement(float x, float y, float z)
	{
		movement = ((x * forward) + (z * right));
		rigidbody.AddForce(movement * ballSpeed * Time.deltaTime);

		if(movement.sqrMagnitude < 0.1f)
		{
			StartCoroutine(Delay ());
			rigidbody.velocity = Vector3.MoveTowards (rigidbody.velocity, (Vector3.zero), ((Time.time - Time.deltaTime) / (ballSpeed / 2f)));
		}

		else
			StopCoroutine("Delay");
	}

And then…

void FixedUpdate()
	{
		
		movementHorizontal = Input.GetAxis("Vertical");
		movementVertical = Input.GetAxis("Horizontal");
		
		
		if(isGrounded == true  StickyWall.canJump == false)
		{
			Movement (movementHorizontal, 0f, movementVertical);
		}
		
		else if(isGrounded == false  StickyWall.canJump == false)
		{	
			Movement (.5f, 0f, 0f);
		}
		
		else if(StickyWall.canJump == true  isGrounded == true)
		{
			Movement (movementHorizontal, movementVertical, 0f);
		}
	}

Yeah, that didn’t help much since you’re using physics AND C#. :stuck_out_tongue:

Yeah, I figured as much. BUT, I do think I see the problem. You’re only assigning values for moveDirection in the isGrounded if statement. Change your code to look like below and see if it fixes it.

function Update() {

    if(controller.velocity.sqrMagnitude < 0.1){

        //animation.CrossFade("IdleAni");

    }

        forward = Camera.main.transform.TransformDirection(Vector3.forward);

        forward.y = 0;

        forward = forward.normalized;

        right = Vector3(forward.z,0,-forward.x);

        h = Input.GetAxis("Horizontal");

        v = Input.GetAxis("Vertical");

        

        moveDirection = (h * right + v * forward);  

        moveDirection *= moveSpeed;

        canDoubleJump = true;   

 if(controller.isGrounded){

        if(Input.GetButton("Jump")  canJump){ //JUMPING INPUT

            Jump();

        }           

    }   

    if(!controller.isGrounded){

        if(canDoubleJump  canJump  Input.GetButtonDown("Jump")){

            DoubleJump();

        }

The other problem you have is that of inconsistent jump behaviour which is why you are seeing the jumping change when you try to add mid-air direction changes.

Notice how in your original code you were resetting the Y component of move direction in each update but only if you were grounded. Therefore, as soon as your character launched into the air, that first branch of code was never being executed and your moveDirection.y component not being reset. Therefore, when you were in the air you were allowing the jump height to naturally diminish over time via gravity.

However, when you modified your code so that the calculation of moveDirection happened for both in-air and grounded cases, the Y component of moveDirection was always being snapped immediately back to zero again on the next update. You simply need to NOT reset the Y component moveDirection each time. Something like this should do the trick and give you proper Jump behaviour with Mid-Air direction changes.

var moveSpeed : float = 18.0;
var rotateSpeed : float = 200.0;
var jumpHeight : float = 15.0;
var gravity : float = 30.0;
var doubleHeight : float = 4.0;

private var moveDirection : Vector3 = Vector3.zero;
private var controller : CharacterController;
private var canJump = true;
private var canMove = true;
private var canDoubleJump = true;
private var rotation : Vector3;

function Awake()
{
    controller = GetComponent(CharacterController);
}

function Update() 
{
        if(controller.velocity.sqrMagnitude < 0.1){
       //animation.CrossFade("IdleAni");
       }

        forward = Camera.main.transform.TransformDirection(Vector3.forward);
        forward.y = 0;
        forward = forward.normalized;

        right = Vector3(forward.z,0,-forward.x);
        h = Input.GetAxis("Horizontal");
        v = Input.GetAxis("Vertical");

        // MAINTAIN Y OF MOVE DIRECTION
        var XZMoveDirection:Vector3 = (h * right + v * forward);  
        XZMoveDirection *= moveSpeed;
        moveDirection.x = XZMoveDirection.x;
        moveDirection.z = XZMoveDirection.z;
        canDoubleJump = true;   
 
         if(controller.isGrounded)
         {
                if(Input.GetButton("Jump")  canJump)    Jump();
          }   

       if(!controller.isGrounded){
        if(canDoubleJump  canJump  Input.GetButtonDown("Jump")){
            DoubleJump();
        }
    }

    if(moveDirection != Vector3.zero){
        var rotation0 = transform.rotation;
        rotation0.SetLookRotation(new Vector3(moveDirection.x,0,moveDirection.z) * rotateSpeed);
        transform.localRotation = rotation0;    
    }

    if(canMove)
    {
        moveDirection.y -= gravity * Time.deltaTime;
        controller.Move(moveDirection * Time.deltaTime);
    }
}

function Jump(){
    moveDirection.y = jumpHeight;
}

function DoubleJump(){
    moveDirection.y = jumpHeight + doubleHeight;
    canDoubleJump = false;
}

@script RequireComponent(CharacterController);

That’s exactly what I needed. I’ve been struggling with this since July, so much thanks.

Also, now that I can do this, if no buttons are pressed with in air, or if the player stands still and jumps, the player will snap forwards(or towards the z-axis). I’ve also been struggling with this for a while. I believe the problem is here:

	if(moveDirection != Vector3.zero){
		var rotation0 = transform.rotation;
		rotation0.SetLookRotation(new Vector3(moveDirection.x,0,moveDirection.z) * rotateSpeed);
		transform.localRotation = rotation0;	
	}

Perhaps I should write

 if(moveDirection != Vector3.zero  controller.velocity.sqrMagnitude < 0.1){
     		var rotation0 = transform.rotation;
		rotation0.SetLookRotation(new Vector3(moveDirection.x,0,moveDirection.z) * rotateSpeed);
		transform.localRotation = rotation0;
}

It’s the code inside that’ll give me errors, because there are already variables for it.
And I appreciate the help! :slight_smile:

I fixed it by replacing (moveDirection != Vector3.zero) with (XZMoveDirection != Vector3.zero).
Here is the full movement of my script for a 3D Platformer:

var moveSpeed : float = 18.0;

var rotateSpeed : float = 200.0;

var jumpHeight : float = 15.0;

var gravity : float = 30.0;

var doubleHeight : float = 4.0;

 

private var moveDirection : Vector3 = Vector3.zero;

private var controller : CharacterController;

private var canJump = true;

private var canMove = true;

private var canDoubleJump = true;

private var rotation : Vector3;

 

function Awake()

{

    controller = GetComponent(CharacterController);

}

 

function Update() 

{

        if(controller.velocity.sqrMagnitude < 0.1){

       //animation.CrossFade("IdleAni");

       }

 

        forward = Camera.main.transform.TransformDirection(Vector3.forward);

        forward.y = 0;

        forward = forward.normalized;

 

        right = Vector3(forward.z,0,-forward.x);

        h = Input.GetAxis("Horizontal");

        v = Input.GetAxis("Vertical");

 

        // MAINTAIN Y OF MOVE DIRECTION

        var XZMoveDirection:Vector3 = (h * right + v * forward);  

        XZMoveDirection *= moveSpeed;

        moveDirection.x = XZMoveDirection.x;

        moveDirection.z = XZMoveDirection.z;

        canDoubleJump = true;   

 

         if(controller.isGrounded)

         {

                if(Input.GetButton("Jump")  canJump){
                        Jump();

          }   

 

       if(!controller.isGrounded){

        if(canDoubleJump  canJump  Input.GetButtonDown("Jump")){

            DoubleJump();

        }

    }

 

    if(XZMoveDirection != Vector3.zero){

        var rotation0 = transform.rotation;

        rotation0.SetLookRotation(new Vector3(moveDirection.x,0,moveDirection.z) * rotateSpeed);

        transform.localRotation = rotation0;    

    }

 

    if(canMove)

    {

        moveDirection.y -= gravity * Time.deltaTime;

        controller.Move(moveDirection * Time.deltaTime);

    }

}

 

function Jump(){

    moveDirection.y = jumpHeight;

}

 

function DoubleJump(){

    moveDirection.y = jumpHeight + doubleHeight;

    canDoubleJump = false;

}

 

@script RequireComponent(CharacterController);

Thank you all for your help! :smile:

Yes you would need to use XZMovement for that test now.

Thank you. Now, I need to learn how to apply climbing to my script. Anyone know where I can start?