So I’ve made a custom character controller that I’ve used in a few projects. The first time I made it, it seems to work fine in that project.
However now, once I set it up and use just the parts I need, It seems like I can’t move at all until I click out of the game window/debug player onto an object in the scene hierarchy, then click back in, and suddenly i can move.
Currently, If I crouch first, then movement starts working.
Other times it does just work fine, but another run it might break again. I can’t pin-point why this is happening.
I’ve been trying to debug but it seems like all values are how they should be, such as my ground check and state management. The player object is definitely above any ground colliders too.

My script is quite long, so I’ve ommited global variables. Just assume any that aren’t declared in the code exist.
...
public class PlayerController : MonoBehaviour
{
//fields/variables declared here
private void Awake()
{
playerInput = GetComponent<PlayerInput>();
moveAction = playerInput.actions.FindAction("Move");
lookAction = playerInput.actions.FindAction("Look");
jumpAction = playerInput.actions.FindAction("Jump");
sprintAction = playerInput.actions.FindAction("Sprint");
crouchAction = playerInput.actions.FindAction("Crouch");
grabAction = playerInput.actions.FindAction("Grab");
shiftTimeAction = playerInput.actions.FindAction("ShiftTime");
rb = GetComponent<Rigidbody>();
playerCollider = playerObject.GetComponent<CapsuleCollider>();
playerAudioSource = GetComponent<AudioSource>();
rb.isKinematic = true;
}
private void OnEnable()
{
moveAction.Enable();
lookAction.Enable();
jumpAction.started += Jump;
sprintAction.started += Sprint;
sprintAction.canceled += EndSprint;
crouchAction.started += Crouch;
crouchAction.canceled += Standup;
grabAction.started += StartGrab;
grabAction.canceled += EndGrab;
shiftTimeAction.started += ShiftTime;
}
private void OnDisable()
{
moveAction.Disable();
lookAction.Disable();
jumpAction.started -= Jump;
sprintAction.started -= Sprint;
sprintAction.canceled -= EndSprint;
crouchAction.started -= Crouch;
crouchAction.canceled -= Standup;
grabAction.started -= StartGrab;
grabAction.canceled -= EndGrab;
shiftTimeAction.started -= ShiftTime;
}
private void Start()
{
readyToJump = true;
startYScale = playerObject.localScale.y;
camStartPos = playerCamera.transform.position;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
private void Update()
{
if(!isFrozen)
{
MoveCamera();
SpeedControl();
StateHandler();
stepTimer += Time.deltaTime;
}
}
private void FixedUpdate()
{
if (!isFrozen)
{
// ground check
RaycastHit groundHit;
grounded = Physics.Raycast(transform.position, Vector3.down, out groundHit, playerHeight + 0.5f, whatIsGround);
if (state != MovementState.air && !isInWater)
{
currentPhysicsMaterial = groundHit.collider != null ? groundHit.collider.sharedMaterial : null;
}
else
{
currentPhysicsMaterial = null;
}
// handle drag
if (grounded)
rb.linearDamping = groundDrag;
else
rb.linearDamping = 0;
MovePlayer();
if (enableFoosteps)
{
CalculateFootsteps();
}
if (isGrabbing)
{
currentObjectVelocity = currentObject.linearVelocity;
}
}
}
private void MoveCamera()
{
if (!isFrozen)
{
Vector3 oldCamRotation = playerCamera.transform.rotation.eulerAngles;
lookInput = lookAction.ReadValue<Vector2>();
float mouseX = lookInput.x / 10 * sensX;
float mouseY = lookInput.y / 10 * sensY;
yRotation += mouseX;
xRotation -= mouseY;
xRotation = Mathf.Clamp(xRotation, -90f, 90f);
// rotate cam and orientation
playerCamera.transform.rotation = Quaternion.Euler(xRotation, yRotation, 0);
orientation.rotation = Quaternion.Euler(0, yRotation, 0);
Vector3 newCamRotation = playerCamera.transform.rotation.eulerAngles;
camVelocity = newCamRotation - oldCamRotation;
float playerHeadPos = playerCollider.bounds.max.y;
Vector3 camPos = transform.position;
camPos.y = playerHeadPos;
playerCamera.transform.position = camPos;
}
}
private void StateHandler()
{
//Mode - Crouching
if (isCrouching)
{
state = MovementState.crouching;
moveSpeed = crouchSpeed;
}
//Mode - Sprinting
else if (grounded && isSprinting)
{
state = MovementState.sprinting;
moveSpeed = sprintSpeed;
}
//Mode - Walking
else if (grounded && _2DVelocity.sqrMagnitude > 0)
{
state = MovementState.walking;
moveSpeed = walkSpeed;
}
//Mode - Air
else if(grounded && _2DVelocity.sqrMagnitude <= 0)
{
state = MovementState.idle;
}
else
{
state = MovementState.air;
}
}
private void MovePlayer()
{
rb.isKinematic = false;
// calculate movement direction
moveActionInput = moveAction.ReadValue<Vector2>();
moveDirection = orientation.forward * moveActionInput.y + orientation.right * moveActionInput.x;
isIdle = moveDirection.normalized.magnitude <= 0;
_2DVelocity = Vector2.right * rb.linearVelocity.x + Vector2.up * rb.linearVelocity.z;
speedToVelocityRatio = (Mathf.Lerp(0, 2, Mathf.InverseLerp(0, (sprintSpeed / 50), _2DVelocity.magnitude)));
_2DVelocityMag = Mathf.Clamp((walkSpeed / 50) / _2DVelocity.magnitude, 0f, 2f);
// on slope
if (OnSlope() && !exitingSlope)
{
rb.AddForce(GetSlopeMoveDirection() * moveSpeed * 20f, ForceMode.Force);
if (rb.linearVelocity.y > 0)
rb.AddForce(Vector3.down * 80f, ForceMode.Force);
}
// on ground
else if (grounded)
rb.AddForce(moveDirection.normalized * moveSpeed * 10f, ForceMode.Force);
// in air
else if (!grounded)
rb.AddForce(moveDirection.normalized * moveSpeed * 10f * airMultiplier, ForceMode.Force);
// turn gravity off while on slope
rb.useGravity = !OnSlope();
}
private void SpeedControl()
{
// limiting speed on slope
if (OnSlope() && !exitingSlope)
{
if (rb.linearVelocity.magnitude > moveSpeed)
rb.linearVelocity = rb.linearVelocity.normalized * moveSpeed;
}
// limiting speed on ground or in air
else
{
Vector3 flatVel = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);
// limit velocity if needed
if (flatVel.magnitude > moveSpeed)
{
Vector3 limitedVel = flatVel.normalized * moveSpeed;
rb.linearVelocity = new Vector3(limitedVel.x, rb.linearVelocity.y, limitedVel.z);
}
}
}
private void Sprint(InputAction.CallbackContext context)
{
if (grounded)
{
isSprinting = true;
state = MovementState.sprinting;
moveSpeed = sprintSpeed;
}
}
private void EndSprint(InputAction.CallbackContext context)
{
isSprinting = false;
moveSpeed = walkSpeed;
}
private void Jump(InputAction.CallbackContext callback)
{
if(grounded && readyToJump)
{
isJumping = true;
state = MovementState.air;
readyToJump = false;
exitingSlope = true;
// reset y velocity
rb.linearVelocity = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);
rb.AddForce(transform.up * jumpForce, ForceMode.Impulse);
Invoke(nameof(ResetJump), jumpCooldown);
}
}
private void Crouch(InputAction.CallbackContext callback)
{
isCrouching = true;
playerObject.localScale = new Vector3(playerObject.localScale.x, crouchYScale, playerObject.localScale.z);
rb.AddForce(Vector3.down * 2f, ForceMode.Force);
}
private void Standup(InputAction.CallbackContext context)
{
isCrouching = false;
playerObject.localScale = new Vector3(playerObject.localScale.x, startYScale, playerObject.localScale.z);
playerCamera.transform.position = camStartPos;
}
private void StartGrab(InputAction.CallbackContext callback)
{
if (!isGrabbing)
{
RaycastHit hit;
if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.TransformDirection(Vector3.forward), out hit, grabDistance, Grabbable))
{
currentObject = hit.transform.GetComponent<Rigidbody>();
currentObjectParent = currentObject.transform.parent;
currentObject.transform.position = ObjectGrabPoint.position;
currentObject.transform.parent = ObjectGrabPoint;
currentObject.useGravity = false;
currentObject.constraints = RigidbodyConstraints.FreezeAll;
currentObject.linearDamping = 0;
currentObject.angularDamping = 10f;
isGrabbing = true;
}
}
}
private void EndGrab(InputAction.CallbackContext callback)
{
if (isGrabbing)
{
currentObject.transform.parent = WorldController.Instance.CurrentTimeParent();
currentObject.useGravity = true;
currentObject.constraints = RigidbodyConstraints.None;
currentObject.linearDamping = 0.5f;
currentObject.angularDamping = 0.05f;
Vector3 force = orientation.forward + rb.linearVelocity + camVelocity;
currentObject.AddForce(force, ForceMode.Impulse);
currentObject = null;
currentObjectParent = null;
isGrabbing = false;
}
}
private void ShiftTime(InputAction.CallbackContext callback)
{
if (canTimeShift)
{
StartCoroutine(ShiftTimeCoroutine());
}
}
IEnumerator ShiftTimeCoroutine()
{
timeShiftCooldownRemaining = timeShiftCooldown;
if (!timeShiftReady)
{
yield return new WaitForSeconds(timeShiftCooldown);
timeShiftReady = true;
}
else
{
WorldController.Instance.ShiftTime();
timeShiftReady = false;
}
}
private void ResetJump()
{
state = MovementState.idle;
isJumping = false;
readyToJump = true;
exitingSlope = false;
}
private bool OnSlope()
{
if (Physics.Raycast(transform.position, Vector3.down, out slopeHit, playerHeight * 0.5f + 0.3f))
{
float angle = Vector3.Angle(Vector3.up, slopeHit.normal);
return angle < maxSlopeAngle && angle != 0;
}
return false;
}
private Vector3 GetSlopeMoveDirection()
{
return Vector3.ProjectOnPlane(moveDirection, slopeHit.normal).normalized;
}
public void CalculateFootsteps()
{
if(_2DVelocity.magnitude > (moveSpeed / 100) && !isIdle && currentPhysicsMaterial != null)
{
if (stepTimer > stepTiming && state != MovementState.air && state != MovementState.crouching)
{
CallFootstepClip();
if (state == MovementState.walking)
{
stepTimer = 0;
}
else if (state == MovementState.sprinting)
{
stepTimer = (stepTiming / 2 + _2DVelocityMag * 2);
}
}
}
}
public void CallFootstepClip()
{
if (playerAudioSource)
{
if (enableFoosteps && foostepProfiles.Any())
{
for (int i = 0; i < foostepProfiles.Count(); i++)
{
if (foostepProfiles[i]._physicMaterials.Contains(currentPhysicsMaterial))
{
currentFootstepClips = foostepProfiles[i].footstepClips;
break;
}
else if (i == foostepProfiles.Count - 1)
{
currentFootstepClips = null;
}
}
if (currentFootstepClips != null && currentFootstepClips.Any())
{
playerAudioSource.PlayOneShot(currentFootstepClips[Random.Range(0, currentFootstepClips.Count())]);
}
}
}
}
private void OnTriggerStay(Collider other)
{
if (other.CompareTag("Water"))
{
currentPhysicsMaterial = other.GetComponent<Collider>().sharedMaterial;
isInWater = true;
rb.linearDamping = 10;
}
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag("Water"))
{
currentPhysicsMaterial = null;
isInWater = false;
rb.linearDamping = groundDrag;
}
}
}
I’m at a loss and just can’t figure out why my movement doesn’t work on inital runs/loads.
If anyone can see something I can’t or needs the full script let me know.