CharacterController being 'Pushed Out' of Colliders in the Wrong Axis?

I’m making a 2.5D platformer, and I have a strange ‘bug’ in my code.

My Player (driven by a CharacterController) has two Update states: UpdateControl, for regular movement, and UpdateKnockback which throws the player into the air a short distance and disables controls until they land. UpdateKnockback is used when the Player is hurt.

I’ve had this effect working great for a couple of weeks. But I just found a scenario in which it acts very oddly.

If the ‘ground’ is a single BoxCollider, there’s no problem. But if the ground is made up of multiple BoxColliders, and the Player Knockback effect causes the Player object to land on a collider other than the one it was on when the Knockback effect was initiated, the Player will instantly jump the same distance as the width of the collider it landed on… if that makes sense.

I’ll try to illustrate:

AAAAABBBBBCCCCC

Imagine AAAA is one BoxCollider, BBBB is another, and CCCC is a third. All three make up the ground.

If the player was standing on AAAA when they were knocked back to the right, and landed on BBBB, they’d instantly jump to the width of BBBB from their current position, ending up on CCCC. So if they landed on the 2nd B, they’d jump to the second C. I hope that makes sense.

I can paste code if it helps, and I expect it will, but there’s a lot of code that might be causing the problem and I’m not sure where to start.

I only post this first request without code incase this is some kind of recognisable collider-update issue or something that someone has come across before.

I know there are some minor issues with collision volumes being updated after the Update method because I have another problem where the player dies in lava, is respawned, and instantly takes damage again. But that’s probably one to solve later.

It would be great if you could post some code. A good starting point for this would be the code that handles the movement in the knockback state. If you are moving the character by changing the transform position then an offset might be getting added on to the movement vector for some reason.

Hi andeeee, thanks for the reply! I will post some code for you for sure!

This method is called from the Update method if State == State.Knockback. I have a feeling that the problem is occurring the instant the state is changed back to the regular update, so I’ll post both.

All code is based on / derived from the PlatformerController script included in the 2D platformer demo project.

	private void UpdateKnockback()
	{
		// Apply friction and gravity
		m_movementDirection *= 0.95f;
		m_verticalSpeed -= m_gravity * Time.deltaTime;
		
		// Initialize the knockback vector
		if (m_previousPlayerState != PlayerState.Knockback)
		{
			m_movementDirection = new Vector3(-m_lastDirection.x * m_knockbackDirection.x, 0.0f, 0.0f);
			m_verticalSpeed = m_knockbackDirection.y * m_knockbackSpeed;
		}
		
		// Calculate actual motion
        Vector3 knockbackMovementOffset = m_movementDirection * m_knockbackSpeed + new Vector3(0.0f, m_verticalSpeed, 0.0f);
        knockbackMovementOffset *= Time.deltaTime;

        // Move the character
        m_collisionFlags = m_characterController.Move(knockbackMovementOffset);
		
		// Check to see if control should be returned to the player
		if (m_controlTimer + m_controlDelay < Time.time)
			m_playerState = PlayerState.Control;
	}

The regular Update method.

    private void UpdateControl()
    {
        // Apply gravity.
        ApplyGravity();
		
		// Update horizontal movement.
        UpdateSmoothedMovementDirection();
		
        // Jump if the player presses the jump button and jumping is legal.
        if (Input.GetButtonDown("Jump")  m_controlEnabled  m_jumpEnabled  !m_isJumping  (IsGrounded || m_hangTime < JUMP_TIMEOUT))
            DoJump();

        // Moving platform support.
        if (m_activePlatform != null)
        {
            Vector3 newGlobalPlatformPoint = m_activePlatform.TransformPoint(m_activeLocalPlatformPoint);
            Vector3 moveDistance = (newGlobalPlatformPoint - m_activeGlobalPlatformPoint);
            transform.position = transform.position + moveDistance;
        }
        m_activePlatform = null;

        // Calculate actual motion.
        Vector3 currentMovementOffset = m_movementDirection * m_movementSpeed + new Vector3(0, m_verticalSpeed, 0);

        // We always want the movement to be framerate independent.  Multiplying by Time.deltaTime does this.
        currentMovementOffset *= Time.deltaTime;

        // Move our character!
        m_collisionFlags = m_characterController.Move(currentMovementOffset);

        // Moving platforms support
        if (m_activePlatform != null)
        {
            m_activeGlobalPlatformPoint = transform.position;
            m_activeLocalPlatformPoint = m_activePlatform.InverseTransformPoint(transform.position);
        }

        // Set rotation to the move direction   
        if (m_movementDirection.sqrMagnitude > 0.01)
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(m_lastDirection), Time.deltaTime * m_rotationSmoothing);
        else
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(m_lastDirection), Time.deltaTime * 100);

        // We are in jump mode but just became grounded.
        if (IsGrounded)
        {
            if (m_isJumping || m_isDoubleJumping)
            {
                m_isJumping = false;
                m_isDoubleJumping = false;
                m_canDoubleJump = false;

                Vector3 jumpMoveDirection = m_movementDirection * m_movementSpeed;
                if (jumpMoveDirection.sqrMagnitude > 0.01)
                    m_movementDirection = jumpMoveDirection.normalized;
            }
        }
    }

I hope this helps!

P.S. Goldfrapp quote ftw :slight_smile:

Oh wait up… I didn’t include the moving-platform-support code in the UpdateKnockback method. I’ll try that when I get home if you haven’t already posted by then :slight_smile:

That fixed it. It was the moving platform support code missing from the UpdateKnockback effect. Sorry for wasting your time!