Fixed Step Movement

I’m designing a game that requires movement to be very precise. I want each step to take you the exact same distance, so the player can count footsteps in order to judge distance, as the game will take place in total darkness.

I’ve seen other answers to this problem that involve using coroutines, but those don’t seem to work for me, and the original posters seem to be long gone without giving the real answer.

Heres where I am with this so far based on those solutions.

public class Movement : MonoBehaviour {

	public GameObject player;
	public float speed;

	// Use this for initialization
	void Start () {
		//Coroutine for movement
		StartCoroutine(CoUpdate());
	}

	IEnumerator CoUpdate()
	{
		if(Input.GetKey(KeyCode.UpArrow))
		{
			move(Vector3.forward);
			yield return null;
		}
		else if(Input.GetKey(KeyCode.DownArrow))
		{
			move (Vector3.back);
			yield return null;
		}
	}

	void move(Vector3 direction)
	{
		transform.Translate (direction * speed, Space.Self);
	}

}

I intend to make left and right do exact 90 degree turns in those directions, thats why those directions aren’t currently present.

Help with this problem would be greatly appreciated

To make an exact movement every frame you could either do it in a CoRoutine:

void Start(){
  StartCoroutine(CoUpdate());
}

IEnumator CoUpdate(){
   while(true){ //while true is needed to loop the CoUpdate() else it is just executed once
      if(Input.GetKey(KeyCode.UpArrow)){
          move(Vector3.forward);
          yield return new WaitForSeconds(0.1f); // this makes sure that each step is executed after 0.1 seconds
      }else if(Input.GetKey(KeyCode.DownArrow)){
          move (Vector3.back);
          yield return new WaitForSeconds(0.1f);
      }
   }
}

But I think the way better solution would be:

void Update(){
    if(Input.GetKey(KeyCode.UpArrow)){
        move(Vector3.forward * Time.deltaTime);
    }else if(Input.GetKey(KeyCode.DownArrow)){
        move (Vector3.back * Time.deltaTime);
    }
}

With DeltaTime you compensate the amount of time between every Update. The result is a smooth and steady movement.

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.