How can I properly tilt an object about two axes, while preventing the other axis from rotating?

I’ve been trying to make a very simple game. You have a gameboard, you tilt it and a marble rolls around on it. I’ve been able to tilt the gameboard well enough, but I’m encountering a small problem. As I keep tilting the game board, it seems to become offset in the wrong direction.

For example I want to be able to tilt each side of the game board. Basically I am rotating the ‘x’ and ‘z’ axes towards the ‘y’ axis. The gameboard is in the ‘xz’ plane if that helps visualize it.

However, the game board starts to “spin” or rotate in the xz plane, either counterclockwise or clockwise, which I want to avoid because it just feels awkward.

  1. Is this avoidable?
  2. If it is, how can I change my code to fix this?
  3. As a side note, I use Quaternion.slerp to reset the gameboard back to original position when left idle. I heard that you don’t really want to mess with the Quaternion. Is there a more efficient way to do this?

Here is my code, thank you in advance.

public class BoardTiltControl : MonoBehaviour
{

    public float rotationspeed = 1;
    public float gameboardTilt = 0.4F;
    private float x_pos, y_pos, x_pos_mouse, y_pos_mouse, x_pos_diff, y_pos_diff, time_off_press = 0;
    private Quaternion starting_pos;
    private Quaternion reset_y;
    private int back_to_origin = 0,  pressed = 0;

    Vector3 rotate_vect = new Vector3(0,0, 0);
 
    // Use this for initialization
    void Start ()
    {

        starting_pos = transform.rotation;


       // print(starting_pos.x);

	}
	
	// Update is called once per frame
	void FixedUpdate ()
    {

        if (Input.GetMouseButtonDown(1))
        {
            x_pos_mouse = Input.mousePosition.x; //get position of the mouse only on the frame it is pressed
            y_pos_mouse = Input.mousePosition.y;
        }

        else { }

        Rotation();

    }

    private void Rotation()
    {
        //float rothorizontal;
        //float rotvertical;

        if (Input.GetMouseButton(1)) //if the right mouse button is being held
        {

            back_to_origin = 0;
            pressed = 1; //means it is true that button is pressed
            x_pos = Input.mousePosition.x; //track the position of the mouse every frame
            y_pos = Input.mousePosition.y;

            x_pos_diff = x_pos - x_pos_mouse; //take differnce of the initial position when mouse was clicked and where the mouse is now
            y_pos_diff = y_pos - y_pos_mouse;

            rotate_vect = new Vector3(y_pos_diff, 0, -x_pos_diff); //create a Vector3 based on displacement
                                                                  //rotate_right = new Vector3(0, x_pos_final, 0);

            

            transform.Rotate(rotate_vect * gameboardTilt * Time.deltaTime, Space.Self);
           


        }

        else if ((Input.GetMouseButton(1) != true) && (pressed == 1)) //if mouse isn't being pressed. The "pressed" variable is to ensure that this statement is only entered once after not pressing, to ensure time_off_press isnt reset
        {

            time_off_press = Time.time; //take time snapshot after button is not being pressed
            //Debug.Log("Mouse is not pressed");
            pressed = 0; //false, button isn't being pressed
            
        }

        else if (((Time.time - time_off_press) > 2.0F) && pressed == 0) //if the interval of time the button hasn't been pressed for is greater than 2 seconds
        {
           // Debug.Log("Should reset gameboard");
            time_off_press = Time.time;
            pressed = 1; //will manually reset gameboard at this point, so it's as if the mouse has been pressed
            back_to_origin = 1; //condition to go back to origin

        }
        else if(back_to_origin ==1)
        {
             //Now find the current positions of the transform and compare it to the initial in order to rotate gameboard back to original state

            transform.rotation = Quaternion.Slerp(transform.rotation, starting_pos, Time.deltaTime*rotationspeed);
            //Debug.Log("BOARD RESET");


        }
    }
}

Thanks again

I dont know if I understand fine your problem.

If you rotates an object about X, when you rotates Z, is based on X. To fix, you can make a parent Object to rotate X, and your gameboard object child of that parent to rotate Z. this way, X rotation not affects to Z, but you have to rotate both objects individualy.

Try this:

public GameObject parentX;
    public GameObject gameboardZ;  // child of parentX
    public float rotationSpeed;

    void Update()
    {

        float X = Input.GetAxis("Mouse X");
        float Z = Input.GetAxis("Mouse Y");

        if(Input.GetMouseButtonDown(1))
        {
            // IF YOU ARE PRESSING MOUSE BUTTON, BOTH OBJECTS ROTATES MOVING YOUR MOUSE
            parentX.transform.Rotate(X, 0, 0);
            gameboardZ.transform.Rotate(0, 0, Z);

        } else
        {
            // IF YOU ARE NOT PRESSING MOUSE BUTTON, BOTH OBJECTS ROTATES TO ORIGIN AT rotationSpeed value.

            // X TO 0
            if (parentX.transform.localEulerAngles.x != 0)
            {
                if (parentX.transform.localEulerAngles.x > 0)
                {
                    parentX.transform.Rotate(-Time.deltaTime * rotationSpeed, 0, 0);

                    if (parentX.transform.localEulerAngles.x < 0)
                    {
                        parentX.transform.localEulerAngles = Vector3.zero;
                    }

                }
                else
                {
                    parentX.transform.Rotate(Time.deltaTime * rotationSpeed, 0, 0);

                    if (parentX.transform.localEulerAngles.x > 0)
                    {
                        parentX.transform.localEulerAngles = Vector3.zero;
                    }
                }
            }

            // Z TO 0
            if (gameboardZ.transform.localEulerAngles.x != 0)
            {
                if (gameboardZ.transform.localEulerAngles.x > 0)
                {
                    gameboardZ.transform.Rotate(-Time.deltaTime * rotationSpeed, 0, 0);

                    if (gameboardZ.transform.localEulerAngles.x < 0)
                    {
                        gameboardZ.transform.localEulerAngles = Vector3.zero;
                    }

                }
                else
                {
                    gameboardZ.transform.Rotate(Time.deltaTime * rotationSpeed, 0, 0);

                    if (gameboardZ.transform.localEulerAngles.x > 0)
                    {
                        gameboardZ.transform.localEulerAngles = Vector3.zero;
                    }
                }
            }
        }
    }

Is that your objective?,