Sunday, July 28, 2013

Grid Based Movement in Unity

If you have played old school games like Dungeon Master, Eye of the Beholder, the early Lands of Lore and Wizardry series or even more recent games like Legend of Grimrock, then you know what Grid Based Movement is.    This is my implementation of grid based movement using Grid Framework by HiPhish.  Feel free to use your own grid system.

See script in action

Setup a scene and if you are using Grid Framework create a new grid.
Add an empty gameobject to represent the player.  I also make the camera a child of this object and set the camera position to 0,0,0 so it is always looking forward from the centre of the player.

Add this script to the player object.  If you are not using Grid Framework, you will need to change/remove the GFGrid variable and the first line in the awake method.  All this line currently does is move the player to 0,0,0.  In a later post the grid will be used to determine if a move is valid or not.  You will also need to either create the inputs I have specified in the script or update the script to match your own.  I use w,s,a,d for forwards, backward, stepleft, stepright and q and e for turning.

public GFGrid Grid;//Grid the Player is attached to.
public float StepSpeed = 0.5f; //how fast in seconds each step or turn takes to complete
public Vector3 CurrentGridPosition = new Vector3(0,0,0);  //3d position in Grid
public string CurrentDirection; //Direction Player is facing (n,s,e,w)

float timer = 0; //used to disable turn left and turn right inputs when player is already turning
bool enableRotation = true; //used to disable turn left and turn right inputs when player is already turning
Vector3 intendedPosition;//Before player is moved, the position the player will end up at is stored here
//Is used to check that the move is valid and also used to disable move inputs
//when player is already moving

void Awake()
{
this.transform.position = Grid.GridToWorld(CurrentGridPosition); //move player to default position/grid origin
intendedPosition = CurrentGridPosition; //no movement has occured yet, so intended position is set to current position
CurrentDirection = "n"; //TODO: default direction is calculated from CurrentGridPosition.
}

void Start()
{
//sets timer to match time it takes to make one step or turn
timer = StepSpeed; 
}

public void MoveTo(Vector3 position)
{
//Move Player to position over time
LeanTween.move(gameObject, position, StepSpeed);
}

public void TurnPlayer(Vector3 position)
{
//Turn Player to new rotation over time
LeanTween.rotate(gameObject, position, StepSpeed);

}

#region Movement

public void MoveForward()
{
// calculate intended position. Get current direction and add one step forward
switch(CurrentDirection.ToLower())
{
case "n":
default:
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,1);
break;
}
case "e":
{
intendedPosition = CurrentGridPosition + new Vector3(1,0,0);
break;
}
case "s":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,-1);
break;
}
case "w":
{
intendedPosition = CurrentGridPosition + new Vector3(-1,0,0);
break;
}
}

//TODO: Check move is valid, if not, break
MoveTo(Grid.GridToWorld(intendedPosition)); //Actually move player
CurrentGridPosition = intendedPosition; //Update CurrentGridPosition
}

public void MoveBackward()
{
// calculate intended position. Get current direction and add one step backward
switch(CurrentDirection.ToLower())
{
case "n":
default:
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,-1);
break;
}
case "e":
{
intendedPosition = CurrentGridPosition + new Vector3(-1,0,0);
break;
}
case "s":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,1);
break;
}
case "w":
{
intendedPosition = CurrentGridPosition + new Vector3(1,0,0);
break;
}
}
//TODO: Check move is valid
MoveTo(Grid.GridToWorld(intendedPosition)); //Actually move player
CurrentGridPosition = intendedPosition; //Update CurrentGridPosition
}

public void StepLeft()
{
// calculate intended position. Get current direction and add one step left
switch(CurrentDirection.ToLower())
{
case "n":
default:
{
intendedPosition = CurrentGridPosition + new Vector3(-1,0,0);
break;
}
case "e":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,1);
break;
}
case "s":
{
intendedPosition = CurrentGridPosition + new Vector3(1,0,0);
break;
}
case "w":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,-1);
break;
}
}
//TODO: Check move is valid
MoveTo(Grid.GridToWorld(intendedPosition));//Actually move player
CurrentGridPosition = intendedPosition; //Update CurrentGridPosition
}

public void StepRight()
{
// calculate intended position. Get current direction and add one step right
switch(CurrentDirection.ToLower())
{
case "n":
default:
{
intendedPosition = CurrentGridPosition + new Vector3(1,0,0);
break;
}
case "e":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,-1);
break;
}
case "s":
{
intendedPosition = CurrentGridPosition + new Vector3(-1,0,0);
break;
}
case "w":
{
intendedPosition = CurrentGridPosition + new Vector3(0,0,1);
break;
}
}

//TODO: Check move is valid
MoveTo(Grid.GridToWorld(intendedPosition)); //Actually move player
CurrentGridPosition = intendedPosition; //Update CurrentGridPosition
}

public void TurnLeft()
{
//check if player is already turning
if(!enableRotation)
return;

enableRotation = false; //disable turn inputs

Vector3 intendedPosition = transform.localEulerAngles + new Vector3(0,-90,0);//calculate intended position after turning 

TurnPlayer(intendedPosition); //actually turn player

//Because player has turned, player direction has changed.  This updates to correct value
switch (CurrentDirection.ToLower())
{
default:
case "n":
CurrentDirection = "w";
break;
case "e":
CurrentDirection = "n";
break;
case "s":
CurrentDirection = "e";
break;
case "w":
CurrentDirection = "s";
break;
}
}

public void TurnRight()
{
//check if player is already turning
if(!enableRotation)
return;

enableRotation = false; //disable turn inputs

Vector3 intendedPosition = transform.localEulerAngles + new Vector3(0,90,0);//calculate intended position after turning 

TurnPlayer(intendedPosition); //actually turn player

//Because player has turned, player direction has changed.  This updates to correct value
switch (CurrentDirection.ToLower())
{
default:
case "n":
CurrentDirection = "e";
break;
case "e":
CurrentDirection = "s";
break;
case "s":
CurrentDirection = "w";
break;
case "w":
CurrentDirection = "n";
break;
}
}

#endregion

void Update()
{
//checks if player is moving, if not enables movement inputs
if(CurrentGridPosition == intendedPosition)
{
if(Input.GetButtonDown("Forward"))
{
MoveForward();
}
if(Input.GetButtonDown("Backward"))
{
MoveBackward();
}
if(Input.GetButtonDown("StepLeft"))
{
StepLeft();
}
if(Input.GetButtonDown("StepRight"))
{
StepRight();
}
}

//checks if player is turning, if not enables turning inputs
if(enableRotation)
{
if(Input.GetButtonDown("TurnLeft"))
{
TurnLeft();
}
if(Input.GetButtonDown("TurnRight"))
{
TurnRight();
}
}

//if player is turning, checks if turn has finished.
if(!enableRotation)
{
timer -= Time.deltaTime;
if(timer < 0)
{
enableRotation = true;
timer = StepSpeed;

}
}
}