Skip to content

Instantly share code, notes, and snippets.

@IronMonk-UK
Created May 11, 2016 19:36
Show Gist options
  • Save IronMonk-UK/e0b73e940bea4a4ed9146c33613bcacd to your computer and use it in GitHub Desktop.
Save IronMonk-UK/e0b73e940bea4a4ed9146c33613bcacd to your computer and use it in GitHub Desktop.
The final collection of classes for my Final Project
using UnityEngine;
using System.Collections;
/* The _Class class is used currently to assign stats to units. When a unit is instantiated, one of the functions below is called, setting the stats for that unit.
* I have been recommended to create a class for every unit class in game. At this time, until I have implemented AI further, I will be sticking to this class for unit classes. */
public class _Class : MonoBehaviour {
public static _Class cInstance;
public static void Fighter(UserUnit u){
u._class = "Fighter";
u.totalHealth = 32;
u.strength = 3;
u.defence = 1;
u.accuracy = .6f;
u.damageRollSides = 8;
u.moveLimit = 6;
u.axe = true;
}
public static void Knight(UserUnit u){
u._class = "Knight";
u.totalHealth = 28;
u.strength = 2;
u.defence = 2;
u.accuracy = .7f;
u.damageRollSides = 7;
u.moveLimit = 8;
u.spear = true;
}
public static void Mercenary(UserUnit u){
u._class = "Mercenary";
u.totalHealth = 23;
u.strength = 2;
u.defence = 1;
u.accuracy = .8f;
u.damageRollSides = 6;
u.moveLimit = 6;
u.sword = true;
}
public static void Thug(AIUnit a) {
a._class = "Thug";
a.totalHealth = 15;
a.strength = 1;
a.defence = 1;
a.accuracy = .7f;
a.damageRollSides = 5;
a.moveLimit = 6;
a.sword = true;
}
public static void Mauruder(AIUnit a) {
a._class = "Mauruder";
a.totalHealth = 19;
a.strength = 1;
a.defence = 3;
a.accuracy = .6f;
a.damageRollSides = 6;
a.moveLimit = 8;
a.spear = true;
}
public static void Barbarian(AIUnit a) {
a._class = "Barbarian";
a.totalHealth = 23;
a.strength = 3;
a.defence = 0;
a.accuracy = .5f;
a.damageRollSides = 8;
a.moveLimit = 6;
a.spear = true;
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class AIUnit : Unit {
GameManager gm;
Pathfinding path;
Grid grid;
Renderer rend;
List<UserUnit> unitsInRange = new List<UserUnit>();
List<UserUnit> posWeapType = new List<UserUnit>();
List<UserUnit> inRangeOfUnits = new List<UserUnit>();
List<UserUnit> inRangeOfPos = new List<UserUnit>();
Item healthVialToUse;
bool targetPicked;
bool inRangeTarget;
bool posWeapTarget;
bool noUnitsinRange;
bool healthChecked;
bool canUseVial;
bool cannotUseVial;
bool nothingInRange;
bool fleeing;
bool movedForward;
bool standingGround;
// Use this for initialization
void Awake () {
rend = GetComponent<Renderer>();
gm = GameManager.gm;
path = gm.pathfinding;
grid = gm.grid;
}
// Update is called once per frame
void Update () {
if (gm.currentUnitIndex < gm.units.Count) {
if (gm.units[gm.currentUnitIndex] == this) {
rend.material.color = Color.green;
} else {
rend.material.color = Color.red;
}
// Ensures that booleans and lists are cleared after an AI units turn
if ((this != gm.units[gm.currentUnitIndex] && unitsInRange.Count > 0) || (this != gm.units[gm.currentUnitIndex] && noUnitsinRange)) {
unitsInRange.Clear();
posWeapType.Clear();
inRangeOfUnits.Clear();
targetPicked = false;
inRangeTarget = false;
posWeapTarget = false;
noUnitsinRange = false;
healthChecked = false;
canUseVial = false;
cannotUseVial = false;
nothingInRange = false;
movedForward = false;
standingGround = false;
moving = false;
moved = false;
Debug.Log("List Cleared");
}
}
if (currentHealth <= 0) {
transform.rotation = Quaternion.Euler(new Vector3(90, 0, 0));
rend.material.color = Color.red;
gm.units.Remove(this);
gm.aiUnits.Remove(this);
}
}
public override void turnUpdate(){
// If the list of user units is empty, a method is run to search for them
if(unitsInRange.Count == 0 && !noUnitsinRange) {
Debug.Log("In Range Check");
inRange();
}
// If there are no units in range, check if this AI unit is in range of any User units
if (unitsInRange.Count == 0 && noUnitsinRange && inRangeOfUnits.Count == 0) {
Debug.Log("In Range Of Check");
inRangeOf();
}
// If there is one or more User units in range, or none, and health has not been checked, the AI checks its health
// This is done so the health check only activates if the AI is not in range to attack any User units
if ((inRangeOfUnits.Count >= 1 && !healthChecked) || (nothingInRange && !healthChecked)) {
Debug.Log("Health Check");
checkHealth();
}
// If health has been checked, and there are no units in range, the AI checks if it is safe to move towards a User unit
if(healthChecked && nothingInRange) {
Debug.Log("Nothing in Range. Can use Vial");
moveForward();
}
// If the AI has moved forward, or is standing their ground, the useVial method is run
if((movedForward && moved) || standingGround) {
useVial();
}
// If the AI is in range of a User unit that can attack it positively, it moves away from it, prioritising a positive attacker over any other User units in range
if (healthChecked && inRangeOfPos.Count >= 1 && !moving && !fleeing) {
Debug.Log("Move from Positive");
moveFromPos();
// else if the AI is in range of only a basic User unit that can attack it, it flees from them
} else if (healthChecked && inRangeOfUnits.Count >= 1 && inRangeOfPos.Count == 0 && !moving && !fleeing) {
Debug.Log("Moving from non-Positive");
moveFromRange();
}
// If it has moved out of range of a User unit, the useVial method is run
if ((healthChecked && inRangeOfPos.Count >= 1 && moved) || (healthChecked && inRangeOfUnits.Count >= 1 && inRangeOfPos.Count == 0 && moved)) {
Debug.Log("Use vial after moving from Positive");
useVial();
}
// If there is a user unit who the AI can positively attack in range, the corresponding method is run
// Otherwise, the AI locates an in range enemy, and runs the corresponding method
if (posWeapType.Count >= 1 && !targetPicked && !moving) {
Debug.Log("Attack Positively");
PosWeaponAttack();
} else if (unitsInRange.Count >= 1 && !targetPicked && !moving && !posWeapTarget) {
Debug.Log("Attack in Range");
InRangeAttack();
}
// If the AI has moved up to the unit, and it is one that can be attacked positively, the attack method is called
// This is currently attacking the first in the list, should the list have multiple user units, they will be ignored at this point
// Otherwise, if the AI has moved to the unit, and it is simply in range, the method is called
// These two if statements were made so the correct unit could be directly attacked. I shall attempt to alter this so the AI makes further decisions on who to attack
// if there are multiple normal or positive weapon units in range.
if (moved && posWeapTarget) {
gm.attackWithCurrentPlayer(posWeapType[0].currentTile);
} else if (moved && inRangeTarget && !posWeapTarget) {
gm.attackWithCurrentPlayer(unitsInRange[0].currentTile);
}
base.turnUpdate ();
}
public void inRange() {
Debug.Log("Checking in range");
// Checks every user unit on the field
foreach (UserUnit u in gm.userUnits) {
Debug.Log(u._name);
// The path target moves to the tile of the unit
path.target.transform.position = u.currentTile._pos;
// The pathfinder draws the quickest path to the target
path.FindPath();
// As the AI does not need to be on the same tile to attack, but an adjacent tile, the total tiles minus one is compared against the AIs movement limit
if(grid.path.Count - 1 <= moveLimit) {
// If the unit is in range, they are added to the list of units in range
unitsInRange.Add(u);
// Checks the AIs positive weapon type against the user units weapon
if (equippedWeapon.posType == u.equippedWeapon.weaponType) {
// If the same, that unit is added to the positive weapon type list
/* This saves having to run a second foreach loop to determine which user units have a weapon type the AI is good against, should there be
* multiple units in range. */
posWeapType.Add(u);
}
}
}
// Reset the target position to the AI units tile
path.target.transform.position = currentTile._pos;
// Ensures the path is redrawn correctly
path.FindPath();
if (unitsInRange.Count == 0) {
noUnitsinRange = true;
}
Debug.Log(unitsInRange.Count);
}
// This method runs to see if the AI unit is in range of any User units
public void inRangeOf() {
// Going through each User unit in the userUnits list
foreach (UserUnit u in gm.userUnits) {
// The path target is placed on their position, and the path is re-drawn
path.target.transform.position = u.currentTile._pos;
path.FindPath();
// If the path length - 1 (As the User unit does not need to be ON the AI unit, only next to it) is less than the User units move limit
if(grid.path.Count - 1 <= u.moveLimit) {
// The User unit is added to the list of units the AI unit is in range of
inRangeOfUnits.Add(u);
// If the User units weapon type equals the AIs weapons negative type (Meaning an attack on the AI from the user unit would be positive)
if(equippedWeapon.negType == u.equippedWeapon.weaponType) {
// They are also added to the list of in range positive attackers
inRangeOfPos.Add(u);
}
}
}
// The path target is then placed back on top of the current AI unit, and the path is re-drawn
path.target.transform.position = currentTile._pos;
path.FindPath();
// If the AI is in range of no units, the nothingInRange boolean is set to true
if(unitsInRange.Count == 0 && inRangeOfUnits.Count == 0) {
nothingInRange = true;
}
}
// This method is run if an enemy is in the PosWeapType list
public void PosWeaponAttack() {
// The target is set to the first index of the list
path.target.transform.position = posWeapType[0].currentTile._pos;
// The path is redrawn
path.FindPath();
// As a target has been chosen, the bool turns true
targetPicked = true;
// As the target has a weapon type the AI can positively attack, the posWeapTarget bool is set to true
posWeapTarget = true;
if (grid.path.Count - 1 <= moveLimit) {
// Creates a temporary Vector3
Vector3 backOne = new Vector3(0, 0, 0);
// If the path count is only one, meaning the unit is one tile away
if (grid.path.Count == 1) {
Debug.Log("1");
// moved is set to true, as the AI will not actually have to move
moved = true;
}
// Else if the path count is 2
else if (grid.path.Count == 2) {
Debug.Log("2");
// backOne is set to the first position on the path. As the unit is two tiles away, there is only one tile between them; the first in the path list
backOne = grid.path[0].tile._pos;
// The target is set to the tile in front of the unit and AI
path.target.transform.position = backOne;
// Moving is set to true, as the unit is now moving
moving = true;
// The path is redrawn
path.FindPath();
// The movement Coroutine is started
gm.moveCurrentPlayer();
}
// Else if the path count is 3 or greater
else if (grid.path.Count >= 3) {
Debug.Log("3+");
/* To get the tile in front of the unit, I take the grid path count, and take away two. One, to account for the fact the count != the index. By removing
* one, I bring the given number to match the array index. I remove one further, to take us back one tile from the unit. */
backOne = grid.path[grid.path.Count - 2].tile._pos;
Debug.Log("BackOne : " + backOne);
// The same sequence sa in the above else if is run
path.target.transform.position = backOne;
moving = true;
path.FindPath();
gm.moveCurrentPlayer();
}
}
}
// The InRangeAttack functions almost identical to the PosWeaponAttack, apart from the assignment of the initial path target.
// I realise this could be potentially be made more efficient, and will be looked at once the entire decision tree has been implemented
public void InRangeAttack() {
path.target.transform.position = unitsInRange[0].currentTile._pos;
path.FindPath();
targetPicked = true;
inRangeTarget = true;
if (grid.path.Count - 1 <= moveLimit) {
Vector3 backOne = new Vector3(0, 0, 0);
if (grid.path.Count >= 3) {
backOne = grid.path[grid.path.Count - 2].tile._pos;
path.target.transform.position = backOne;
moving = true;
path.FindPath();
gm.moveCurrentPlayer();
} else if (grid.path.Count == 2) {
backOne = grid.path[0].tile._pos;
path.target.transform.position = backOne;
moving = true;
path.FindPath();
gm.moveCurrentPlayer();
} else if (grid.path.Count == 1) {
moved = true;
}
}
}
// The moveFromPos function works out which direction the AI wants to retreat in, and how far, from a positive User unit
public void moveFromPos() {
// The path target is set to the first positive User unit in the list, and the path re-drawn
path.target.transform.position = inRangeOfPos[0].currentTile._pos;
path.FindPath();
// The difference between the User units move limit, and the path length, is worked out
// The move limit of the User unit takes away the path length - 1, as the User units path will start one tile in front of itself
// 1 is added, so that the unit will always move one tile further back than the User units move limit
// Example: User Knight has move limit 8. AI Barbarian is 8 tiles away, too far to attack
// (Move limit) 8 - (7) (The path length to the User unit, - 1) + 1
// 8 - 7 + 1 = 2
// This will result in the AI Barbarian moving back 2 tiles (10 tiles total from the User unit), leaving a tile between how far the User Knight can move,
// And how far away the AI Barbarian is
int difference = (inRangeOfPos[0].moveLimit - (grid.path.Count - 1) + 1);
Debug.Log("Difference : " + difference);
// A set of if statements are run, comparing the position of the tile in front of the User unit, to the following tile in the list
// Based on the difference of grid co-ordinates, the AI will determine which way to move from the User unit
if (grid.path[1].gridX == grid.path[0].gridX + 1) {
Debug.Log("Path goes right");
path.target.transform.position = grid.grid[currentTile.node.gridX - difference, currentTile.node.gridY].tile._pos;
} else if (grid.path[1].gridX == grid.path[0].gridX - 1) {
Debug.Log("Path goes left");
path.target.transform.position = grid.grid[currentTile.node.gridX + difference, currentTile.node.gridY].tile._pos;
} else if (grid.path[1].gridY == grid.path[0].gridY + 1) {
Debug.Log("Path goes up");
path.target.transform.position = grid.grid[currentTile.node.gridX, currentTile.node.gridY - difference].tile._pos;
} else if (grid.path[1].gridY == grid.path[0].gridY - 1) {
Debug.Log("Path goes down");
path.target.transform.position = grid.grid[currentTile.node.gridX, currentTile.node.gridY + difference].tile._pos;
}
// The path is re-drawn once re-positioned, the fleeing bool is set to true, and the unit moves
path.FindPath();
fleeing = true;
gm.moveCurrentPlayer();
}
// Much like the positive and regular attack methods above, moveFromRange only differs in the list it picks the User unit from
public void moveFromRange() {
path.target.transform.position = inRangeOfUnits[0].currentTile._pos;
path.FindPath();
int difference = (inRangeOfUnits[0].moveLimit - (grid.path.Count - 1)) + 1;
Debug.Log("Difference : " + difference);
if (grid.path[1].gridX == grid.path[0].gridX + 1) {
Debug.Log("Path goes right");
path.target.transform.position = grid.grid[currentTile.node.gridX - difference, currentTile.node.gridY].tile._pos;
} else if (grid.path[1].gridX == grid.path[0].gridX - 1) {
Debug.Log("Path goes left");
path.target.transform.position = grid.grid[currentTile.node.gridX + difference, currentTile.node.gridY].tile._pos;
} else if (grid.path[1].gridY == grid.path[0].gridY + 1) {
Debug.Log("Path goes up");
path.target.transform.position = grid.grid[currentTile.node.gridX, currentTile.node.gridY - difference].tile._pos;
} else if (grid.path[1].gridY == grid.path[0].gridY - 1) {
Debug.Log("Path goes down");
path.target.transform.position = grid.grid[currentTile.node.gridX, currentTile.node.gridY + difference].tile._pos;
}
path.FindPath();
fleeing = true;
gm.moveCurrentPlayer();
}
// This method checks to see if the AI unit can/needs to use a health vial
public void checkHealth() {
// If the current health is 10 or less than the total health
// Or the total health is less than 10, and the current health is 2 or less than the total health
if((currentHealth <= totalHealth - 10) || (totalHealth <= 10 && currentHealth <= totalHealth - 2))
{
// The inventory of the AI unit is checked
foreach (Item i in inventory) {
// If a health vial is found, canUseVial is set to true, and that vial is set to the healthVialToUse, then breaks out the loop
if (i.itemType == Item.ItemType.Tool) {
canUseVial = true;
healthVialToUse = i;
break;
}
}
}
// If a health vial isn't found, or the AIs current health is high enough, cannotUseVial is set to true
if (!canUseVial) {
cannotUseVial = true;
}
// The healthChecked bool is set to true regardless, informing the AI that it has checked its health
healthChecked = true;
}
// If there are no User units the AI can reach, and no units can reach the AI, it will attempt to move forward
void moveForward() {
int i = 0;
UserUnit target = null;
// Checking every user unit
foreach (UserUnit u in gm.userUnits) {
// The path target is positioned on the User unit, and the path re-drawn
path.target.transform.position = u.currentTile._pos;
path.FindPath();
// Temporary int j takes the path length needed to be in front of a User unit
int j = grid.path.Count - 1;
// If j is less than temp int i
if(j < i || i == 0) {
// j is set to i
i = j;
// The User unit is set to the target
target = u;
}
}
// If i minus the AIs move limit is less than the target User units move limit, plus 1
if(target != null && (i - moveLimit > target.moveLimit + 1)) {
// The AI moves forward towards that User unit
moving = true;
gm.moveCurrentPlayer();
movedForward = true;
} else {
// Else they stand their ground on the current tile
standingGround = true;
}
}
// This method is run after an AI unit has fled, either allowing them to use a vial, or ending the turn
void useVial() {
// If the AI can use a vial
if (canUseVial) {
// The heal function on the health vial is used
healthVialToUse._heal(this);
// It is removed from the inventory
inventory.Remove(healthVialToUse);
// The healthVialToUse is set to null
healthVialToUse = null;
Debug.Log("Vial was used");
// The turn ends
gm.EndTurn();
// else if the AI cannot use a vial
} else if (cannotUseVial) {
Debug.Log("Vial not required / Available");
// The turn ends
gm.nextTurn();
}
}
}
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
public class GameManager : MonoBehaviour
{
public static GameManager gm;
public GameObject tilePrefab;
public GameObject userUnitPrefab;
public GameObject AIUnitPrefab;
public Text _name;
public Text _class;
public Text _health;
public Text _strength;
public Text _defence;
public Text _accuracy;
public Text _weaponType;
public Text _eName;
public Text _eClass;
public Text _eHealth;
public Text _eStrength;
public Text _eDefence;
public Text _eAccuracy;
public Text _eWeaponType;
public Button inv1;
public Text _inv1;
public Button inv2;
public Text _inv2;
public Button inv3;
public Text _inv3;
public Button inv4;
public Text _inv4;
public Button inv5;
public Text _inv5;
public List<Button> inv = new List<Button>();
public Text announcer;
public int mapSize;
public bool startMove;
bool supportAttack;
bool posHit;
bool posSupHit;
bool negHit;
bool negSupHit;
bool hit;
bool supHit;
public bool unitMoving;
public List<List<Tile>> map = new List<List<Tile>>();
public List<Unit> units = new List<Unit>();
public List<Unit> userUnits = new List<Unit>();
public List<Unit> aiUnits = new List<Unit>();
public List<Vector2> moveTiles = new List<Vector2>();
[Header("Manually Change Starting Positions")]
public List<Vector2> startingTiles = new List<Vector2>();
public Grid grid;
public Pathfinding pathfinding;
public int currentUnitIndex;
public bool unitsPositioned;
Tile targetTile;
void Awake()
{
// Makes the instance variable the current GameManager class
gm = this;
grid = GetComponent<Grid>();
pathfinding = GetComponent<Pathfinding>();
//EnemyStatsDown ();
}
void Start()
{
// Runs the generateMap function on start
//generateMap ();
// Runs the generatePlayers function on start
generateUnits();
currentUnitIndex = 0;
//announcer.text = "";
EnemyStatsDown();
}
void Update()
{
//if(userUnits.Contains(units[currentUnitIndex]))
//BlockUnitTiles();
if (currentUnitIndex >= units.Count)
{
currentUnitIndex = 0;
pathfinding.target.transform.position = units[currentUnitIndex].currentTile._pos;
pathfinding.FindPath();
}
// The units starting grid positions can now manually be set via the inspector, making debugging and testing much quicker
if (!unitsPositioned)
{
for(int i = 0; i < units.Count; i++) {
units[i].transform.position = grid.grid[Mathf.RoundToInt(startingTiles[i].x), Mathf.RoundToInt(startingTiles[i].y)].tile._pos;
units[i].currentTile = grid.grid[Mathf.RoundToInt(startingTiles[i].x), Mathf.RoundToInt(startingTiles[i].y)].tile;
}
unitsPositioned = true;
pathfinding.target.transform.position = units[currentUnitIndex].currentTile._pos;
}
// This foreach loop is to ensure that a units grid position is always updating to its current grid position
foreach (Unit u in units) {
foreach (List<Tile> l in grid.map) {
foreach (Tile t in l) {
if (u.transform.position == new Vector3(t.transform.position.x, t.transform.position.y + 2, t.transform.position.z))
u.gridPosition = t.gridPosition;
}
}
}
if (units[currentUnitIndex].inventory.Count == 1) { inventory1(); }
if (units[currentUnitIndex].inventory.Count == 2) { inventory2(); }
if (units[currentUnitIndex].inventory.Count == 3) { inventory3(); }
if (units[currentUnitIndex].inventory.Count == 4) { inventory4(); }
if (units[currentUnitIndex].inventory.Count == 5) { inventory5(); }
// This for loop runs to ensure that any usable item, such as the health vial
if (userUnits.Contains(units[currentUnitIndex])) {
for(int i = 0; i < units[currentUnitIndex].inventory.Count; i++) {
// if the item in inventory index i has the ItemType "Tool"
if (units[currentUnitIndex].inventory[i].itemType == Item.ItemType.Tool) {
// and if the Tool has the name "Health Vial"
if (units[currentUnitIndex].inventory[i].Name == "Health Vial") {
// create a temporary item variable, that is the inventory item with index i
Item item = units[currentUnitIndex].inventory[i];
// Ensure that any previous listeners are removed from the button
inv[i].onClick.RemoveAllListeners();
// Adds the item method _heal to the button's onClick
inv[i].onClick.AddListener(delegate { item._heal(units[currentUnitIndex]); });
}
// If there is another weapon in the inventory that is not the currently equipped weapon
} else if (i != 0 && units[currentUnitIndex].inventory[i].itemType == Item.ItemType.Weapon) {
// A temporary int takes note of the inventory position
int j = i;
// The item being moved is put into a temp variable
Item itemToMove = units[currentUnitIndex].inventory[i];
// As is the currently equipped item
Item currentItem = units[currentUnitIndex].inventory[0];
// All previous listeners on that inventory position are removed
inv[j].onClick.RemoveAllListeners();
// The moveItem method is attached to that button, taking in the item to move, the currently equipped item, and the inventory position
inv[j].onClick.AddListener(delegate { moveItem(itemToMove, currentItem, j); });
}
}
}
/* 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].currentHealth > 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].currentHealth.ToString() + " / " + units[currentUnitIndex].totalHealth.ToString();
_strength.text = units [currentUnitIndex].strength.ToString();
_defence.text = units [currentUnitIndex].defence.ToString();
_accuracy.text = units [currentUnitIndex].accuracy.ToString();
_weaponType.text = units[currentUnitIndex].equippedWeapon.Name;
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++;
pathfinding.target.transform.position = units[currentUnitIndex].currentTile._pos;
}
else
{
currentUnitIndex = 0;
pathfinding.target.transform.position = units[currentUnitIndex].currentTile._pos;
}
}
/* 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()
{
targetTile = grid.path[grid.path.Count - 1].tile;
unitMoving = true;
if (units[currentUnitIndex].moved == false)
{
int i = 0;
for (i = 0; (grid.path.Count <= units[currentUnitIndex].moveLimit && i < grid.path.Count) || (grid.path.Count >= units[currentUnitIndex].moveLimit && i < units[currentUnitIndex].moveLimit); i++)
{
Debug.Log(grid.path[i].tile._pos);
units[currentUnitIndex].transform.position = grid.path[i].tile._pos;
units[currentUnitIndex].currentTile = grid.path[i].tile;
if (units[currentUnitIndex].currentTile == targetTile || i == units[currentUnitIndex].moveLimit - 1)
{
Debug.Log("Done thing");
units[currentUnitIndex].actions--;
units[currentUnitIndex].moved = true;
pathfinding.target.transform.position = units[currentUnitIndex].currentTile._pos;
pathfinding.FindPath();
}
}
}
unitMoving = false;
}
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;
Unit support = null;
foreach (Unit u in units)
{
if (u.gridPosition == destTile.gridPosition)
{
target = u;
}
}
/* 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)
{
/* 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);
if (units[currentUnitIndex].gridPosition == up || units[currentUnitIndex].gridPosition == down || units[currentUnitIndex].gridPosition == left || units[currentUnitIndex].gridPosition == right)
{
units[currentUnitIndex].actions--;
// Ensuring the Unique Support Mechanic (USM) only kicks in for User units
// The USM is a simple pincer attack. If another User unit is opposite the attacking unit, rather than only the attacking unit attacking, both they
// and the other, "supporting" unit attack in one turn
if (userUnits.Contains(units[currentUnitIndex])) {
// Checking every User unit
foreach (UserUnit u in userUnits) {
// If any User unit is opposite the attacking unit, supportAttack is turned true
if(units[currentUnitIndex].gridPosition == up && u.gridPosition == down) {
Debug.Log("Pincer Attack : Current unit down, Supporting unit up");
supportAttack = true;
support = u;
}
if(units[currentUnitIndex].gridPosition == down && u.gridPosition == up) {
Debug.Log("Pincer Attack : Current unit up, Supporting unit down");
supportAttack = true;
support = u;
}
if(units[currentUnitIndex].gridPosition == left && u.gridPosition == right) {
Debug.Log("Pincer Attack : Current unit left, Supporting unit right");
supportAttack = true;
support = u;
}
if(units[currentUnitIndex].gridPosition == right && u.gridPosition == left) {
Debug.Log("Pincer Attack : Current unit right, Supporting unit left");
supportAttack = true;
support = u;
}
}
}
// If supportAttack is true
if (supportAttack) {
// The support unit is run through the same checks as regularly attacking units
if (support.equippedWeapon.posType == target.equippedWeapon.weaponType) {
posSupHit = Random.Range(0.0f, 1.0f) <= (units[currentUnitIndex].accuracy + .2f);
} else if (support.equippedWeapon.negType == target.equippedWeapon.weaponType) {
negSupHit = Random.Range(0.0f, 1.0f) <= (units[currentUnitIndex].accuracy - .2f);
} else {
supHit = Random.Range(0.0f, 1.0f) <= units[currentUnitIndex].accuracy;
}
}
if (units[currentUnitIndex].equippedWeapon.posType == target.equippedWeapon.weaponType) {
posHit = Random.Range(0.0f, 1.0f) <= (units[currentUnitIndex].accuracy + .2f);
} else if (units[currentUnitIndex].equippedWeapon.negType == target.equippedWeapon.weaponType) {
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. */
int damageAmount = 0;
int damageSupAmount = 0;
// Again confirming that supportingAttack is true
if (supportAttack) {
// If the supporting unit managed to land their hit, it is calculated in the same manner as regularly attacking units
if (posSupHit) {
damageSupAmount = (int)Mathf.Floor((support.strength + 1) + support.equippedWeapon.Damage - target.defence);
target.currentHealth -= damageSupAmount;
Debug.Log("Supporter " + support._name + " hit " + target._name + " effectively for " + damageSupAmount + " damage!");
} else if (negSupHit) {
damageSupAmount = (int)Mathf.Floor((support.strength - 2) + support.equippedWeapon.Damage - target.defence);
target.currentHealth -= damageSupAmount;
Debug.Log("Supporter " + support._name + " hit " + target._name + " ineffectively for " + damageSupAmount + " damage!");
} else if (supHit) {
damageSupAmount = (int)Mathf.Floor(support.strength + support.equippedWeapon.Damage - target.defence);
target.currentHealth -= damageSupAmount;
Debug.Log("Supporter " + support._name + " hit " + target._name + " for " + damageSupAmount + " damage!");
} else {
Debug.Log("Supporter" + support._name + " missed " + target._name);
}
}
if (posHit) {
damageAmount = (int)Mathf.Floor((units[currentUnitIndex].strength + 1) + units[currentUnitIndex].equippedWeapon.Damage - target.defence);
target.currentHealth -= 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) {
damageAmount = (int)Mathf.Floor((units[currentUnitIndex].strength - 2) + units[currentUnitIndex].equippedWeapon.Damage - target.defence);
target.currentHealth -= 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) {
damageAmount = (int)Mathf.Floor(units[currentUnitIndex].strength + units[currentUnitIndex].equippedWeapon.Damage - target.defence);
target.currentHealth -= 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("Attacker " + units[currentUnitIndex]._name + " missed " + target._name);
}
}
else
{
Debug.Log("Target not available");
}
}
supportAttack = false;
posHit = false;
posSupHit = false;
negHit = false;
negSupHit = false;
hit = false;
supHit = false;
EndTurn();
}
void generateUnits()
{
UserUnit unit;
unit = ((GameObject)Instantiate(userUnitPrefab, new Vector3(0 - Mathf.Floor(grid.worldSize.x / 2) + .5f, 1, -0 + Mathf.Floor(mapSize / 2) - .5f), Quaternion.Euler(new Vector3()))).GetComponent<UserUnit>();
unit._name = "Bentley";
_Class.Fighter(unit);
Items.sItems.IronAxe(unit);
Items.sItems.IronSpear(unit);
Items.sItems.IronSword(unit);
units.Add(unit);
userUnits.Add(unit);
unit = ((GameObject)Instantiate(userUnitPrefab, new Vector3((mapSize - 1) - Mathf.Floor(grid.worldSize.x / 2) + .5f, 1, -(grid.worldSize.y - 1) + Mathf.Floor(mapSize / 2) - .5f), Quaternion.Euler(new Vector3()))).GetComponent<UserUnit>();
unit._name = "Harrison";
_Class.Mercenary(unit);
Items.sItems.IronSword(unit);
units.Add(unit);
userUnits.Add(unit);
unit = ((GameObject)Instantiate(userUnitPrefab, new Vector3((mapSize - 1) - Mathf.Floor(grid.worldSize.x / 2) + .5f, 1, -(grid.worldSize.y - 1) + Mathf.Floor(mapSize / 2) - .5f), Quaternion.Euler(new Vector3()))).GetComponent<UserUnit>();
unit._name = "Isaac";
_Class.Mercenary(unit);
Items.sItems.IronSword(unit);
units.Add(unit);
userUnits.Add(unit);
unit = ((GameObject)Instantiate(userUnitPrefab, new Vector3(4 - Mathf.Floor(grid.worldSize.x / 2) + .5f, 1, -4 + Mathf.Floor(grid.worldSize.y / 2) - .5f), Quaternion.Euler(new Vector3()))).GetComponent<UserUnit>();
unit._name = "Alexander";
_Class.Knight(unit);
Items.sItems.IronSpear(unit);
units.Add(unit);
userUnits.Add(unit);
AIUnit aUnit;
aUnit = ((GameObject)Instantiate(AIUnitPrefab, new Vector3(0, 0, 0), Quaternion.Euler(new Vector3()))).GetComponent<AIUnit>();
aUnit._name = "Tim";
_Class.Thug(aUnit);
Items.sItems.IronSword(aUnit);
Items.sItems.healthVial(aUnit);
units.Add(aUnit);
aiUnits.Add(aUnit);
aUnit = ((GameObject)Instantiate(AIUnitPrefab, new Vector3(0, 0, 0), Quaternion.Euler(new Vector3()))).GetComponent<AIUnit>();
aUnit._name = "Harry";
_Class.Mauruder(aUnit);
Items.sItems.IronSpear(aUnit);
units.Add(aUnit);
aiUnits.Add(aUnit);
aUnit = ((GameObject)Instantiate(AIUnitPrefab, new Vector3(0, 0, 0), Quaternion.Euler(new Vector3()))).GetComponent<AIUnit>();
aUnit._name = "Brick";
_Class.Barbarian(aUnit);
Items.sItems.IronAxe(aUnit);
Items.sItems.healthVial(aUnit);
units.Add(aUnit);
aiUnits.Add(aUnit);
aUnit = ((GameObject)Instantiate(AIUnitPrefab, new Vector3(0, 0, 0), Quaternion.Euler(new Vector3()))).GetComponent<AIUnit>();
aUnit._name = "Trick";
_Class.Barbarian(aUnit);
Items.sItems.IronAxe(aUnit);
units.Add(aUnit);
aiUnits.Add(aUnit);
aUnit = ((GameObject)Instantiate(AIUnitPrefab, new Vector3(0, 0, 0), Quaternion.Euler(new Vector3()))).GetComponent<AIUnit>();
aUnit._name = "Richard";
_Class.Barbarian(aUnit);
Items.sItems.IronAxe(aUnit);
units.Add(aUnit);
aiUnits.Add(aUnit);
}
// 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;
pathfinding.target.transform.position = units[currentUnitIndex].currentTile._pos;
}
}
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()
{
if (!units[currentUnitIndex].moving) {
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;
_eWeaponType.enabled = true;
}
public void EnemyStatsDown(){
_eName.enabled = false;
_eClass.enabled = false;
_eHealth.enabled = false;
_eStrength.enabled = false;
_eDefence.enabled = false;
_eAccuracy.enabled = false;
_eWeaponType.enabled = false;
}
// The following set of methods are designed to stop any errors occuring due to the inventory not being completely full, by ensuring blank spaces are set to null,
// depending on the amount of items in the inventory
public void inventory1()
{
_inv1.text = units[currentUnitIndex].inventory[0].Name;
_inv2.text = null;
_inv3.text = null;
_inv4.text = null;
_inv5.text = null;
}
public void inventory2()
{
_inv1.text = units[currentUnitIndex].inventory[0].Name;
_inv2.text = units[currentUnitIndex].inventory[1].Name;
_inv3.text = null;
_inv4.text = null;
_inv5.text = null;
}
public void inventory3()
{
_inv1.text = units[currentUnitIndex].inventory[0].Name;
_inv2.text = units[currentUnitIndex].inventory[1].Name;
_inv3.text = units[currentUnitIndex].inventory[2].Name;
_inv4.text = null;
_inv5.text = null;
}
public void inventory4()
{
_inv1.text = units[currentUnitIndex].inventory[0].Name;
_inv2.text = units[currentUnitIndex].inventory[1].Name;
_inv3.text = units[currentUnitIndex].inventory[2].Name;
_inv4.text = units[currentUnitIndex].inventory[3].Name;
_inv5.text = null;
}
public void inventory5()
{
_inv1.text = units[currentUnitIndex].inventory[0].Name;
_inv2.text = units[currentUnitIndex].inventory[1].Name;
_inv3.text = units[currentUnitIndex].inventory[2].Name;
_inv4.text = units[currentUnitIndex].inventory[3].Name;
_inv5.text = units[currentUnitIndex].inventory[4].Name;
}
// This is the method run to change the currently equipped weapon
void moveItem(Item i, Item i2, int j) {
// If the unit is not moving or attacking
if(!units[currentUnitIndex].moving && !units[currentUnitIndex].attacking) {
// The current weapon is moved to the changing weapons inventory position
units[currentUnitIndex].inventory[j] = i2;
// And the changing weapon is positioned at the front of the array, making it the currently equipped weapon
units[currentUnitIndex].inventory[0] = i;
// A set of if statements then ensure the items are displayed correctly within the UI inventory
if (units[currentUnitIndex].inventory.Count == 1) { inventory1(); }
if (units[currentUnitIndex].inventory.Count == 2) { inventory2(); }
if (units[currentUnitIndex].inventory.Count == 3) { inventory3(); }
if (units[currentUnitIndex].inventory.Count == 4) { inventory4(); }
if (units[currentUnitIndex].inventory.Count == 5) { inventory5(); }
}
}
// Makes tiles populated by units unwalkable, so they cannot be moved to, or stopped on
/*void BlockUnitTiles() {
// If the current unit is a User unit, and moving
if (userUnits.Contains(units[currentUnitIndex]) && units[currentUnitIndex].moving) {
// For every unit
foreach(Unit u in units) {
// If the unit is not the current unit
if(u != units[currentUnitIndex])
// Set the walkable bool on the node of the populated tile to false
u.currentTile.node.walkable = false;
}
}
// If the unit has finished movement
if (units[currentUnitIndex].moved) {
// For every unit
foreach(Unit u in units) {
// Set the walkable bool on the node of the populated tile back to true
u.currentTile.node.walkable = true;
}
}
}*/
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Grid : MonoBehaviour {
GameManager gm;
public Vector2 worldSize;
public float nodeRadius;
float nodeDiameter;
int gridSizeX, gridSizeY;
public LayerMask terrainMask;
[SerializeField]
public Node[,] grid;
public GameObject tilePrefab;
public List<List<Tile>> map = new List<List<Tile>>();
public List<Node> path = new List<Node>();
void Awake() {
gm = GetComponent<GameManager>();
nodeDiameter = nodeRadius * 2;
gridSizeX = Mathf.RoundToInt(worldSize.x / nodeDiameter);
gridSizeY = Mathf.RoundToInt(worldSize.y / nodeDiameter);
CreateGrid();
}
void Update() {
if (grid != null) {
foreach (Node n in grid) {
n.tile.rend.material.color = (n.walkable) ? Color.white : Color.red;
if (path != null)
{
if(gm.currentUnitIndex < gm.units.Count)
{
if (path.Contains(n) && path.IndexOf(n) < gm.units[gm.currentUnitIndex].moveLimit)
n.tile.rend.material.color = Color.black;
}
}
}
}
}
void CreateGrid(){
map = new List<List<Tile>>();
grid = new Node[gridSizeX, gridSizeY];
Vector3 worldBottomLeft = transform.position - Vector3.right * worldSize.x / 2 - Vector3.forward * worldSize.y / 2;
for (int x = 0; x < gridSizeX; x++) {
List<Tile> row = new List<Tile>();
for (int y = 0; y < gridSizeX; y++) {
Vector3 worldPoint = worldBottomLeft + Vector3.right * (x * nodeDiameter + nodeRadius) + Vector3.forward * (y * nodeDiameter + nodeRadius);
bool walkable = !(Physics.CheckSphere(worldPoint, nodeRadius, terrainMask));
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);
}
}
private void AddNeighbour(ref List<Node> neighbours, int neighbourX, int neighbourY)
{
if (neighbourX >= 0 && neighbourX < gridSizeX && neighbourY >= 0 && neighbourY < gridSizeY)
{
neighbours.Add(grid[neighbourX, neighbourY]);
}
}
public List<Node> GetNeighbours(Node node) {
List<Node> neighbours = new List<Node>();
AddNeighbour(ref neighbours, node.gridX + 1, node.gridY);
AddNeighbour(ref neighbours, node.gridX - 1, node.gridY);
AddNeighbour(ref neighbours, node.gridX, node.gridY + 1);
AddNeighbour(ref neighbours, node.gridX, node.gridY - 1);
return neighbours;
}
public Node NodeFromWorldPoint(Vector3 worldPos) {
float percentX = (worldPos.x + worldSize.x / 2) / worldSize.x;
float percentY = (worldPos.z + worldSize.y / 2) / worldSize.y;
percentX = Mathf.Clamp01(percentX);
percentY = Mathf.Clamp01(percentY);
int x = Mathf.RoundToInt((gridSizeX - 1) * percentX);
int y = Mathf.RoundToInt((gridSizeY - 1) * percentY);
return grid[x, y];
}
void OnDrawGizmos() {
Gizmos.DrawWireCube(transform.position, new Vector3(worldSize.x, 1, worldSize.y));
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;
using System.Collections;
public class Item {
string name;
int damage;
int hitChance;
int heal;
public ItemType itemType;
public WeaponType weaponType;
public WeaponType posType;
public WeaponType negType;
public enum ItemType
{
Weapon,
Tool
}
public enum WeaponType
{
None,
Sword,
Axe,
Spear
}
public Item (ItemType _itemType, WeaponType _weaponType, string _name, int _damage, int _hitChance, int _heal)
{
itemType = _itemType;
weaponType = _weaponType;
name = _name;
damage = _damage;
hitChance = _hitChance;
heal = _heal;
if (weaponType == WeaponType.Axe)
{
posType = WeaponType.Spear;
negType = WeaponType.Sword;
}
else if (weaponType == WeaponType.Sword)
{
posType = WeaponType.Axe;
negType = WeaponType.Spear;
}
else if (weaponType == WeaponType.Spear)
{
posType = WeaponType.Sword;
negType = WeaponType.Axe;
}
}
public string Name
{
get { return name; }
}
public int Damage
{
get { return damage; }
}
public int HitChance
{
get { return hitChance; }
}
public int Heal
{
get { return heal; }
}
public void _heal(Unit unit)
{
if(unit.currentHealth < unit.totalHealth) {
unit.currentHealth += heal;
if(unit.currentHealth > unit.totalHealth) {
unit.currentHealth = unit.totalHealth;
}
unit.inventory.Remove(this);
unit.actions--;
}
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Items : MonoBehaviour {
public static Items sItems;
void Awake () {
sItems = this;
}
void Update () {
}
public void IronSword(Unit unit)
{
Item item = new Item(Item.ItemType.Weapon, Item.WeaponType.Sword, "Iron Sword", 5, 10, 0);
unit.inventory.Add(item);
}
public void IronAxe(Unit unit)
{
Item item = new Item(Item.ItemType.Weapon, Item.WeaponType.Axe, "Iron Axe", 8, 7, 0);
unit.inventory.Add(item);
}
public void IronSpear(Unit unit)
{
Item item = new Item(Item.ItemType.Weapon, Item.WeaponType.Spear, "Iron Spear", 7, 8, 0);
unit.inventory.Add(item);
}
public void healthVial(Unit unit)
{
Item item = new Item(Item.ItemType.Tool, Item.WeaponType.None, "Health Vial", 0, 0, 10);
unit.inventory.Add(item);
}
}
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
GameManager gm;
Grid grid;
public Transform seeker, target;
public bool pathTraced;
void Awake() {
// Sets the grid variable to the Grid class attached to the same gameObject
grid = GetComponent<Grid>();
gm = GetComponent<GameManager>();
}
void Update()
{
if (gm.currentUnitIndex < gm.units.Count)
seeker = gm.units[gm.currentUnitIndex].gameObject.transform;
// Finds the path between the seeker position and target position
if(!pathTraced)
FindPath(seeker.position, target.position);
}
public void FindPath()
{
seeker = gm.units[gm.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);
//return 10 * dstY + 10 * dstX;
}
}
using UnityEngine;
using System.Collections;
public class Tile : MonoBehaviour
{
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 Vector3 _pos;
public Pathfinding pathfinding;
public Grid grid;
public GameManager gm;
//public bool forest;
void Start() {
pathfinding = GameObject.FindWithTag("A*").GetComponent<Pathfinding>();
grid = GameObject.FindWithTag("A*").GetComponent<Grid>();
gm = GameObject.FindWithTag("A*").GetComponent<GameManager>();
rend = GetComponent<Renderer>();
nodePos = new Vector2(node.gridX, node.gridY);
_pos = new Vector3(transform.position.x, 1, transform.position.z);
}
void Update() {
if (movingTile)
rend.material.color = Color.green;
// This if statement has been altered, so the path is visibly shown to the player now
if (!movingTile && !blue && !grid.path.Contains(node) && node.walkable)
rend.material.color = Color.white;
}
void OnMouseEnter() {
if (gm.units[gm.currentUnitIndex].moving && !gm.unitMoving) {
pathfinding.target.transform.position = _pos;
pathfinding.FindPath();
}
if (!blue && !gm.units[gm.currentUnitIndex].attacking) {
blue = true;
rend.material.color = Color.blue;
}
else if (gm.units[gm.currentUnitIndex].attacking) {
rend.material.color = Color.red;
}
foreach (Unit u in gm.units)
{
if (u.gridPosition == gridPosition && u != gm.units[gm.currentUnitIndex])
{
Debug.Log("Unit found");
gm.EnemyStatsUp();
gm._eName.text = u._name;
gm._eClass.text = u._class;
gm._eHealth.text = u.currentHealth.ToString() + " / " + u.totalHealth.ToString();
gm._eStrength.text = u.strength.ToString();
gm._eDefence.text = u.defence.ToString();
gm._eAccuracy.text = u.accuracy.ToString();
gm._eWeaponType.text = u.equippedWeapon.Name;
if(gm.units[gm.currentUnitIndex].equippedWeapon.posType == u.equippedWeapon.weaponType) {
gm._eWeaponType.color = Color.red;
gm._weaponType.color = Color.green;
}
if(gm.units[gm.currentUnitIndex].equippedWeapon.negType == u.equippedWeapon.weaponType) {
gm._eWeaponType.color = Color.green;
gm._weaponType.color = Color.red;
}
if(gm.units[gm.currentUnitIndex].equippedWeapon.weaponType == u.equippedWeapon.weaponType) {
gm._eWeaponType.color = Color.black;
gm._weaponType.color = Color.black;
}
}
}
}
void OnMouseExit() {
if (!movingTile && node.walkable)
rend.material.color = Color.white;
if (blue)
blue = false;
foreach (Unit u in gm.units) {
if (u.gridPosition == gridPosition) {
gm.EnemyStatsDown();
gm._weaponType.color = Color.black;
}
}
}
void OnMouseDown() {
if (gm.units[gm.currentUnitIndex].moving) {
pathfinding.pathTraced = false;
pathfinding.FindPath();
pathfinding.pathTraced = true;
gm.moveCurrentPlayer();
gm.units[gm.currentUnitIndex].gridPosition = gridPosition;
}
else if (gm.units[gm.currentUnitIndex].attacking)
gm.attackWithCurrentPlayer(this);
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Unit : MonoBehaviour {
public Vector3 gridPosition = Vector2.zero;
[SerializeField] public List<Item> inventory = new List<Item>();
public Item currentWeapon;
public bool moving = false;
public bool moved = false;
public bool attacking = false;
public int moveSpeed = 1;
public int actions = 2;
public int totalHealth;
public int currentHealth;
public int strength;
public int defence;
public int moveLimit;
public float accuracy;
public float damageRollSides;
public string _name;
public string _class;
public bool sword;
public bool axe;
public bool spear;
public bool startPos;
public Item equippedWeapon;
public Tile currentTile;
void Start()
{
currentHealth = totalHealth;
}
void FixedUpdate()
{
if(inventory[0].itemType == Item.ItemType.Weapon)
{
equippedWeapon = inventory[0];
}
}
// 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.gm.nextTurn();
}
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
// Sets the class to work off the player class, rather than being a monoBehaviour class.
public class UserUnit : Unit {
GameManager gm;
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>();
gm = GameManager.gm;
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(gm.currentUnitIndex < gm.units.Count)
{
if (gm.units[gm.currentUnitIndex] == this)
{
rend.material.color = Color.green;
}
else
{
rend.material.color = Color.white;
}
}
if (currentHealth <= 0)
{
transform.rotation = Quaternion.Euler(new Vector3(90, 0, 0));
rend.material.color = Color.red;
gm.units.Remove(this);
gm.userUnits.Remove(this);
}
}
// This allows the child class UserPlayer to override what is in the base Player class' turnUpdate function.
public override void turnUpdate(){
// 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