Controller work differently with same code and objects, but in different position

Hello.
I have the most bizarre bug and wonder if someone might know the solution.

CONTEXT

I wrote a controller for a 2.5D-ish platformer. It allows the character to:

  • Jump
  • Climb vertical pipes
  • Hang and “move hanging” along horizontal pipes
  • Crouch

Everything seems to work as intended ( I paste the full controller below). My colleague started to develop the test level (mainly using Pro Builder and some simple models imported from Blender) and reported to me a bizarre occurrence - if he moved more to the “right” of our map, the protagonist stopped grabbing the pipe to hang. No variables seemed to be different - the pipe was copied from earlier parts of the level, the code was the same no matter where it was executed.

We started experimenting, moving the whole level blockout left and right, and indeed we received different results. If we moved the level further left, the controller didn’t grab event the first pipes, that always worked fine. Moving the blackout left or up seemed to fix the problem, but only partially.

PROBLEM

The same code with the same objects on the level seems to give different results with no apparent reason other than the location in the editor.

CODE

Main controller:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;


public class Controller : MonoBehaviour
{

    private CharacterController controller;
    private ComplexMovement tricks;
    public float gravity = -9.806f;

    //Movement variables
    public Vector3 velocity;
    public float speed;

    //Jumping variables
    public float jumpHeight;
    public float fallMultiplier;
    public float lowJumpMultiplier;
    public bool isJumping;

    //Ground and ceiling check variables and objects
    public float groundDistance;
    public float ceilingDistance;
    public Transform groundChecker;
    public Transform ceilingChecker;
    public float ceilingHitMultiplier;

    //Climbing variables
    public bool canClimb;
    public bool isClimbing;
    private Vector3 canClimbDirectionZ = new Vector3(0, 0, 1);
    public float sphereDiameter = 0.1f;

    //Hanging variables
    public bool canHang;
    public bool isHanging;
    private Vector3 canHangDirectionY = new Vector3(0, 1, 0);

    public bool isGrounded = true;
    public bool ceilingOverhead = false;

    //Functional layers for interactions
    public LayerMask Ground;
    public LayerMask Climable;
    public LayerMask Usable;

    public Animator animator;

    //resetting values
    float resetSpeed;
    float resetControllerHeight;
    Vector3 resetControllerCenter;
    float resetGroundDistance;
    float resetGravity;


    public Vector3 normalGroundCheck;
    public Vector3 controllerOffsetY = new Vector3(0, 0.5f, 0);


    public float speedCrouchingFactor = 0.5f;

    public RaycastHit hit;

    public object Translate { get; private set; }

    void Start()
    {
        //Getting the controller component of the Protag
        controller = GetComponent<CharacterController>();
        tricks = GetComponent<ComplexMovement>();

        animator = GetComponentInChildren<Animator>();
        normalGroundCheck = groundChecker.transform.position;

        //setting the reseters
        resetSpeed = speed;
        resetControllerHeight = controller.height;
        resetControllerCenter = controller.center;
        resetGroundDistance = groundDistance;
        resetGravity = gravity;
        
    }

    void Update()
    {
        if(Input.GetKeyDown("r"))
        {
            SceneManager.LoadScene("Level0", LoadSceneMode.Single);
        }
        if (Input.GetButton("Crouch") && isGrounded)
        {
            Crouch();
        }
        else
        {
            ResetProperties();
        }

        //Setting current animation state
        if ((Input.GetAxis("Horizontal") != 0))
        {
            animator.SetInteger("run", 1);
        }
        else if ((Input.GetAxis("Horizontal") == 0) && !isClimbing)
        {
            animator.SetInteger("run", 0);
        }
       
        //Ground check using the shape of a sphere.
        isGrounded = Physics.CheckSphere(groundChecker.position, groundDistance, Ground);
        
        //setting animator parameter equal to ground check
        animator.SetBool("grounded", isGrounded);
        
        //Raycast chosen over Sphere. Sphere blocked jumping near walls even when it was really small
        ceilingOverhead = Physics.Raycast(ceilingChecker.position, Vector3.up,ceilingDistance);

        canHang = Physics.Raycast(ceilingChecker.position, ceilingChecker.position + canHangDirectionY, 0.5f, Climable);

        if (canHang && Input.GetButton("Interact"))
        {
            isHanging = true;
            
        }

        
        
        
        canClimb = Physics.SphereCast(ceilingChecker.position, sphereDiameter, canClimbDirectionZ, out hit, 1f, Climable);

        if (canClimb && Input.GetButton("Interact"))
        {
            isClimbing = true;
           
        }
        //Script for interacting with objects
        else if (Input.GetButton("Interact"))
        {
            Interact();
        }

        //Velocity.y must be reseted when jump is finished.
        if (isGrounded && velocity.y < 0)
            {
                velocity.y = 0f;
            }

        //The jump input and checks. Slope limit make jumping near walls jitter-free.
        if (Input.GetButtonDown("Jump") && isGrounded && !ceilingOverhead)
            {

            isJumping = true;
                
        }

    }
    
