MovementSystem.GridBasedMovement1 - robblofield/TomeboundDocs GitHub Wiki
This script stores movement-related variables and controls the X/Z movement and Y rotation of characters, objects and enemies that require grid-based movement. It forms part of the "MovementSystem".
- Tome.cs
- GameManager.cs
- CollisionChecker.cs
- Breakdown of code
- Variables
- Awake()
- TurnObject()
- MoveObject()
- Full Code Reference
private List<Vector3> origPoses = new List<Vector3>();
private List<Vector3> targetPoses = new List<Vector3>();
public float hoverDuration = 0.25f;
public float rayLength = 1.25f;
public bool isMoving = false;
public bool xLocked;
public bool yLocked;
public bool movementLocked = true;
public bool lockRotation = false;
private GameManager gameManager;
public List<Transform> allTransforms = new List<Transform>();
public CollisionChecker collision;
List origPosses: origPos is a list of the positions of the target object and other objects in sync before they move.
List targetPoses: targetPos is the location that the target object and other objects in sync will travel to.
float hoverDuration: This is the transit time taken for the target object to move from their origPos to targetPos. A value of 0.25f means that the object will take 0.25 seconds to travel between the positions.
float rayLength: This is the length of the ray that is cast during Physics.Raycast() to determine whether a collision will happen on the requested movement to targetPos.
bool isMoving: A boolean that is set true at the beginning of the MoveObject() coroutine to override the player input and other automated functions during the transit time.
bool xLocked: A boolean that locks the horizontal movement of the player character. Set true if the character was named "Wizard Y".
bool yLocked: A boolean that locks the vertical movement of the player character. Set true if the character was named "Wizard X".
bool movementLocked: If a boolean is set to false, the character can move in any direction regardless of the xLocked or yLocked set to true.
bool lockRotation: A boolean set during the MoveObject() coroutine.
bool ignoreCollider: A boolean that allows an object to clip through the walls. Can be used for debugging or for enemies that need to pass through objects.
GameManager gameManager: A standard reference to the GameManager.cs
List allTransforms: A list of every transform that moves simultaneously when MoveObject() coroutine has started.
public CollisionChecker collision: A standard reference to the CollisionChecker.cs
Finds the gameManager from the active scene and stores a reference to it. Then adds its own transform to the allTransforms.
void Awake()
{
gameManager = GameObject.FindObjectOfType<GameManager>().GetComponent<GameManager>();
allTransforms.Add(transform);
}
A coroutine to handle the rotation of the target object.
N.B - This coroutine is called outside of this script.
public IEnumerator TurnObject(Vector3 direction, float duration = -1)
{
float elapsedTime = 0;
Vector3 targetDirection = new Vector3(direction.x - transform.position.x, 0, direction.z - transform.position.z);
if(duration < 0){
duration = Mathf.Abs(hoverDuration);
}
while (elapsedTime < duration)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDirection), (elapsedTime / duration));
elapsedTime += Time.deltaTime;
yield return null;
}
transform.rotation = Quaternion.LookRotation(targetDirection)
}
float elapsedTime = 0;
Vector3 targetDirection = new Vector3(direction.x - transform.position.x, 0, direction.z - transform.position.z);
- It resets elapsedTime variable to 0.
- It gets targetDirection by subtracting x and z axis of the direction parameter with its own position.
if(duration < 0){
duration = Mathf.Abs(hoverDuration);
}
The parameter duration is originally set to -1 so that this coroutine can either be called with default hoverDuration (Absolute value) or custom duration if necessary.
while (elapsedTime < duration)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDirection), (elapsedTime / duration));
elapsedTime += Time.deltaTime;
yield return null;
}
A while loop runs for the set duration (a float determining how long the transition from origPos to targetPos should take).
The object rotate between its rotation and targetDirection over the "elapsedTime" divided by "duration" in order to set the transition value to a percentage of completion, thanks to the Quaternion.Slerp().
During the while loop, elapsedTime increases.
transform.rotation = Quaternion.LookRotation(targetDirection)
}
If for any reason the while loop has ended but the object has not reached its intended angle, a failsafe system to rotate the object to targetDirection instantly.
A coroutine to handle the movement of the target object. A breakdown of this codeblock is shown after the full version below.
N.B - This coroutine is called outside of this script.
public IEnumerator MoveObject(Vector3 direction, float duration = -1)
{
if(!isMoving){
isMoving = true;
float elapsedTime = 0;
int obstructedNum = 0;
if(duration < 0){
duration = hoverDuration;
}
Debug.Log(duration);
for(int i = 0; i < allTransforms.Count; i++){
//Collects all original/target positions from all the transforms in sync
origPoses.Add(allTransforms[i].position);
targetPoses.Add(origPoses[i] + direction);
//Running Raycast to check whether any colliders are in the way
if(collision.CheckCollidingObject(allTransforms[i], direction, null))
{
obstructedNum++;
for(int n = 0; n < allTransforms.Count; n++){
if(collision.collidingObject.transform == allTransforms[n]){
isMoving = true;
obstructedNum--;
}
}
}
}
if(obstructedNum > 0){
isMoving = false;
}
Debug.Log(isMoving);
Debug.Log(allTransforms);
if (isMoving)
{
while (elapsedTime < duration)
{
for (int i = 0; i < allTransforms.Count; i++){
if(targetPoses[i].x + targetPoses[i].z == origPoses[i].x + origPoses[i].z){
origPoses[i] = new Vector3(allTransforms[i].position.x, origPoses[i].y, allTransforms[i].position.z);
targetPoses[i] = new Vector3(allTransforms[i].position.x, targetPoses[i].y, allTransforms[i].position.z);
}
if(targetPoses[i].y == origPoses[i].y){
origPoses[i] = new Vector3(origPoses[i].x, allTransforms[i].position.y, origPoses[i].z);
targetPoses[i] = new Vector3(targetPoses[i].x, allTransforms[i].position.y, targetPoses[i].z);
}
allTransforms[i].position = Vector3.Lerp(origPoses[i], targetPoses[i], (elapsedTime / duration));
if (!lockRotation && allTransforms.Count <= 1)
{
allTransforms[i].rotation = Quaternion.Slerp(allTransforms[i].rotation, Quaternion.LookRotation(direction), (elapsedTime / duration));
}
}
elapsedTime += Time.deltaTime;
yield return null;
}
for (int i = 0; i < allTransforms.Count; i++){
allTransforms[i].position = targetPoses[i];
if (!lockRotation && allTransforms.Count <= 1)
{
allTransforms[i].rotation = Quaternion.LookRotation(direction);
}
}
}
origPoses.Clear();
targetPoses.Clear();
}
isMoving = false;
// Debug.Log("Movement Completed");
yield return null;
}
if(!isMoving){
isMoving = true;
float elapsedTime = 0;
int obstructedNum = 0;
The codeblock is mostly contained within an "if(!isMoving)" statement to ensure code is only executed while the object is stationary.
- Boolean isMoving is set to true.
- float elapsedTime is restarted by setting it to 0.
- origPos is set to the current transform.position.
- targetPos takes the "direction" parameter fed into the coroutine and adds it to the origPos
if(duration < 0){
duration = hoverDuration;
}
The parameter duration is originally set to -1 so that this coroutine can either be called with default hoverDuration (Absolute value) or custom duration if necessary.
for(int i = 0; i < allTransforms.Count; i++){
//Collects all original/target positions from all the transforms in sync
origPoses.Add(allTransforms[i].position);
targetPoses.Add(origPoses[i] + direction);
//Running Raycast to check whether any colliders are in the way
if(collision.CheckCollidingObject(allTransforms[i], direction, null))
{
obstructedNum++;
for(int n = 0; n < allTransforms.Count; n++){
if(collision.collidingObject.transform == allTransforms[n]){
isMoving = true;
obstructedNum--;
}
}
}
}
if(obstructedNum > 0){
isMoving = false;
}
The for loop goes on until running raycast on all the object in the list allTransforms. A ray is cast between the "transform.position" and "direction" at a set length.
If the ray hits something, the integer obstructedNum is incremented, unless the collided object is one of the list allTransforms.
If the ray hits nothing, obstructedNum is decremented.
After the for loop, if the number of objects that collided with the raycast is more than 0, isMoving is set to false.
if (isMoving)
{
while (elapsedTime < duration)
{
for (int i = 0; i < allTransforms.Count; i++){
if(targetPoses[i].x + targetPoses[i].z == origPoses[i].x + origPoses[i].z){
origPoses[i] = new Vector3(allTransforms[i].position.x, origPoses[i].y, allTransforms[i].position.z);
targetPoses[i] = new Vector3(allTransforms[i].position.x, targetPoses[i].y, allTransforms[i].position.z);
}
if(targetPoses[i].y == origPoses[i].y){
origPoses[i] = new Vector3(origPoses[i].x, allTransforms[i].position.y, origPoses[i].z);
targetPoses[i] = new Vector3(targetPoses[i].x, allTransforms[i].position.y, targetPoses[i].z);
}
allTransforms[i].position = Vector3.Lerp(origPoses[i], targetPoses[i], (elapsedTime / duration));
if (!lockRotation && allTransforms.Count <= 1)
{
allTransforms[i].rotation = Quaternion.Slerp(allTransforms[i].rotation, Quaternion.LookRotation(direction), (elapsedTime / duration));
}
}
elapsedTime += Time.deltaTime;
yield return null;
}
The codeblock is mostly contained within an if statement to make sure none of the objects is in their way and isMoving is still true.
A while loop that runs for the duration (a float determining how long the transition from origPos to targetPos should take).
The for loop goes on until the transition of every object in the list allTransforms is completed.
If the x or z axis of origPos or targetPos is the equal then it updates x or z axis of its position. It's mainly utilised when other MoveObject() for yAxis called while this coroutine is still executing.
If the y axis of origPos or targetPos is the equal then it updates y axis of its position. It's mainly utilised when other MoveObject() for x or z axis called while this coroutine is still executing.
The object Lerps between origPos and targetPos over the "elapsedTime" divided by "hoverDuration" in order to set the transition value to a percentage of completion.
In cases where objects are not supposed to rotate (e.g. player holding book, the book itself, grabbed objects) their rotation is locked. If the object does not have locked rotation or the length of the list allTransforms is less or equal to 1, we rotate them towards their direction of travel via Quaternion.Slerp().
During the while loop, elapsedTime increases.
for (int i = 0; i < allTransforms.Count; i++){
allTransforms[i].position = targetPoses[i];
if (!lockRotation && allTransforms.Count <= 1)
{
allTransforms[i].rotation = Quaternion.LookRotation(direction);
}
}
If for any reason the while loop has ended but objects have not reached their intended location, a failsafe system to teleport all the objects to their targetPos and set their final rotation is executed.
}
origPoses.Clear();
targetPoses.Clear();
}
After the end of for loop, all the data within the list for the original positions and target positions are cleared for the next coroutine.
isMoving = false;
yield return null;
After the while loop has ended and all moving has been completed, isMoving is set to false and the coroutine is ended.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GridBasedMovement1 : MonoBehaviour
{
//This script handles basic grid based movement ONLY. No other actions go here.
//This script requires CollisionChecker.cs to funtion properly.
private List<Vector3> origPoses = new List<Vector3>();
private List<Vector3> targetPoses = new List<Vector3>();
public float hoverDuration = 0.25f;
public float rayLength = 1.25f;
public bool isMoving = false;
public bool xLocked;
public bool yLocked;
public bool movementLocked = true;
public bool lockRotation = false;
private GameManager gameManager;
//Variable for all transforms in sync
public List<Transform> allTransforms = new List<Transform>();
//Reference for Collision Checker
public CollisionChecker collision;
//Finds GameManager from the scene and gets component from the start of the game
void Awake()
{
gameManager = GameObject.FindObjectOfType<GameManager>().GetComponent<GameManager>();
allTransforms.Add(transform);
}
//Turns Object at set direction
public IEnumerator TurnObject(Vector3 direction, float duration = -1)
{
float elapsedTime = 0;
Vector3 targetDirection = new Vector3(direction.x - transform.position.x, 0, direction.z - transform.position.z);
if(duration < 0){
duration = Mathf.Abs(hoverDuration);
}
while (elapsedTime < duration)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDirection), (elapsedTime / duration));
elapsedTime += Time.deltaTime;
yield return null;
}
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDirection), (elapsedTime / duration));
}
//Moves Object at set direction
public IEnumerator MoveObject(Vector3 direction, float duration = -1)
{
if(!isMoving){
isMoving = true;
float elapsedTime = 0;
int obstructedNum = 0;
if(duration < 0){
duration = hoverDuration;
}
Debug.Log(duration);
for(int i = 0; i < allTransforms.Count; i++){
//Collects all original/target positions from all the transforms in sync
origPoses.Add(allTransforms[i].position);
targetPoses.Add(origPoses[i] + direction);
//Running Raycast to check whether any colliders are in the way
if(collision.CheckCollidingObject(allTransforms[i], direction, null))
{
obstructedNum++;
for(int n = 0; n < allTransforms.Count; n++){
if(collision.collidingObject.transform == allTransforms[n]){
isMoving = true;
obstructedNum--;
}
}
}
}
if(obstructedNum > 0){
isMoving = false;
}
Debug.Log(isMoving);
Debug.Log(allTransforms);
if (isMoving)
{
while (elapsedTime < duration)
{
for (int i = 0; i < allTransforms.Count; i++){
if(targetPoses[i].x + targetPoses[i].z == origPoses[i].x + origPoses[i].z){
origPoses[i] = new Vector3(allTransforms[i].position.x, origPoses[i].y, allTransforms[i].position.z);
targetPoses[i] = new Vector3(allTransforms[i].position.x, targetPoses[i].y, allTransforms[i].position.z);
}
if(targetPoses[i].y == origPoses[i].y){
origPoses[i] = new Vector3(origPoses[i].x, allTransforms[i].position.y, origPoses[i].z);
targetPoses[i] = new Vector3(targetPoses[i].x, allTransforms[i].position.y, targetPoses[i].z);
}
allTransforms[i].position = Vector3.Lerp(origPoses[i], targetPoses[i], (elapsedTime / duration));
if (!lockRotation && allTransforms.Count <= 1)
{
allTransforms[i].rotation = Quaternion.Slerp(allTransforms[i].rotation, Quaternion.LookRotation(direction), (elapsedTime / duration));
}
}
elapsedTime += Time.deltaTime;
yield return null;
}
for (int i = 0; i < allTransforms.Count; i++){
allTransforms[i].position = targetPoses[i];
if (!lockRotation && allTransforms.Count <= 1)
{
allTransforms[i].rotation = Quaternion.LookRotation(direction);
}
}
}
origPoses.Clear();
targetPoses.Clear();
}
isMoving = false;
// Debug.Log("Movement Completed");
yield return null;
}
}