Skip to content

Instantly share code, notes, and snippets.

@IronMonk-UK
Created April 25, 2016 17:48
Show Gist options
  • Save IronMonk-UK/f8cf88e9a7f5db56a858285ab25c2b61 to your computer and use it in GitHub Desktop.
Save IronMonk-UK/f8cf88e9a7f5db56a858285ab25c2b61 to your computer and use it in GitHub Desktop.
All code towards the project up to 25/04/16
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 Barbarian(Unit u){
u.totalHealth = 15;
u.strength = 3;
u.defence = 1;
u.accuracy = .6f;
u.damageRollSides = 8;
u.moveLimit = 6;
u.axe = true;
}
public static void Knight(Unit u){
u._class = "Knight";
u.totalHealth = 12;
u.strength = 2;
u.defence = 2;
u.accuracy = .7f;
u.damageRollSides = 7;
u.moveLimit = 8;
u.spear = true;
}
public static void Mercenary(Unit u){
u._class = "Mercenary";
u.totalHealth = 10;
u.strength = 2;
u.defence = 1;
u.accuracy = .9f;
u.damageRollSides = 6;
u.moveLimit = 6;
u.sword = 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;
// 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;
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);
}
}
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 (unitsInRange.Count == 0 && noUnitsinRange && inRangeOfUnits.Count == 0) {
Debug.Log("In Range Of Check");
inRangeOf();
}
if ((inRangeOfUnits.Count >= 1 && !healthChecked) || (nothingInRange && !healthChecked)) {
Debug.Log("Health Check");
checkHealth();
}
if(healthChecked && nothingInRange) {
Debug.Log("Use Vial");
useVial();
} else if (healthChecked && inRangeOfPos.Count >= 1 && !moving && !fleeing) {
Debug.Log("Move from Positive");
moveFromPos();
} else if(healthChecked && inRangeOfPos.Count >= 1 && 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() {
// Checks every user unit on the field
foreach (UserUnit u in gm.userUnits) {
// 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;
}
}
public void inRangeOf() {
foreach (UserUnit u in gm.userUnits) {
path.target.transform.position = u.currentTile._pos;
path.FindPath();
if(grid.path.Count - 1 <= u.moveLimit) {
inRangeOfUnits.Add(u);
if(equippedWeapon.negType == u.equippedWeapon.weaponType) {
inRangeOfPos.Add(u);
}
}
}
path.target.transform.position = currentTile._pos;
path.FindPath();
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 space 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 space 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 space 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;
}
}
}
public void moveFromPos() {
path.target.transform.position = inRangeOfPos[0].currentTile._pos;
path.FindPath();
int difference = (inRangeOfPos[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();
}
public void moveFromRange() {
}
public void checkHealth() {
if((currentHealth <= totalHealth - 10) || (totalHealth <= 10 && currentHealth <= totalHealth - 2))
{
foreach (Item i in inventory) {
if (i.itemType == Item.ItemType.Tool) {
canUseVial = true;
healthVialToUse = i;
break;
}
}
}
if (!canUseVial) {
cannotUseVial = true;
}
healthChecked = true;
}
void useVial() {
if (canUseVial) {
healthVialToUse._heal(this);
inventory.Remove(healthVialToUse);
healthVialToUse = null;
Debug.Log("Vial was used");
gm.EndTurn();
} else if (cannotUseVial) {
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 _eName;
public Text _eClass;
public Text _eHealth;
public Text _eStrength;
public Text _eDefence;
public Text _eAccuracy;
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 posHit;
bool negHit;
bool hit;
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>();
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 = "";
}
void Update()
{
if (currentUnitIndex >= units.Count)
{
currentUnitIndex = 0;
pathfinding.target.transform.position = units[currentUnitIndex].currentTile._pos;
pathfinding.FindPath();
}
if (!unitsPositioned)
{
units[0].transform.position = grid.grid[1, 10].tile._pos;
units[0].currentTile = grid.grid[1, 10].tile;
units[1].transform.position = grid.grid[4, 23].tile._pos;
units[1].currentTile = grid.grid[4, 23].tile;
units[2].transform.position = grid.grid[6, 14].tile._pos;
units[2].currentTile = grid.grid[6, 14].tile;
units[3].transform.position = grid.grid[6, 5].tile._pos;
units[3].currentTile = grid.grid[6, 5].tile;
units[4].transform.position = grid.grid[7, 17].tile._pos;
units[4].currentTile = grid.grid[7, 17].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
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]); });
}
}
}
/* 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();
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;
//yield return new WaitForSeconds(.5f);
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();
}
}
Vector3 up = new Vector3(units[currentUnitIndex].transform.position.x, units[currentUnitIndex].transform.position.y - 2, units[currentUnitIndex].transform.position.z + 1);
Vector3 down = new Vector3(units[currentUnitIndex].transform.position.x, units[currentUnitIndex].transform.position.y - 2, units[currentUnitIndex].transform.position.z - 1);
Vector3 left = new Vector3(units[currentUnitIndex].transform.position.x - 1, units[currentUnitIndex].transform.position.y - 2, units[currentUnitIndex].transform.position.z);
Vector3 right = new Vector3(units[currentUnitIndex].transform.position.x + 1, units[currentUnitIndex].transform.position.y - 2, units[currentUnitIndex].transform.position.z);
}
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;
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].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. */
if (posHit)
{
int 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)
{
int 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)
{
int 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(target._name + " miss.");
}
}
else
{
Debug.Log("Target not available");
}
}
}
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";
unit._class = "Barbarian";
_Class.Barbarian(unit);
Items.sItems.IronAxe(unit);
Items.sItems.healthVial(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";
unit._class = "Mercenary";
_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";
unit._class = "Knight";
_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 = "Damien";
aUnit._class = "Mercenary";
_Class.Mercenary(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 = "Isaac";
aUnit._class = "Knight";
_Class.Knight(aUnit);
Items.sItems.IronSpear(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 && !aiUnits.Contains(units[currentUnitIndex])) {
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;
}
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;
}
}
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;
void Start() {
pathfinding = GameObject.FindWithTag("A*").GetComponent<Pathfinding>();
grid = GameObject.FindWithTag("A*").GetComponent<Grid>();
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;
foreach(Unit u in GameManager.gm.units)
{
if (u.gridPosition == gridPosition)
{
//node.walkable = false;
}
}
}
void OnMouseEnter() {
if (GameManager.gm.units[GameManager.gm.currentUnitIndex].moving && !GameManager.gm.unitMoving) {
pathfinding.target.transform.position = _pos;
pathfinding.FindPath();
}
if (!blue && !GameManager.gm.units[GameManager.gm.currentUnitIndex].attacking) {
blue = true;
rend.material.color = Color.blue;
}
else if (GameManager.gm.units[GameManager.gm.currentUnitIndex].attacking) {
rend.material.color = Color.red;
}
foreach (Unit u in GameManager.gm.units)
{
if (u.gridPosition == gridPosition && u != GameManager.gm.units[GameManager.gm.currentUnitIndex])
{
Debug.Log("Unit found");
GameManager.gm.EnemyStatsUp();
GameManager.gm._eName.text = u._name;
GameManager.gm._eClass.text = u._class;
GameManager.gm._eHealth.text = u.currentHealth.ToString() + " / " + u.totalHealth.ToString();
GameManager.gm._eStrength.text = u.strength.ToString();
GameManager.gm._eDefence.text = u.defence.ToString();
GameManager.gm._eAccuracy.text = u.accuracy.ToString();
}
}
}
void OnMouseExit() {
if (!movingTile && node.walkable)
rend.material.color = Color.white;
if (blue)
blue = false;
foreach (Unit u in GameManager.gm.units) {
if (u.gridPosition == gridPosition) {
GameManager.gm.EnemyStatsDown();
}
}
}
void OnMouseDown() {
if (GameManager.gm.units[GameManager.gm.currentUnitIndex].moving) {
pathfinding.pathTraced = false;
pathfinding.FindPath();
pathfinding.pathTraced = true;
GameManager.gm.moveCurrentPlayer();
GameManager.gm.units[GameManager.gm.currentUnitIndex].gridPosition = gridPosition;
}
else if (GameManager.gm.units[GameManager.gm.currentUnitIndex].attacking)
GameManager.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);
Destroy(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