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.
Answer by MrMeows · Oct 22, 2015 at 08:02 AM
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);
}
}
}
Hi, sorry for my late reply. I was trying alot around. Well my oldPosition is basicly the currentposition. I have the rotatation working as @Statement showed me. Currently i try to get mouseclick and mousedrag under one hat.
$$anonymous$$y dragging works so far, but also the mouseclick gets attention, any idea how to handle both seperatly?
Did you look at my updated example and played the demo? It does dragging (well, swapping) and rotation.
Hi, yes i looked into it, well it's not what i am exaclty trying to achieve. I want the tile stick on the mouse and drag it freely around for response to the player. Thats why i tried to figure the the difference between dragging and clicking an other way.
Answer by Statement · Oct 21, 2015 at 06:48 AM
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.
First of all i want to say, thats an amazing and well written explaination for that, also it works like a charm. I really need to learn alot more. I will study all and also will try to do it as you mentioned with an enum/int counting around.
I later on also want to take those tiles and drag them around the playfield and switch those with other tiles. Which also mean if i drag them they need to go up hold and go down on release. $$anonymous$$ay i need to use a statemachine for that?
You don't necessarily need to create a statemachine for it. To me it sounds like you should break up the problem into two separate ones. One to deal with input handling (dragging replaces tiles, clicking rotates tiles) and one to deal with the event handling of said actions.
Anyway, once you have decided on an implementation to detect if the user clicks, begin drag and release drag, all you have to do is either call Flip or Swap. Just add input guards to not perform any input actions in case a coroutine is running (so you don't get a flip and a swap at the same time).
I updated the github repository so you can view the changes there.
I have heard of the new event system, i just thought there is an easier way understandable for me. I just have basic knowledge of program$$anonymous$$g, so it is not easy stuff at all. I just made dragging with normal scripting but it seems it wont work with your flipping around stuff.
I will look into the event system, not sure how to construct this, with snapping, since my current dragging works good so far, sadly without the move up/down animations at the moment.
Thanks for the github stuff, i'll again look into it.
public class DragingTiles : $$anonymous$$onoBehaviour {
private Vector3 screenPoint;
private Vector3 offset;
public Vector2 ylimits = new Vector2(-9, 9);
public Vector2 xlimits = new Vector2(-9, 9);
public Vector3 oldPosition, newPosition;
public Layer$$anonymous$$ask Playfield;
public bool isdragged = false;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update ()
{
//snap to others
if(isdragged)
{
Ray ray = new Ray (transform.position, Vector3.down);
RaycastHit hit; //save the hitted unit
Debug.DrawRay(ray.origin, ray.direction, Color.red);
if(Physics.Raycast(ray, out hit, 6, Playfield))
{
if(hit.collider.tag == "SnapField")
{
newPosition = hit.collider.gameObject.transform.position;
}
else
{
newPosition = oldPosition;
}
}
}
}
void On$$anonymous$$ouseDown()
{
screenPoint = Camera.main.WorldToScreenPoint(gameObject.transform.position);
offset = gameObject.transform.position - Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, screenPoint.z));
oldPosition = transform.position;
}
void On$$anonymous$$ouseDrag()
{
Vector3 curScreenPoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, screenPoint.z);
Vector3 curPosition = Camera.main.ScreenToWorldPoint(curScreenPoint) + new Vector3(0,1,0);
//curPosition.y = $$anonymous$$athf.Clamp(curPosition.y, ylimits.x, ylimits.y);
//curPosition.x = $$anonymous$$athf.Clamp(curPosition.x, xlimits.x, xlimits.y);
isdragged = true;
transform.position = curPosition;
}
void On$$anonymous$$ouseUp()
{
isdragged = false;
transform.position = newPosition;
}
}
I believe the problem is the direct mouseinputs to consider what is flipping, what is dragging.
Your answer
Follow this Question
Related Questions
Rotate on Transform.localeulerAngels smoothly 0 Answers
Lerp going crazy when negative 1 Answer
Rotation (Quaternion.Lerp) not working 0 Answers
Slerp rotation issue when calling a lot 0 Answers
How to control rocket pathing with Quaternion.Slerp 1 Answer