- Home /
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
Answer by felixpk · Jun 25, 2015 at 02:46 PM
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.
This just causes Unity to hang on an infinate loop and crash for me.
I'm aware of your second solution, I normally use something like that, I just want to articulate exact steps, so even if you just tap the button or let go of the button mid step, you always end up taking exact steps, like moving on a grid, so that if you were to take 2 steps forward and 2 steps back, you would be exactly where you were when you started.
The idea is that the player is completely blind and has to rely entirely on sound to find their way around, so I want you to be able to count the amount of footsteps you've taken as a reliable method of tracking one's progress.
felixpk's CoUpdate
function needs to handle the case where a key isn't held down to avoid the infinite loop...
else
yield return null; // yield until next frame
I'm not so sure felix's solution addresses your problem, but that should fix the loop regardless.
That did fix the infinate loop, and he does step pretty reliably now, but his movement is pretty jumpy...
I've tried changing out my transform.translate for a Lerp, but I don't totally understand lerps, so it's still really rigid.
Here is my move method now:
void move(Vector3 direction)
{
Vector3 newPosition = transform.position + (direction * speed);
transform.position = Vector3.Lerp(transform.position, newPosition, 0.3f);
}
How do I make the movement smooth? Would Lerp even be a good choice for this?
Answer by KnightsHouseGames · Jun 29, 2015 at 10:38 AM
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
https://www.youtube.com/watch?v=au6_95iI_gE
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.
Your answer