    void FixedUpdate()
    {
        //Movement is applied in fixed update, because then physics just works.
        Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, 0);
 
      
      
        
        if (velocity.y < 0)
        {
            velocity += Vector3.up * gravity * (fallMultiplier - 1) * Time.deltaTime;
        }
        else if (velocity.y > 0 && !Input.GetButtonDown("Jump"))
        {
            velocity += Vector3.up * gravity * (lowJumpMultiplier - 1) * Time.deltaTime;
        }
       
        //Calling complex movement
        
        if(isClimbing)
        {
            tricks.climb();
            if (Input.GetButton("Vertical"))
            {
                animator.SetInteger("run", 1);
            }
           
            else
            {
                animator.SetInteger("run", 0);
            }

            animator.SetBool("climbing", true);
            transform.forward = new Vector3(1, 0, 0);
            
            
        }
        else if (isHanging)
        {
            tricks.hang();

            if (Input.GetButton("Horizontal"))
            {
                animator.SetInteger("run", 1);
            }

            else
            {
                animator.SetInteger("run", 0);
            }

            turnPlayerInTheDirectionOfMovement(move);

            animator.SetBool("hanging", true);
            //transform.forward = new Vector3(1, 0, 0);
        }
        else if(isJumping)
        {
            Jump();
            isJumping = false;
        }
        else
        {
            turnPlayerInTheDirectionOfMovement(move);



            animator.SetBool("hanging", false);
            animator.SetBool("climbing", false);
            controller.Move(move * Time.deltaTime * speed);
        }


        velocity.y += gravity * Time.deltaTime;
        
        //Finally applying movement over time
        controller.Move(velocity * Time.deltaTime);
       
    }



    public void ResetProperties ()
    {
        controller.center = resetControllerCenter;
        controller.height = resetControllerHeight;
        gravity = resetGravity;
        animator.SetBool("crouching", false);

        groundDistance = resetGroundDistance;
        speed = resetSpeed;

    }

    public void turnPlayerInTheDirectionOfMovement(Vector3 move)
    {
      
        if ((move != Vector3.zero))
        {

            transform.forward = move;

        }
    }

    public void Jump()
    {
        controller.slopeLimit = 90.0f;

        velocity.y += Mathf.Sqrt(jumpHeight * -2f * gravity);

        if (ceilingOverhead)
        {
            velocity.y -= ceilingHitMultiplier * fallMultiplier;
        }

    }

    public void Crouch()
    {
        controller.center = new Vector3(0, 0.5f, 0);
        controller.height = 1;

        groundDistance = 0.2f;
        animator.SetBool("crouching", true);

        speed = speedCrouchingFactor * resetSpeed;
    }

    public void Interact()
    {
        Usable usable;
        RaycastHit useThisObject;
        if (Physics.Raycast(ceilingChecker.position, canClimbDirectionZ, out useThisObject, 7f, Usable))
        {
            usable = useThisObject.collider.gameObject.GetComponent<Usable>();
            usable.OnRayHit();
        }
        Debug.DrawRay(ceilingChecker.position, canClimbDirectionZ + new Vector3(0, 0, 4), Color.blue, 2f);
    }
}

Climbing pipes and hanging on them:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ComplexMovement : MonoBehaviour
{
    public CharacterController controller;
    public Controller controllerScript;


    public float climbSpeed = 4f;
    public float hangSpeed = 3f;

    private void Start()
    {
        controller = GetComponent<CharacterController>();
        controllerScript = GetComponent<Controller>();
    }
    //Climbing method
    public void climb()
    {
        //Debug.Log("JESTEM HARDKOREM");

       //Ensuring that no other "force" apply to climbing object
        controllerScript.velocity.y = 0f;
        controllerScript.gravity = 0;
        



        Vector3 move = new Vector3(0, Input.GetAxis("Vertical"), 0);

        if (Input.GetButton("Crouch") || controllerScript.canClimb == false)
        {
            controllerScript.isClimbing = false;
            controllerScript.ResetProperties();
        }

        if(Input.GetButton("Jump") && Input.GetButton("Horizontal") && !Input.GetButton("Vertical"))
        {

            controllerScript.isGrounded = true;
            controllerScript.isClimbing = false;
            controllerScript.ResetProperties();

           
            controllerScript.Jump();

            
            
        }

        controller.Move(move * Time.deltaTime * climbSpeed);
    }


    //Hanging method

    public void hang()
    {
        //Debug.Log("wiszenie i chodzenie");
      
        //Ensuring that no other "force" apply to hanging object
        controllerScript.velocity.y = 0f;
        controllerScript.gravity = 0f;
        

        Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, 0);

        if(Input.GetButton("Crouch") || controllerScript.canHang == false)
        {
            controllerScript.isHanging = false;
            controllerScript.ResetProperties();
        }

        controller.Move(move * Time.deltaTime * hangSpeed);
    }
    



}

Hey, we are continuing our work by moving whooole level to the “left” ( on x-axis), yet the question remains. I wonder if this is something that can come back far down the project pipeline and stop us once and for all. If anybody wants to receive any kind of details I would be more than happy to provide them, since we are totally lost.