Skip to content

Instantly share code, notes, and snippets.

@IronMonk-UK
Created February 20, 2016 16:06
Show Gist options
  • Save IronMonk-UK/84315d7e8409d7208753 to your computer and use it in GitHub Desktop.
Save IronMonk-UK/84315d7e8409d7208753 to your computer and use it in GitHub Desktop.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
public class GameManager : MonoBehaviour {
// Creates a public static variable for the GameManager class
// A static class will mean that any changes made to the GameManager will affect all copies of the variable
public static GameManager instance;
// A game object containing the tile object prefab
public GameObject tilePrefab;
// A game object containing the prefab for player units
public GameObject userUnitPrefab;
/*public Text _name;
public Text _class;
public Text _health;
public Text _strength;
public Text _defence;
public Text _accuracy;
public Text _eName;
public Text _eClass;
public Text _eHealth;
public Text _eStrength;
public Text _eDefence;
public Text _eAccuracy;
public Text announcer;*/
// Dictates the width and length of the map
public int mapSize;
public bool startMove;
bool posHit;
bool negHit;
bool hit;
// Creates a list containing a list of tiles for the creation of the map
public List<List<Tile>> map = new List<List<Tile>>();
// Creates a list containing players, to populate the game with player units
public List<Unit> units = new List<Unit>();
public List<Vector2> moveTiles = new List<Vector2>();
public Grid grid;
[SerializeField]
// How many units are initially in the current Player Index
public int currentUnitIndex = 0;
void Awake(){
// Makes the instance variable the current GameManager class
instance = this;
grid = GameObject.FindWithTag("A*").GetComponent<Grid>();
//EnemyStatsDown ();
}
void Start () {
// Runs the generateMap function on start
//generateMap ();
// Runs the generatePlayers function on start
generateUnits ();
//announcer.text = "";
}
void Update () {
/* Looks at which player in the player list is currently moving
* and constantly runs the turnUpdate function for them, as it is in the update function */
if (units [currentUnitIndex].health > 0) {
units [currentUnitIndex].turnUpdate ();
} else {
nextTurn ();
}
// Sets the UI text to show the current units stats
/*_name.text = units [currentUnitIndex]._name;
_class.text = units [currentUnitIndex]._class;
_health.text = units [currentUnitIndex].health.ToString();
_strength.text = units [currentUnitIndex].strength.ToString();
_defence.text = units [currentUnitIndex].defence.ToString();
_accuracy.text = units [currentUnitIndex].accuracy.ToString();*/
if (units [currentUnitIndex].moved)
units [currentUnitIndex].moving = false;
if(units [currentUnitIndex].moving == false)
{
foreach (Vector3 v in moveTiles)
{
foreach (List<Tile> l in map)
{
foreach (Tile t in l)
{
if (v == t.gridPosition)
{
t.movingTile = false;
}
}
}
}
}
}
/* The nextTurn function determines which player is currently having their turn
* The current player index starts at 0, so must have +1 added to it when counting players so both start from 1.
* When the nextTurn function is run, if the player index + 1 is lower than the amount in the players list
* it will increase the index by one, causing the turnUpdate function above to start for a new player.
* If the index + 1 ends up equal to the amount of players in the list, it will start back to 0.*/
/* While this is not my ideal turn system, I believe that as I progress with my research, it will help me form a base towards the operation of the AI
* As this is one potential way I could have enemy units moving one at a time as an index goes through a list of enemies */
public void nextTurn(){
if (currentUnitIndex + 1 < units.Count) {
currentUnitIndex++;
} else {
currentUnitIndex = 0;
}
}
/* The moveCurrentPlayer function sets the current player units moveDestination position as the destTile, as set when the player clicks on a tile through the Tile class
* OnMouseDown function. */
/* The moveCurrentPlayer function now runs through an if statement to make sure the current unit has not already moved. If it has not, they will use one action, and move to the
* destTile. This function also sets the current units gridPosition to be that of the destTile's gridPosition, and then sets the moved boolean to true,
* so that unit cannot move again. */
public void moveCurrentPlayer(){
/*if (units [currentUnitIndex].moved == false) {
units [currentUnitIndex].actions --;
units [currentUnitIndex].moveDestination = destTile.transform.position + Vector3.up;
units [currentUnitIndex].gridPosition = destTile.gridPosition;
units [currentUnitIndex].moved = true;
}*/
if (units[currentUnitIndex].moved == false) {
/* for(int i = 0; i < grid.path.Count; i++) {
units[currentUnitIndex].transform.position = grid.path[i].tile.transform.position + Vector3.up;
Debug.Log("Moved.");
}*/
/*foreach(Node n in grid.path) {
units[currentUnitIndex].transform.position = n.tile.transform.position;
}*/
units[currentUnitIndex].transform.position = grid.path[grid.path.Count - 1].tile.transform.position;
}
}
public void attackWithCurrentPlayer(Tile destTile){
// The attackWithCurrentPlayer function sets the rules of combat within the game, dealing with how the currently selected player can engage in combat.
/* The variable target is created first. A foreach loop then runs, looking for any unit that has the same gridPosition as the tile being selected,
* as rather than targetting a unit, the tile they are on is selected. If a unit is found, they are set as the target. */
Unit target = null;
foreach (Unit u in units) {
if (u.gridPosition == destTile.gridPosition){
target = u;
}
}
/* So as not to create a giant line of specific gridPositions that can be attacked, I created 4 Vector3 variables, and implemented them into it.
* Each Vector3 represents a space above, below, left, or right, of the current units position */
Vector3 up = new Vector2 (target.gridPosition.x, target.gridPosition.y - 1);
Vector3 down = new Vector2 (target.gridPosition.x, target.gridPosition.y + 1);
Vector3 left = new Vector2 (target.gridPosition.x -1, target.gridPosition.y);
Vector3 right = new Vector2 (target.gridPosition.x +1, target.gridPosition.y);
/* This if statement starts off by making sure that there is a target available. If so, the next if statement ensures that they are in a position that can be attacked by the
* current unit. If this returns true, an action is used by the current unit. A series of if statements are run after this, checking if the units class is weak against them,
* strong against them, or the same. At this point in time, units gain or lose accuracy based on this, and sets one of three booleans, based on the classes.
* THIS IS SUBJECT TO CHANGE. */
if (target != null) {
if(units [currentUnitIndex].gridPosition == up || units [currentUnitIndex].gridPosition == down || units [currentUnitIndex].gridPosition == left || units [currentUnitIndex].gridPosition == right){
units [currentUnitIndex].actions --;
if(units [currentUnitIndex].posClass == target._class){
posHit = Random.Range (0.0f, 1.0f) <= (units[currentUnitIndex].accuracy + .2f);
}else if(units [currentUnitIndex].negClass == target._class){
negHit = Random.Range (0.0f, 1.0f) <= (units[currentUnitIndex].accuracy - .2f);
}else{
hit = Random.Range (0.0f, 1.0f) <= units[currentUnitIndex].accuracy;
}
/* This set of if statements breaks down the damage dealt to an opposing unit if the unit hits. Based on the boolean activated above, damage will be dealt with
* positive or negative modifiers, or no modifers. If the above hit does not return true, the attack simply misses. If the unit being attacked is not on an
* adjacent tile, the debug log will return stating the target is not available. */
if(posHit){
int damageAmount = (int)Mathf.Floor(units [currentUnitIndex].strength + Random.Range(2, units [currentUnitIndex].damageRollSides + 2 ) - target.defence);
target.health -= damageAmount;
//announcer.text = (units[currentUnitIndex]._name + " hit " + target._name.ToString() + " effectively for " + damageAmount + " damage!");
Debug.Log (units [currentUnitIndex]._name + " hit " + target._name + " effectively for " + damageAmount + " damage!");
}else if(negHit){
int damageAmount = (int)Mathf.Floor(units [currentUnitIndex].strength + Random.Range(0, units [currentUnitIndex].damageRollSides - 1 ) - target.defence);
target.health -= damageAmount;
//announcer.text = (units[currentUnitIndex]._name + " hit " + target._name.ToString() + " ineffectively for " + damageAmount + " damage!");
Debug.Log (units [currentUnitIndex]._name + " hit " + target._name + " ineffectively for " + damageAmount + " damage!");
} else if (hit) {
int damageAmount = (int)Mathf.Floor(units [currentUnitIndex].strength + Random.Range(0, units [currentUnitIndex].damageRollSides) - target.defence);
target.health -= damageAmount;
//announcer.text = (units[currentUnitIndex]._name + " hit " + target._name.ToString() + " for " + damageAmount + " damage!");
Debug.Log (units [currentUnitIndex]._name + " hit " + target._name + " for " + damageAmount + " damage!");
} else {
Debug.Log (target._name + " miss.");
}
} else {
Debug.Log("Target not available");
}
}
}
// The generateMap function is a function set to run on start, creating the map for the game
/*void generateMap (){
// Creates a new map list
map = new List<List<Tile>>();
// Runs the statement inside the amount of times equals to the mapsize
for (int i = 0; i < mapSize; i++)
{
// Declares the Tile lists inside the map list
List<Tile> row = new List<Tile>();
// Runs the statement inside the amount of times equals to the mapsize
for (int j = 0; j < mapSize; j++)
{*/
/* Creates a new copy of the Tile class while declaring the new variable tile, creating a new copy of the tile prefab. A new vector3 is created, using the current int i minus the
* mapSize divided by 2 for x, 0 for y as the map is presently set for a single level, and minus-j plus the mapSize divided by 2 for z. This determines where the tiles are actually going
* to spawn based on on global unity co-ordinates. The Mathf.Floor will return the largest integer possible that is smaller or equals to the bracket conditions. The Quaternion.Euler sets
* the rotation of the objects, but as they are squares going into a grid, this is left blank as they have no need to be roatated. Finally, the Tile class component is added to the
* instantiated prefab. */
/*Tile tile = ((GameObject)Instantiate(tilePrefab, new Vector3(i - Mathf.Floor(mapSize/2), 0, -j + Mathf.Floor(mapSize/2)), Quaternion.Euler(new Vector3()))).GetComponent<Tile>();
// The new tile is given its gridPosition Vector2 based on what the current int i and int j are
tile.gridPosition = new Vector2(i, j);
// Adds the tile to the row list
row.Add (tile);
}
// Adds the row list to the map list
map.Add(row);
}
}*/
// The generatePlayers function is used to place the player units into the game
void generateUnits (){
// This sets new players added to the players list to use the UserPlayer class
UserUnit unit;
// Follows the same formula as used for the generateMap function, but instead positions this player unit in the first available grid position, and the one below in the last possible grid position
unit = ((GameObject)Instantiate(userUnitPrefab, new Vector3(0 - Mathf.Floor(mapSize/2) + .5f, 1, -0 + Mathf.Floor(mapSize/2) - .5f), Quaternion.Euler(new Vector3()))).GetComponent<UserUnit>();
unit.gridPosition = new Vector2 (0, 0);
// A player name is set. THIS IS FOR FUTURE USE ONCE THE UI HAS BEEN FURTHER DEVELOPED. The units _class is also set, changing stats in the Player class Start function.
unit._name = "Bentley";
unit._class = "Barbarian";
// The _class function is run to assign stats, depending on the class selected
_Class.Barbarian (unit);
// Adds the new player to the players list
units.Add (unit);
unit = ((GameObject)Instantiate(userUnitPrefab, new Vector3((mapSize-1) - Mathf.Floor(mapSize/2) + .5f, 1, -(mapSize-1) + Mathf.Floor(mapSize/2) - .5f), Quaternion.Euler(new Vector3()))).GetComponent<UserUnit>();
unit.gridPosition = new Vector2 (mapSize - 1, mapSize-1);
unit._name = "Harrison";
unit._class = "Mercenary";
_Class.Mercenary (unit);
units.Add (unit);
unit = ((GameObject)Instantiate(userUnitPrefab, new Vector3(4 - Mathf.Floor(mapSize/2) + .5f, 1, -4 + Mathf.Floor(mapSize/2) - .5f), Quaternion.Euler(new Vector3()))).GetComponent<UserUnit>();
unit.gridPosition = new Vector2 (mapSize/2 - 1, mapSize/2 - 1);
unit._name = "Alexander";
unit._class = "Knight";
_Class.Knight (unit);
units.Add (unit);
}
// These three functions are for use with the UI buttons, calling on the current unit and changing states within it, so it is either moving, attacking, or its turn has ended.
public void Move(){
if (units [currentUnitIndex].moving == false) {
units [currentUnitIndex].moving = true;
units [currentUnitIndex].attacking = false;
} else {
units [currentUnitIndex].moving = false;
units [currentUnitIndex].attacking = false;
}
}
public void Attack(){
if (units [currentUnitIndex].attacking == false) {
units [currentUnitIndex].attacking = true;
units [currentUnitIndex].moving = false;
} else {
units [currentUnitIndex].moving = false;
units [currentUnitIndex].attacking = false;
}
}
public void EndTurn(){
units [currentUnitIndex].moving = false;
units [currentUnitIndex].attacking = false;
units [currentUnitIndex].actions = 2;
units [currentUnitIndex].moved = false;
nextTurn ();
}
/*public void EnemyStatsUp(){
_eName.enabled = true;
_eClass.enabled = true;
_eHealth.enabled = true;
_eStrength.enabled = true;
_eDefence.enabled = true;
_eAccuracy.enabled = true;
}
public void EnemyStatsDown(){
_eName.enabled = false;
_eClass.enabled = false;
_eHealth.enabled = false;
_eStrength.enabled = false;
_eDefence.enabled = false;
_eAccuracy.enabled = false;
}*/
/* The MovingTile function is run through the Tile class. If the current tile can be moved to, this function will run, changing that tile to green, indicating that it is part of
* the movement path. The bool movingTile is turned true on the Tile class, ensuring that the specific tile knows it is a "moving" tile. The foreach loop then goes through every
* tile, setting the prevMovePos to the last tile moved to, so the tiles know which ones can be moved to within the class. */
public void MovingTile(Tile movedTile){
startMove = true;
movedTile.movingTile = true;
foreach (List<Tile> l in map) {
foreach (Tile t in l){
t.prevMovePos = movedTile.gridPosition;
}
}
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Grid : MonoBehaviour {
// Defines how much worldspace the grid is going to cover
public Vector2 worldSize;
// Defines the radius the node covers
public float nodeRadius;
// Defines the diameter of the node; how much area is actually covered by one node
float nodeDiameter;
int gridSizeX, gridSizeY;
// Creates a variable for the layer that terrain objects, the currently unpassable objects, will be set to
public LayerMask terrainMask;
// A 2D array of nodes
Node[,] grid;
public GameObject tilePrefab;
public List<List<Tile>> map = new List<List<Tile>>();
public List<Node> path = new List<Node>();
[SerializeField]
void Start() {
// Sets the diameter
nodeDiameter = nodeRadius * 2;
/* Defines how many nodes can be fit into the grid, by taking the lengths of x & y, and dividing it by the nodeDiameter; the size of the node. This is rounded to an integer, ensuring only whole
* nodes are taken into account. */
gridSizeX = Mathf.RoundToInt(worldSize.x / nodeDiameter);
gridSizeY = Mathf.RoundToInt(worldSize.y / nodeDiameter);
// Runs the function to create the node grid
CreateGrid();
}
void Update() {
foreach(Node n in path) {
Debug.Log(n.worldPos);
}
/*if (grid != null) {
foreach (List<Tile> l in map) {
foreach (Tile t in l) {
t.rend.material.color = (t.node.walkable) ? Color.white : Color.red;
if (path != null)
if (path.Contains(t.node))
t.rend.material.color = Color.black;
}
}
}*/
if (grid != null) {
foreach (Node n in grid) {
n.tile.rend.material.color = (n.walkable) ? Color.white : Color.red;
if (path != null)
if (path.Contains(n))
n.tile.rend.material.color = Color.black;
}
}
}
void CreateGrid(){
map = new List<List<Tile>>();
// Sets the grid to a new Node array
grid = new Node[gridSizeX, gridSizeY];
// Defines the bottom left corner of the grid
// worldBottomLeft = (0 , 0, 0) - (1, 0, 0) * (30, 0, 0) / 2 - (0 ,0, 1) * (0, 0, 30) / 2;
// This returns the result (-15, 0, -15)
Vector3 worldBottomLeft = transform.position - Vector3.right * worldSize.x / 2 - Vector3.forward * worldSize.y / 2;
// Debug.Log(worldBottomLeft);
// Creates a nested for loop, looking at the first entry in X, that loops through all related Y entries, then moves onto the next X entry
for (int x = 0; x < gridSizeX; x++) {
List<Tile> row = new List<Tile>();
for (int y = 0; y < gridSizeX; y++) {
// worldPoint = (-15, 0, -15) + (1, 0, 0) * ( (1 * 1 + 0.5) + (0, 0, 1) * (1 * 1 + 0.5);
// This function is used to work out at which point in the world the node is going to be placed, with the x and y values being used to push the world point around the grid
Vector3 worldPoint = worldBottomLeft + Vector3.right * (x * nodeDiameter + nodeRadius) + Vector3.forward * (y * nodeDiameter + nodeRadius);
/* This checks what status the walkable bool on the node should be. A sphere is created around the current node worldpoint, using the radius as the sphere radius, that looks to see if it is
* colliding with anything attached to the terrainMask layer. If false, the node is walkable. */
bool walkable = !(Physics.CheckSphere(worldPoint, nodeRadius, terrainMask));
/* The node is pushed into the grid at the position, with the walkable bool being set, the nodes worldpoint, and its x and y positions on the grid being set through its position in the loops
* defined by x and y. */
Tile tile = ((GameObject)Instantiate(tilePrefab, new Vector3(worldPoint.x, -1, worldPoint.z), Quaternion.Euler(new Vector3()))).GetComponent<Tile>();
grid[x, y] = new Node(walkable, worldPoint, x, y, tile);
tile.gridPosition = new Vector2(x, y);
tile.node = grid[x, y];
row.Add(tile);
}
map.Add(row);
}
}
// A method that returns a list of nodes that surround the current node
public List<Node> GetNeighbours(Node node) {
// Set the list as a new list
List<Node> neighbours = new List<Node>();
// Creates a loop that looks in a 3x3 area around the current node
for (int x = -1; x <= 1; x++) {
for(int y = -1; y <= 1; y++) {
// This if statement skips over the centre node, as that is the current node
if (x == 0 && y == 0)
continue;
// Creates 2 integers, that start in the bottom left node in the 3x3 grid, working upwards in columns, then across, due to the for loops
int checkX = node.gridX + x;
int checkY = node.gridY + y;
// Ensure that the node is on the grid correctly, then adds it to the neighbours list
if(checkX >= 0 && checkX < gridSizeX && checkY >= 0 && checkY < gridSizeY) {
neighbours.Add(grid[checkX, checkY]);
}
}
}
// Returns the list once the loop is complete
return neighbours;
}
// Method that converts the world position of a node to a grid position
public Node NodeFromWorldPoint(Vector3 worldPos) {
// Works out how far along the axis a node is, with 0 for far left/bottom, .5 for middle, 1 for far right/top, and converts them into a percentage
// Takes in the position of the node through worldPos
// percentX is equal to the x position of the node, plus the total x size of the grid, divided by 2, all divided by the x size of the grid
// ((-15, 0, 0) + (30, 0, 0) / 2) / (30, 0, 0) = 0 / 30 = 0;
float percentX = (worldPos.x + worldSize.x / 2) / worldSize.x;
float percentY = (worldPos.z + worldSize.y / 2) / worldSize.y;
// Ensures the values are always between 0 and 1, so if a seeker/target is off the grid, the values do not error
percentX = Mathf.Clamp01(percentX);
percentY = Mathf.Clamp01(percentY);
/* Gets the x and y of the grid array. The gridSize's are taken, with minus 1, due to arrays starting at 0, and are multiplied by the percent worked out above. These are rounded to integers, to work
* with the array */
int x = Mathf.RoundToInt((gridSizeX - 1) * percentX);
int y = Mathf.RoundToInt((gridSizeY - 1) * percentY);
// This returns the node to the grid with x and y set
return grid[x, y];
}
// Gizmos are used to aid in the scene development side, not showing in the actual game. OnDrawGizmos is a function within MonoBehaviour, that applies any gizmos created to the scene
void OnDrawGizmos() {
// The first gizmo is a wire cube, that shows the area being covered by the grid. As the worldSize is altered in the inspector, the wire cube gizmo alters with it
Gizmos.DrawWireCube(transform.position, new Vector3(worldSize.x, 1, worldSize.y));
/* This if statement first looks to make sure there is something within the grid array. If true, a foreach loop is started, looking at every node in the grid. Depending on if the node is walkable
* or not, the colour of the node will change. If there is a path formed between the seeker transform and target transform, that path will be coloured black. Once the colours have been defined,
* the cubes are drawn into the scene editor. */
if(grid != null) {
foreach (Node n in grid)
{
Gizmos.color = (n.walkable) ? Color.white : Color.red;
if (path != null)
if (path.Contains(n))
Gizmos.color = Color.black;
Gizmos.DrawCube(n.worldPos, Vector3.one * (nodeDiameter - .1f));
}
}
}
}
using UnityEngine;
using System.Collections;
public class Node {
// Declares if the node can be walked through or not
public bool walkable;
public Vector3 worldPos;
// The distance from the starting node
public int gCost;
// The distance to the target node
public int hCost;
// Allows the node to know its own location on the 2D array
public int gridX;
public int gridY;
public Node parent;
public Tile tile;
// Assigns the values of the node to the class
public Node(bool _walkable, Vector3 _worldPos, int _gridX, int _gridY, Tile _tile) {
walkable = _walkable;
worldPos = _worldPos;
gridX = _gridX;
gridY = _gridY;
tile = _tile;
}
// The total cost of a node. This will never be set, as it is worked out through the calculations of gCost + hCost
public int fCost {
get {
return gCost + hCost;
}
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Pathfinding : MonoBehaviour {
// Reference to the grid class
Grid grid;
public Transform seeker, target;
void Awake() {
// Sets the grid variable to the Grid class attached to the same gameObject
grid = GetComponent<Grid>();
}
void Update()
{
seeker = GameManager.instance.units[GameManager.instance.currentUnitIndex].gameObject.transform;
// Finds the path between the seeker position and target position
FindPath(seeker.position, target.position);
}
// The method that finds the path between a starting position, and an end position
void FindPath(Vector3 startPos, Vector3 targetPos) {
// Stores 2 nodes in the method as the starting and end position, from the nodes input at the beginning of the method
Node startNode = grid.NodeFromWorldPoint(startPos);
Node targetNode = grid.NodeFromWorldPoint(targetPos);
/* Creates a list to contain open nodes, those that have not yet been evaluated, and a HashSet to contain nodes that have already been evaluated. A list is used for the open data, as this will be
* be the container that is searched the most, allowing for a more efficient search. A hashSet is used for the closed data, as this is rarely looked upon or changed once a node is entered, other than
* to ensure that a unique entry is already in there. */
// Information obtained from http://geekswithblogs.net/BlackRabbitCoder/archive/2011/06/16/c.net-fundamentals-choosing-the-right-collection-class.aspx
List<Node> openSet = new List<Node>();
HashSet<Node> closedSet = new HashSet<Node>();
// The first node is added to the open data, ensuring the count is above 0 and the loop starts
openSet.Add(startNode);
// This is the loop that determines the best path to take
while (openSet.Count > 0) {
// The variable currentNode is set to the first entry of the openSet
Node currentNode = openSet[0];
// A for loop starts, going through the entries in the open data
for (int i = 1; i < openSet.Count; i++) {
/* if the node i total cost (fCost) is less than the current node fCost, or the node i fCost is equal to the current node fCost & node i's distance to target (hCost) is less than the
* current nodes, the current node becomes node i. */
if (openSet[i].fCost < currentNode.fCost || openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost) {
currentNode = openSet[i];
}
}
// Removes the current node from the open data to the closed data
openSet.Remove(currentNode);
closedSet.Add(currentNode);
// If the path is complete, leave the loop
if(currentNode == targetNode) {
RetracePath(startNode, targetNode);
return;
}
// This loop goes through all the nodes in the GetNeighbours list in the Grid class, for the current node
foreach (Node n in grid.GetNeighbours(currentNode)) {
// If the neighbour is not walkable, or already closed data, the loop continues
if(!n.walkable || closedSet.Contains(n)) {
continue;
}
// This integer is distance cost from the neighbour node (n). The cost of the current node is added to the distance between it and the neighbour
int newCostToN = currentNode.gCost + GetDistance(currentNode, n);
// If the new distance cost is less than the neighbours old cost, or the neighbour node is not in the open data
if(newCostToN < n.gCost || !openSet.Contains(n)) {
// New variables are set for the neighbour, with both a new gCost and hCost being set, the hCost checked through the GetDistance method, and the current node is made its parent node
n.gCost = newCostToN;
n.hCost = GetDistance(n, targetNode);
n.parent = currentNode;
// If the neighbour is not in the open data, it is added
if (!openSet.Contains(n))
openSet.Add(n);
}
}
}
}
// Builds a path from the target node
void RetracePath(Node startN, Node endN) {
// The nodes that will be travelled along
List<Node> path = new List<Node>();
// Sets the target node as the current node
Node currentN = endN;
// A loop that runs until the current node is the starting node, adding the current node to the path list, then looks to the parent of that node for the shortest path, and sets it as the current node
while (currentN != startN)
{
path.Add(currentN);
currentN = currentN.parent;
}
// Sets the order of the path list to be in the correct order, from start node to end node
path.Reverse();
//Pushes the path list to the path list in the Grid class
grid.path = path;
}
// Gets the current distance between 2 nodes
int GetDistance(Node nodeA, Node nodeB) {
// Creates 2 integers that work out the distance for both x and y axis between nodeA and nodeB, ensuring they're absolute
int dstX = Mathf.Abs(nodeA.gridX - nodeB.gridX);
int dstY = Mathf.Abs(nodeA.gridY - nodeB.gridY);
// if dstX is greater than dstY, using 14y + 10, multiplied by (dstX - dstY)
/* NodeA = (5, 4). NodeB = (4, 7). dstX = 1. dstY = -3
* 1 > -3
* return (14 * -3 =)-42 + (10 * (1 - -3 =)4)
* -42 + 40 = -2
*
* The shortest distance is taken into account first, moving diagonally towards as represented by the cost of 14. The cost of vertical/horizontal movements is worked out next, by taking the standard
* cost of 10, and applying it to however many spaces are left once the lowest distance is subtracted from the highest, as diagonal movement reduces the amount of distance between them. */
if (dstX > dstY)
return 14 * dstY + 10 * (dstX - dstY);
return 14 * dstX + 10 * (dstY - dstX);
}
}
using UnityEngine;
using System.Collections;
public class Tile : MonoBehaviour {
// The gridPosition are Vectors set in the GameManager.
public Vector3 gridPosition = Vector2.zero;
public Vector3 prevMovePos = Vector2.zero;
public Renderer rend;
public bool movingTile;
public bool blue;
public Node node;
public Vector2 nodePos;
public Pathfinding pathfinding;
public Grid grid;
void Start () {
pathfinding = GameObject.FindWithTag("A*").GetComponent<Pathfinding>();
grid = GameObject.FindWithTag("A*").GetComponent<Grid>();
rend = GetComponent<Renderer> ();
nodePos = new Vector2(node.gridX, node.gridY);
}
void Update () {
if (movingTile)
rend.material.color = Color.green;
if(!movingTile && !blue)
rend.material.color = Color.white;
}
void OnMouseEnter (){
if(GameManager.instance.units[GameManager.instance.currentUnitIndex].moving) {
pathfinding.target.transform.position = new Vector3(gameObject.transform.position.x, 1, gameObject.transform.position.z);
}
// The below if functions simply change the colour of the tile that the mouse is over, depending on if the current unit is in a moving state, or attacking state.
if (!blue && !GameManager.instance.units[GameManager.instance.currentUnitIndex].attacking) {
blue = true;
rend.material.color = Color.blue;
} else if (GameManager.instance.units [GameManager.instance.currentUnitIndex].attacking) {
rend.material.color = Color.red;
}
// If the tile current being moused over contains a unit, their stats will be displayed in UI text
foreach (Unit u in GameManager.instance.units) {
/*if (u.gridPosition == gridPosition && u != GameManager.instance.units [GameManager.instance.currentUnitIndex]) {
GameManager.instance.EnemyStatsUp ();
GameManager.instance._eName.text = u._name;
GameManager.instance._eClass.text = u._class;
GameManager.instance._eHealth.text = u.health.ToString ();
GameManager.instance._eStrength.text = u.strength.ToString ();
GameManager.instance._eDefence.text = u.defence.ToString ();
GameManager.instance._eAccuracy.text = u.accuracy.ToString ();
}*/
}
// This is the if statement that starts the movement function
if (GameManager.instance.units [GameManager.instance.currentUnitIndex].moving) {
// If a tile has not yet been moved to, the prevMovePos will be set to the current units gridPosition
if (!GameManager.instance.startMove)
prevMovePos = GameManager.instance.units [GameManager.instance.currentUnitIndex].gridPosition;
// 4 Vector3 variables that determine the 4 spaces around the previously moved to tile
Vector3 up = new Vector2 (prevMovePos.x, prevMovePos.y - 1);
Vector3 down = new Vector2 (prevMovePos.x, prevMovePos.y + 1);
Vector3 left = new Vector2 (prevMovePos.x - 1, prevMovePos.y);
Vector3 right = new Vector2 (prevMovePos.x + 1, prevMovePos.y);
bool placeTile = true;
// This foreach statement ensures that a tile with a unit on it cannot be selected as part of the movement path
foreach (Unit u in GameManager.instance.units) {
if (u.gridPosition == gridPosition) {
placeTile = false;
break;
}
}
/* Here is where I have been struggling. I am using a vector2 list to store each tile that will be moved to. With my current code, I am able to push the grid positions
* into the list, with the issue of two instances of the first position being added. The main issue I am trying to deal with, is so a grid position already added cannot
* be added again, as there would be no reason for a unit to cross its own path.
*
* The issue I have come across is that when the foreach loop below starts, it will only check the first entry in the list, and never look any further. As can be seen, I
* have tried multiple loops to overcome this, adding and taking away breaks and continues, in hopes of finding a solution. At this point, I have so far not found the
* solution I am seeking. This is my next priority. Afterwards, I will be implementing AI, that will ideally be able to map out paths for themselves. Once I have done
* this, I will need to compare the current movement system I have implemented to an A* Pathfinding algorithm, to ensure that I will be using the most effective method
* of pathfinding and movement. */
if (placeTile && (gridPosition == up || gridPosition == down || gridPosition == left || gridPosition == right)) {
//GameManager.instance.MovingTile (this);
if (GameManager.instance.moveTiles.Count == 0) {
GameManager.instance.MovingTile (this);
GameManager.instance.moveTiles.Add (this.gridPosition);
}
/*for(int i = 0; i < GameManager.instance.moveTiles.Count; i++){
Vector2 gp = this.gridPosition;
if(gp != GameManager.instance.moveTiles[i]){
GameManager.instance.moveTiles.Add (this.gridPosition);
break;
}
}*/
foreach (Vector3 v in GameManager.instance.moveTiles.ToArray()) {
Debug.Log (v);
if (this.gridPosition != v) {
GameManager.instance.MovingTile (this);
GameManager.instance.moveTiles.Add (this.gridPosition);
break;
}else if (GameManager.instance.moveTiles.Count > 1){
GameManager.instance.moveTiles.Remove(v);
Debug.Log ("Removed " + v);
}
}
//&& GameManager.instance.moveTiles.Count >= 2
/*foreach (Vector3 v in GameManager.instance.moveTiles.ToArray()) {
Debug.Log (v);
if ((this.gridPosition.x != v.x) && (this.gridPosition.y != v.y)) {
GameManager.instance.moveTiles.Add (this.gridPosition);
break;
}
//if ((this.gridPosition.x == v.x) && (this.gridPosition.y == v.y))
//Debug.Log ("Hit same position.");
}*/
}
}
// A debug log that constantly tells me what the co-ordinates of the grid I have my mouse on are.
Debug.Log ("My position is (" + gridPosition.x + "," + gridPosition.y + ")");
}
// Sets the tile colour back to white once the mouse has exited it.
void OnMouseExit(){
if(!movingTile)
rend.material.color = Color.white;
if (blue)
blue = false;
foreach (Unit u in GameManager.instance.units) {
if(u.gridPosition == gridPosition){
//GameManager.instance.EnemyStatsDown();
}
}
}
void OnMouseDown(){
// The if function makes sure that the current unit is in a moving state before running the moveCurrentPlayer function in the GameManager.
if (GameManager.instance.units [GameManager.instance.currentUnitIndex].moving)
GameManager.instance.moveCurrentPlayer ();
// Before reaching it in the tutorial, I noticed that the unit gridPosition was not being set
// So this was my own take on applying new grid positioning to the unit
// This was changed when the moved variable was added
// GameManager.instance.players [GameManager.instance.currentPlayerIndex].gridPosition = gridPosition;
else if (GameManager.instance.units [GameManager.instance.currentUnitIndex].attacking)
GameManager.instance.attackWithCurrentPlayer (this);
}
}
using UnityEngine;
using System.Collections;
public class Unit : MonoBehaviour {
public Vector3 moveDestination;
public Vector3 gridPosition = Vector2.zero;
public bool moving = false;
public bool moved = false;
public bool attacking = false;
public int moveSpeed = 1;
public int actions = 2;
public int health;
public int strength;
public int defence;
public float accuracy;
public float damageRollSides;
public string _name;
public string _class;
public string posClass;
public string negClass;
void Start () {
// Holds the moveDestination position for the current player.
moveDestination = transform.position;
}
void Update () {
}
// By being a virtual function, this allows child classes to override this function.
public virtual void turnUpdate(){
// This if function makes sure that when a unit, either player or AI controlled, has used all its actions, the actions counter, and movement state booleans are reset.
if (actions <= 0) {
actions = 2;
moving = false;
attacking = false;
moved = false;
GameManager.instance.nextTurn();
}
}
}
using UnityEngine;
using System.Collections;
// Sets the class to work off the player class, rather than being a monoBehaviour class.
public class UserUnit : Unit {
Renderer rend;
// Makes sure the rend variable is set to the units renderer, and ensures that moving and attacking states are both set to false.
void Awake (){
rend = GetComponent<Renderer> ();
moving = false;
attacking = false;
}
void Update () {
// This if statement sets any user unit that is currently active to green, so it is clear which unit at the time can be moved.
if (GameManager.instance.units [GameManager.instance.currentUnitIndex] == this)
{
rend.material.color = Color.green;
} else {
rend.material.color = Color.white;
}
// This statement sets the unit to be red, and fall on its side, once it has run out of health.
// NOTE TO SELF: Make sure that any dead unit cannot be set as an active unit again.
if (health <= 0) {
transform.rotation = Quaternion.Euler(new Vector3(90,0,0));
rend.material.color = Color.red;
}
}
// This allows the child class UserPlayer to override what is in the base Player class' turnUpdate function.
public override void turnUpdate(){
// Looks to see if the distance between the moveDestination, as set in the GameManger class through the Tile class, is further than 0.1 away from the current unit position.
if (Vector3.Distance (moveDestination, transform.position) >0.1f)
{
// Starts the unit moving towards their destination.
transform.position += (moveDestination - transform.position).normalized * moveSpeed * Time.deltaTime;
// Looks to see if the unit has reached its destination by confirming it is less that 0.1 away.
if (Vector3.Distance(moveDestination, transform.position) <= 0.1f)
{
// Sets the current position as the moveDestination, so it stays in position.
transform.position = moveDestination;
}
}
// This calls on the Player class turnUpdate function to run with the UserPlayer turnUpdate.
base.turnUpdate ();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment