- Home /
Problems with custom controller
I'm having a few issues with my character controller. While I do have movement and jumping working, as well as aiming the weapon, the problems are with character states.
So what's going on is I added a force in Update that just adds gravity so the character will fall down when they walk off the edge of a platform. I also have the CharacterState set up with Idle, Walk, Jump, and Fall. The problem with this is, with the gravity being added in Update, if I jump and hold down the jump key long enough then my character will be stuck in Fall, unable to do anything but look up and down. Another thing that happens is if I let go of Jump at the right moment, I will hit the ceiling in Fall and switch to Idle, and I'll slowly float down.
Additionally, I'm using velocity to move the character and it isn't really working as I'd want. First off, even with gravity being added at the beginning, I can't walk off of platforms and I instead just fly over the gaps. Also, since I'm moving the player along forward, I don't know how to rotate the character so forward is in the opposite direction when Left is being pressed.
Here is my code so far:
using UnityEngine;
using System.Collections;
public class PlayerController : MonoBehaviour {
#region Animations
public AnimationClip[] IdleAnimations;
public AnimationClip[] WalkAnimations;
public AnimationClip[] JumpAnimations;
public AnimationClip[] FallAnimations;
#endregion
#region Variables
public float WalkSpeed;
public float Gravity;
public float JumpForce;
bool IsGrounded;
bool HasJumped;
bool HasHitCeiling;
Vector3 Forward;
CharacterController controller;
//Object LevelMesh;
//Object Player;
RaycastHit hit;
#endregion
#region Enums
enum PlayerDirection
{
Forward = 0,
Up = 1,
Down = 2
}
enum PlayerState
{
Idle = 0,
Walking = 1,
Jumping = 2,
Falling = 3,
Dead = 4
}
PlayerDirection playerDirection;
PlayerState playerState;
#endregion
// Use this for initialization
void Start () {
Forward = transform.forward;
playerState = PlayerState.Idle;
IsGrounded = true;
//LevelMesh = GameObject.FindGameObjectWithTag ("Level");
//Player = gameObject;
}
// Update is called once per frame
void Update () {
rigidbody.AddForce (Vector3.down * Gravity);
ChangeAnimation ();
AimGun ();
print ("State: " + playerState + "; Direction: " + playerDirection);
//MOVE THE PLAYER IN FORWARD AT WALKSPEED
float h = Input.GetAxis ("Horizontal");
if (h != 0)
{
if (IsGrounded)
{
playerState = PlayerState.Walking;
}
rigidbody.velocity = Forward * WalkSpeed;
//rigidbody.AddForce(Forward * WalkSpeed);
}
else if (h == 0)
{
if (IsGrounded)
{
playerState = PlayerState.Idle;
rigidbody.AddForce (new Vector3(0,0,0));
}
}
//JUMP
if (Input.GetButtonDown ("Jump") && playerState != PlayerState.Jumping && playerState != PlayerState.Falling)
{
rigidbody.AddForce (new Vector3(0, JumpForce, 0));
//print ("You are jumping.");
playerState = PlayerState.Jumping;
IsGrounded = false;
}
if (Input.GetButtonUp ("Jump") && playerState == PlayerState.Jumping)
{
playerState = PlayerState.Falling;
}
switch (playerState)
{
case PlayerState.Falling:
//rigidbody.AddForce (new Vector3(0,-Gravity,0));
IsGrounded = false;
break;
}
//FALL WHEN CEILING IS HIT
//ALSO BE ABLE TO LOOK UP WHILE GROUNDED
//AND LOOK UP & DOWN WHILE IN AIR
}
void OnCollisionEnter (Collision col)
{
if (col.gameObject.tag == "LevelMesh")
{
if (playerState == PlayerState.Falling)
{
IsGrounded = true;
playerState = PlayerState.Idle;
}
if (playerState == PlayerState.Jumping && !IsGrounded || playerState == PlayerState.Falling && !IsGrounded)
{
playerState = PlayerState.Falling;
}
}
}
void ChangeAnimation()
{
switch (playerState)
{
case PlayerState.Idle:
if (playerDirection == PlayerDirection.Forward)
{
animation.Play (IdleAnimations[0].name);
}
if (playerDirection == PlayerDirection.Up)
{
animation.Play (IdleAnimations[1].name);
}
break;
case PlayerState.Walking:
if (playerDirection == PlayerDirection.Forward)
{
animation.Play (WalkAnimations[0].name);
}
if (playerDirection == PlayerDirection.Up)
{
animation.Play (WalkAnimations[1].name);
}
break;
case PlayerState.Jumping:
if (playerDirection == PlayerDirection.Forward)
{
animation.Play (JumpAnimations[0].name);
}
if (playerDirection == PlayerDirection.Up)
{
animation.Play (JumpAnimations[1].name);
}
if (playerDirection == PlayerDirection.Down)
{
animation.Play (JumpAnimations[2].name);
}
break;
case PlayerState.Falling:
if (playerDirection == PlayerDirection.Forward)
{
animation.Play (FallAnimations[0].name);
}
if (playerDirection == PlayerDirection.Up)
{
animation.Play (FallAnimations[1].name);
}
if (playerDirection == PlayerDirection.Down)
{
animation.Play (FallAnimations[2].name);
}
break;
}
}
void AimGun()
{
if (Input.GetButton ("AimUp"))
{
playerDirection = PlayerDirection.Up;
}
if (Input.GetButton ("AimDown") && playerState == PlayerState.Jumping || Input.GetButton ("AimDown") && playerState == PlayerState.Falling)
{
playerDirection = PlayerDirection.Down;
}
if (!Input.GetButton ("AimUp") && !Input.GetButton ("AimDown"))
{
playerDirection = PlayerDirection.Forward;
}
}
}
I switched out rigidbody.velocity for rigidbody.AddForce. But this doesn't completely solve the problem.
Answer by 0tacun · Jul 20, 2014 at 09:25 PM
First you would like to call AddForce in FixedUpdate() and handling Input in Update(). Because FixedUpdate() uses the fixedtimestep used for physic behaivour.
Second I would cast a Raycast from the player towards the ground to check if there is ground beneth us. This would kill the problem with touching the ceiling and go into idle/walk state. Also Lerp your player position to the impact to prevent that the player flys away and sticks to the ground:
if( Physics.Raycast( myTransform.position, -Vector3.up, 0.5, hit ) ) {
isGrounded = true;
myTransform.position = Vector3.Lerp( myTransform.position, hit.point, Time.deltaTime * frictionAmount );
} else {
isGrounded = false;
}
Third, if you want that the player rotates into the direction you want to move like in an action adventure (Uncharted etc.) there are already solutions: http://answers.unity3d.com/questions/8444/moving-player-relative-to-camera.html
With the raycast, I'm getting a couple errors:
"The best overloaded method match for UnityEngine.Vector3.Lerp(UnityEngine.Vector3, UnityEngine.Vector3, float)' has some invalid arguments" "Argument
#2' cannot convert UnityEngine.RaycastHit' expression to type
UnityEngine.Vector3'"
Commenting out the line with the errors, I am able to jump once before I get stuck on the ground in the Falling state.
Also getting this error on the first line of the raycast:
"Argument #4' cannot convert
UnityEngine.RaycastHit' expression to type `int'"
you have to declare the hit variable as a RaycastHit type:
RaycastHit hit;
if (Physics.Raycast(transform.position, -Vector3.up, out hit))
sorry I just wrote something of the top of my head, check the documents for the right overload function and variables. http://docs.unity3d.com/ScriptReference/Physics.Raycast.html