- Home /
Isometric tile-based RPG: Restricting turns to 45 degrees?
I'm creating an isometric RPG-style game, and the world is tile-based (so one character per tile, etc.). Think Jagged Alliance 2, X-Com or whatever.
As common with these games, characters can only look in one of 8 viewing directions (fwd, back, left, right, and the diagionals).
When I click my mouse button somewhere on the terrain around the character, he should turn towards it smoothly (not instantly) but only until he reaches a rotation that is the nearest 45 degree angle (for 8 possible rotations) for the point I clicked at.
I have trouble working with Quaternions so I took the cumbersome approach: Have 8 seperate colliding planes around the character in an octagonal fashion, and then cast a ray out from the chracter towards the point I clicked. Depending on the plane that was hit by the ray, the Vector3 coordinates of the final point that the character should rotate towards (one of the eight tiles around him) are assigned.
The system only works kind of, though. When first ordered to turn, the Vector3 isn't updated, only if I click again, and the same hold true after reaching a new waypoint. (-> "Look rotation viewing vector is zero" error)
The question is, how can I make the turning mechanic more simple?
var MouseCoords : Vector3;
var LookTarget : Vector3;
var TurnSpeed : float = 3.0;
var IsTurning : boolean;
function Update()
{
if (Physics.Raycast (ray, hit, Mathf.Infinity, TerrainMask))
{
MouseCoords = Vector3( Mathf.RoundToInt(hit.point.x), Mathf.RoundToInt(hit.point.y), Mathf.RoundToInt(hit.point.z));
}
if(Input.GetButtonDown("Fire1"))
{
CheckRotation();
TurnTowards(LookTarget);
}
}
function CheckRotation()
{
var RotationTargetX : float = transform.position.x;
var RotationTargetZ : float = transform.position.z;
var LocalOffset : Vector3 = transform.position + transform.up * 0.5;
var TurnTarget : Vector3 = Vector3(MouseCoords.x, transform.position.y, MouseCoords.z);
var hit : RaycastHit;
if(Physics.Raycast (transform.position, TurnTarget - transform.position, hit, Mathf.Infinity, RotationMask))
{
if (hit.collider.gameObject.CompareTag("RotationMeshN"))
{
RotationTargetZ += 1.0;
}
if (hit.collider.gameObject.CompareTag("RotationMeshNE"))
{
RotationTargetX += 1.0;
RotationTargetZ += 1.0;
}
if (hit.collider.gameObject.CompareTag("RotationMeshE"))
{
RotationTargetX += 1.0;
}
if (hit.collider.gameObject.CompareTag("RotationMeshSE"))
{
RotationTargetX += 1.0;
RotationTargetZ -= 1.0;
}
if (hit.collider.gameObject.CompareTag("RotationMeshS"))
{
RotationTargetZ -= 1.0;
}
if (hit.collider.gameObject.CompareTag("RotationMeshSW"))
{
RotationTargetX -= 1.0;
RotationTargetZ -= 1.0;
}
if (hit.collider.gameObject.CompareTag("RotationMeshW"))
{
RotationTargetX -= 1.0;
}
if (hit.collider.gameObject.CompareTag("RotationMeshNW"))
{
RotationTargetX -= 1.0;
RotationTargetZ += 1.0;
}
}
LookTarget = Vector3(RotationTargetX, transform.position.y, RotationTargetZ);
TurnTowards(LookTarget);
}
function TurnTowards(LookAtTarget : Vector3)
{
if(IsTurning == false)
{
var dir : Vector3 = (LookAtTarget - transform.position).normalized;
var NewRotation = Quaternion.LookRotation(dir);
for (u = 0.0; u <= 1.0; u += (TurnSpeed * Time.deltaTime))
{
IsTurning = true;
//transform.LookAt(LookAtTarget); // for instant turns
if(LookAtTarget != transform.position)
{
transform.rotation = Quaternion.Slerp( transform.rotation, Quaternion.LookRotation(LookAtTarget - transform.position), TurnSpeed);
}
yield;
}
IsTurning = false;
}
}
See if this helps
http://forum.unity3d.com/threads/53407-Euler-Quaternions-Radians-Degrees-huh
I think Eulers match your design and you should not have to worry about the complications of pitch/yawl in this simplified turning scenario.
Answer by robertbu · Aug 25, 2013 at 04:52 AM
I had a hard time sorting out your code, so I'm going to give you some example code instead:
#pragma strict
var speed = 90.0;
private var qTo : Quaternion;
function Update() {
if(Input.GetButtonDown("Fire1")) {
NewTurn();
}
transform.rotation = Quaternion.RotateTowards(transform.rotation, qTo, speed * Time.deltaTime);
}
function NewTurn () {
var hit : RaycastHit;
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast (ray, hit)) {
var aimingDirection = hit.point - transform.position;
var angle = -Mathf.Atan2(aimingDirection.z, aimingDirection.x) * Mathf.Rad2Deg + 90.0;
angle = Mathf.Round(angle / 45.0f) * 45.0f;
qTo = Quaternion.AngleAxis(angle, Vector3.up);
}
}
If you want a more eased movement to the turn, replace Quaternion.RotateTowards() with Quaternion.Slerp() and adjust the speed way down.
No, this doesn't work :( I tried playing around with the values but the operations are hard to grasp for me as well :(
Before you write-off the code above, do the following:
Create a new scene
Add a plane and resize to something large
Add a Cube sitting on the plane
Add the above script to the cube
Adjust the camera for a more downward view
Run and click on the plane
I tested this code before I posted it, so it should work in the above configuration. I don't know if or how your configuration differs from this one. But start by verifying the code works in this simple case.
The way this code works is on line 19, we use Atan2 to get the angle between the 'character' and the point hit on the plane. Line 20, rounds that angle to the nearest 45 degrees. Line 21 then rotates the character to that angle.
Thank you, it works, I made a c&p error.
I tried modifiying it so the actual transformation doesn't happen in the Update phase, I fear that this causes unneccessary CPU overhead when basically all characters rotate every frame (even ifthe angle is zero)? However, I didn't manage to get it to turn smoothly with either slerp or rotatetowards, all it ever does is turn instantly (the basic boolean/t += time.deltaTime system works in my old turning script)
#pragma strict
var speed = 90.0;
private var qTo : Quaternion;
var IsTurning : boolean;
function Update() {
if(Input.GetButtonDown("Fire1")) {
NewTurn();
}
}
function NewTurn ()
{
if(IsTurning == false)
{
var hit : RaycastHit;
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast (ray, hit))
{
var ai$$anonymous$$gDirection = hit.point - transform.position;
var angle = -$$anonymous$$athf.Atan2(ai$$anonymous$$gDirection.z, ai$$anonymous$$gDirection.x) * $$anonymous$$athf.Rad2Deg + 90.0;
angle = $$anonymous$$athf.Round(angle / 45.0f) * 45.0f;
qTo = Quaternion.AngleAxis(angle, Vector3.up);
var t : float;
for (t = 0.0; t <= 1.0; t += Time.deltaTime)
{
IsTurning = true;
transform.rotation = Quaternion.Lerp(transform.rotation, qTo, speed);
yield;
}
IsTurning = false;
}
}
}
Got it working finally, thanks again. Your method works flawlessly° I put in another way of doing it below in a seperate answer.
Answer by Cherno · Aug 31, 2013 at 04:26 PM
I finally managed to get it to work, seems like I made another mistake and for some reason typed in "Lerp" instead of "Slerp" ^_^
Here is the working code that has everything but the initial mouseclick and function call in a seperate function outside Update():
var speed = 90.0;
private var qTo : Quaternion;
var IsTurning : boolean;
var t : float;
function Update()
{
if(Input.GetButtonDown("Fire1"))
{
NewTurn();
}
}
function NewTurn ()
{
if(IsTurning == false)
{
var hit : RaycastHit;
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast (ray, hit))
{
var aimingDirection = hit.point - transform.position;
var angle = -Mathf.Atan2(aimingDirection.z, aimingDirection.x) * Mathf.Rad2Deg + 90.0;
angle = Mathf.Round(angle / 45.0f) * 45.0f;
qTo = Quaternion.AngleAxis(angle, Vector3.up);
print(qTo);
for (t = 0.0; t <= 1.0; t += Time.deltaTime * speed)
{
IsTurning = true;
transform.rotation = Quaternion.Slerp(transform.rotation, qTo, t);
yield;
}
IsTurning = false;
}
}
}
Your answer
Follow this Question
Related Questions
Tile-Based 2D Isometric RPG. Building a map? 3 Answers
3D Isometric Grid Based System, a feast of questions; 1 Answer
2d Isometric game? 1 Answer
Isometric 2.5D RPG: Advice 7 Answers
Isometric movement in Unity3D... 1 Answer