OK, so I have the solution, it’s probably not the best solution to this problem because it is insanely complicated, but this is often the case with any solution I find.
I followed a series of videos about grid based movement for a Civ/dungeon crawler movement system that relies on pathfinding algorithms and a procedurally generated map system, you can find that video here
Don't bother watching all the videos unless you have an entire day to waste, as he never actually covers the animation part. Download the project from the link in the description of that video to save yourself a lot of time. In there, you'll get the algorithm and all that good stuff, but it will be click based rather than d-pad based like I had in mind for my project.
I followed the videos rather than downloading the project, so I made a few customizations here and there, and wrote out a character movement system based on his platform.
It runs sorta like tank controls, with forward and backward steps, and 90 degree rotation on left and right button presses.
Here is my player code. This would replace the “Unit” class in his system. It’s important you include system.collections.generic, as he uses that to manage his data structures.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Player : MonoBehaviour {
public int tileX;
public int tileY;
public TileMap map;
public bool moving = false;
public bool rotating = false;
public enum Direction{North, South, East, West}
Direction currentDirection = Direction.North;
Quaternion newRotation = new Quaternion();
public List<Node> currentPath = null;
void Update() {
if(Vector3.Distance(transform.position, map.TileCoordToWorldCoord( tileX, tileY )) < 0.2f)
AdvancePathing();
// Smoothly animate towards the correct map tile.
transform.position = Vector3.Lerp(transform.position, map.TileCoordToWorldCoord( tileX, tileY ), 3f * Time.deltaTime);
//Smoothly animate turns
transform.rotation = Quaternion.Slerp(transform.rotation, newRotation, 4f * Time.deltaTime);
//Step Forward
if(Input.GetKey(KeyCode.UpArrow))
{
if(!moving)
{
StartCoroutine(StepForward());
}
}
//Step Backward
if(Input.GetKey(KeyCode.DownArrow))
{
if(!moving)
{
StartCoroutine(StepBackward());
}
}
//Turn Right
if(Input.GetKey(KeyCode.RightArrow))
{
if (!rotating)
{
StartCoroutine(RotateRight());
}
}
//Turn Left
if(Input.GetKey(KeyCode.LeftArrow))
{
if (!rotating)
{
StartCoroutine(RotateLeft());
}
}
}
// Advances our pathfinding progress by one tile.
void AdvancePathing() {
if(currentPath==null)
return;
// Teleport us to our correct "current" position, in case we
// haven't finished the animation yet.
transform.position = map.TileCoordToWorldCoord( tileX, tileY );
// Move us to the next tile in the sequence
tileX = currentPath[1].x;
tileY = currentPath[1].y;
// Remove the old "current" tile from the pathfinding list
currentPath.RemoveAt(0);
if(currentPath.Count == 1) {
// We only have one tile left in the path, and that tile MUST be our ultimate
// destination -- and we are standing on it!
// So let's just clear our pathfinding info.
currentPath = null;
}
}
IEnumerator StepForward()
{
if (currentDirection == Direction.North) {
moving = true;
if (tileY < map.mapSizeY - 1)
map.GeneratePathTo (tileX, tileY + 1);
yield return new WaitForSeconds (0.3f);
moving = false;
}
if (currentDirection == Direction.South) {
moving = true;
if (tileY > 0)
map.GeneratePathTo (tileX, tileY - 1);
yield return new WaitForSeconds (0.3f);
moving = false;
}
if (currentDirection == Direction.East) {
moving = true;
if (tileX < map.mapSizeX - 1)
map.GeneratePathTo (tileX + 1, tileY);
yield return new WaitForSeconds (0.3f);
moving = false;
}
if (currentDirection == Direction.West) {
moving = true;
if (tileX > 0)
map.GeneratePathTo (tileX - 1, tileY);
yield return new WaitForSeconds (0.3f);
moving = false;
}
}
IEnumerator StepBackward()
{
if (currentDirection == Direction.North) {
moving = true;
if (tileY > 0)
map.GeneratePathTo (tileX, tileY - 1);
yield return new WaitForSeconds (0.3f);
moving = false;
}
if (currentDirection == Direction.South) {
moving = true;
if (tileY < map.mapSizeY - 1)
map.GeneratePathTo (tileX, tileY + 1);
yield return new WaitForSeconds (0.3f);
moving = false;
}
if (currentDirection == Direction.East) {
moving = true;
if (tileX > 0)
map.GeneratePathTo (tileX - 1, tileY);
yield return new WaitForSeconds (0.3f);
moving = false;
}
if (currentDirection == Direction.West) {
moving = true;
if (tileX < map.mapSizeX - 1)
map.GeneratePathTo (tileX + 1, tileY);
yield return new WaitForSeconds (0.3f);
moving = false;
}
}
IEnumerator RotateRight()
{
rotating = true;
float yRotation = transform.eulerAngles.y;
newRotation = Quaternion.Euler(0,yRotation+90,0);
switch(currentDirection)
{
case(Direction.West):
currentDirection = Direction.North;
Debug.Log ("Direction is now North");
break;
case(Direction.East):
currentDirection = Direction.South;
Debug.Log ("Direction is now South");
break;
case(Direction.South):
currentDirection = Direction.West;
Debug.Log ("Direction is now West");
break;
case(Direction.North):
currentDirection = Direction.East;
Debug.Log ("Direction is now East");
break;
}
yield return new WaitForSeconds(1f);
rotating = false;
}
IEnumerator RotateLeft()
{
rotating = true;
float yRotation = transform.eulerAngles.y;
newRotation = Quaternion.Euler(0,yRotation-90,0);
switch(currentDirection)
{
case(Direction.West):
currentDirection = Direction.South;
Debug.Log ("Direction is now South");
break;
case(Direction.East):
currentDirection = Direction.North;
Debug.Log ("Direction is now North");
break;
case(Direction.South):
currentDirection = Direction.East;
Debug.Log ("Direction is now East");
break;
case(Direction.North):
currentDirection = Direction.West;
Debug.Log ("Direction is now East");
break;
}
yield return new WaitForSeconds(1f);
rotating = false;
}
}
My additions should compensate for the array size and should lead to a quite accurate grid movement system. Mine was built for a first person camera (only out of nessessity, I hate first person otherwise), but I would imagine it wouldn’t be difficult to modify for a 3rd person camera like some sort of NES style Zelda game in 3D or something. Frankly it almost completely puts that pathfinding algorithm to waste, but I can’t figure out how to simplify it right now
Next time someone searches for Fixed Step Movement, now there will at least be some sort of solution to be found.