How to move Tile Up, Rotate by 90 degrees, then down again on click, while supporting dragging tile freely

Hi,

i word on a puzzlegame where i want the player to drag around tiles and when the player just pressed the left mouse button, i want the tile to move up a bit, then rotate by 90 degrees on the y-axis and the put it back down.

This should happen when i use a raycast and click the left mousebutton.

i just started with that:

void Update()
{
	if (Input.GetMouseButtonDown (0)) 
	{
		ray = Camera.main.ScreenPointToRay (Input.mousePosition);
		{
		if (Physics.Raycast (ray, out hit)) 
			{
			if (hit.transform.tag == "card") 
				{
					print (hit.collider.name);
					
					
					oldRotation = hit.transform.rotation;

					Vector3 newRotationAngles = oldRotation.eulerAngles;
					newRotationAngles.y += 90;
					newRotation = Quaternion.Euler (newRotationAngles);

					isRotating = true;
				}
			}
		
		}
	}
	if(isRotating)
	{
		hit.transform.rotation = Quaternion.Slerp(oldRotation,newRotation,Time.deltaTime * speed);
	}
}

The problem is the rotation wont work, and i have no idea how to move the tile up before, and move it down after.

Thanks in advance.

Replace “Quaternion.Slerp(oldRotation” with “Quaternion.RotateTowards(hit.transform.rotation”. Slerp uses a float between 0 and 1 as its third input. RotateTowards uses an angle as its third input. Also, you should change the rotation from the current rotation instead of the old one. Otherwise, you never make any progress. As for moving the tile…

if(isRotating)
{
if(hit.transform.position.y < maximumHeight)
{
if(Quaternion.Angle(hit.transform.rotation,newRotation) >= 1)
{
transform.Translate(0,Time.deltaTime * liftSpeed,0);
} else
{
if(hit.transform.position.y > minimumHeight)
{
transform.Translate(0,Time.deltaTime * -liftSpeed,0);
}
}
} else
{
if(Quaternion.Angle(hit.transform.rotation,newRotation) >= 1)
{
hit.transform.rotation = Quaternion.RotateTowards(hit.transform.rotation,newRotation,Time.deltaTime * speed);
} else
{
transform.Translate(0,Time.deltaTime * -liftSpeed,0);
}
}
}

One way to make code run in sequence over several frames is to use coroutines with StartCoroutine.

 // A *high level* example of how to use Flip
void Update() {
    if (IWasClicked)
        StartCoroutine("Flip"); // Coroutine Flip for moving up, rotate, down
}

bool isRunning;
// A *high level* example of how your code could be structured
IEnumerator Flip() {
    if (!isRunning) {
        isRunning = true;
        yield return StartCoroutine("Step_MoveUp"); // Coroutine Step_MoveUp for moving up
        yield return StartCoroutine("Step_Rotate90"); // Coroutine Step_Rotate90 for rotation
        yield return StartCoroutine("Step_MoveDown");  // Coroutine Step_MoveDown for moving down
        isRunning = false;
    }
}

You could also make a state machine. There are a few ways you can go about. For instance you could just have a int to define which state you are in (0 = waiting for input, 1 = moving up, 2 = rotating, 3 = moving down) and switch (state). A slight improvement on readability and safety would be to declare an enum with the four states but it would work the same as having an int. An object oriented approach would solve it with polymorphism where you subclass (or “extend”) a state type (see state pattern). You could also create components that deal with one problem each and let your existing component manage those other components.

I made an example project you can checkout.

Interactive demo (Updated)

Interactive demo (Old)

If we look at the core of the Flip coroutine, it looks like this:

        Vector3 rest = transform.position;
        Vector3 lift = transform.position + Vector3.up;
        Quaternion right = transform.rotation * Quaternion.Euler(0, 90, 0);

        while (MoveTowards(lift)) { yield return null; }
        while (RotateTowards(right)) { yield return null; }
        while (MoveTowards(rest)) { yield return null; }

Basically it is looping until MoveTowards return false, then does the same for RotateTowards and finally with MoveTowards again. yield return null;with coroutines means “pause execution until the next frame”.

Looking at MoveTowards, it is very simple but the syntax can look a little odd at first glance.

    private bool MoveTowards(Vector3 toward)
    {
        return toward != (transform.position = Vector3.MoveTowards(
            transform.position, toward, moveSpeed * Time.deltaTime));
    }

This is a oneliner which updates transform.postion and then compares it with toward. Since it is designed to be used in the while loop, it return true if transform.position hasn’t reached toward. So the while loop will run until transform.position reach toward.

RotateTowards is pretty much the same, except using transform.rotation.