2D Roguelike Enemy in Walls
I have been working on the 2D Roguelike tutorial with my students. The game is working great, but we all have the same issue. Occasionally, the enemies will spawn on the same block as an interior wall making them "stuck" for the level. It does not seem to happen in the completed game that downloaded with the resources, and as far as I can tell our scripts match the ones in the completed game perfectly. Is anyone else having the same issue? Please advise on how to fix it!
@fantonucci When asking questions of these sorts it makes it easier on the people answering if you included a script. Possibly the script that spawns the enemies in the first place.
Answer by fantonucci · May 30, 2018 at 01:36 PM
OK, Here are my BoardManager and GameManager scripts. They're responsible for the scene setup so I have a feeling the issue might be there:
//BoardManager
using System.Collections; using System; using System.Collections.Generic; using UnityEngine; using Random = UnityEngine.Random;
public class BoardManager : MonoBehaviour { //make Inspector have a drop-down [Serializable] public class Count { public int minimum; public int maximum;
public Count(int min, int max)
{
minimum = min;
maximum = max;
}
}
public int columns = 8;
public int rows = 8;
//level will have a min of 5 walls and a max of 9
public Count wallCount = new Count(5, 9);
public Count foodCount = new Count(1, 5);
public GameObject exit;
public GameObject[] floorTiles;
public GameObject[] wallTiles;
public GameObject[] enemyTiles;
public GameObject[] foodTiles;
public GameObject[] outerWallTiles;
private Transform boardHolder;
private List<Vector3> gridPositions = new List<Vector3>();
//Create list of possible positions for enemies, food and walls
void InitializeList()
{
gridPositions.Clear();
//Leaving border empty so level is passable
for (int x = 1; x < (columns - 1); x++)
{
for (int y = 1; y < (rows - 1); y++)
{
gridPositions.Add(new Vector3(x, y, 0f));
}
}
}
//Lay out the Outer Walls and Floor Tiles
void BoardSetup()
{
boardHolder = new GameObject("Board").transform;
for (int x = -1; x < columns + 1; x++)
{
for (int y = -1; y < rows + 1; y++)
{
GameObject toInstantiate = floorTiles[Random.Range(0, floorTiles.Length)];
//If this spot should be an Outer Wall instead
if ((x == -1) || (x == columns) || (y == -1) || (y == rows))
toInstantiate = outerWallTiles[Random.Range(0, outerWallTiles.Length)];
//Instantiate (object, position, rotation)
GameObject instance = Instantiate(toInstantiate, new Vector3(x, y, 0f), Quaternion.identity) as GameObject;
instance.transform.SetParent(boardHolder);
}
}
}
//Generate random position for the object
Vector3 RandomPosition()
{
int randomIndex = Random.Range(0, gridPositions.Count);
Vector3 randomPosition = gridPositions[randomIndex];
//We don't want to put more than 1 item in the same spot
gridPositions.RemoveAt(randomIndex);
return randomPosition;
}
//Put down random objects
void LayoutObjectAtRandom(GameObject[] tileArray, int minimum, int maximum)
{
//How many of a random object we will spawn
int objectCount = Random.Range(minimum, maximum + 1);
Debug.Log(objectCount);
for (int i = 0; i < objectCount; i++)
{
Vector3 randomPosition = RandomPosition();
GameObject tileChoice = tileArray[Random.Range(0, tileArray.Length)];
Instantiate(tileChoice, randomPosition, Quaternion.identity);
}
}
//must be a public method because called by GameManager script
public void SetupScene(int level)
{
//Put down outer walls and floor
BoardSetup();
InitializeList(); //Creates gridPositions list
LayoutObjectAtRandom(wallTiles, wallCount.minimum, wallCount.maximum);
LayoutObjectAtRandom(foodTiles, foodCount.minimum, foodCount.maximum);
//Generate # of enemies based on level # - difficulty
//1 enemy at level 2, 2 at level 4, 3 at level 8
int enemyCount = (int)Mathf.Log(level, 2f);
LayoutObjectAtRandom(enemyTiles, enemyCount, enemyCount);
Instantiate(exit, new Vector3(columns - 1, rows - 1, 0f), Quaternion.identity);
}
}
//GameManager
using System.Collections; using System.Collections.Generic; //Lets you use Lists using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour { //So we can call SetupScene private BoardManager boardScript;
private int level = 1;
public static GameManager instance = null;
public int playerFoodPoints = 100;
[HideInInspector] public bool playersTurn = true;
public float turnDelay = .1f;
private float levelDelay = 2f;
private Text levelText;
private GameObject levelImage;
private List<Enemy> enemies;
private bool enemiesMoving;
private bool firstRun = true;
//Prevents Player from moving during setup
private bool doingSetup = true;
// Use this for initialization
void Awake()
{
if (instance == null)
instance = this;
else if (instance != this)
Destroy(gameObject); //destroys duplicate GameManager
//When we load a new scene, don't want to destroy score
DontDestroyOnLoad(gameObject);
enemies = new List<Enemy>();
boardScript = GetComponent<BoardManager>();
InitGame();
}
void InitGame()
{
doingSetup = true;
levelImage = GameObject.Find("LevelImage");
levelText = GameObject.Find("LevelText").GetComponent<Text>();
levelText.text = "Day " + level;
levelImage.SetActive(true);
Invoke("HideLevelImage", levelDelay);
//Clear out enemies between levels
//GameManager never exits
enemies.Clear();
boardScript.SetupScene(level);
}
private void HideLevelImage()
{
levelImage.SetActive(false);
doingSetup = false;
}
//This is called each time a scene is loaded
void OnLevelFinishedLoading(Scene scene, LoadSceneMode mode)
{
if (firstRun)
{
firstRun = false;
return;
}
level++;
InitGame();
}
void OnEnable()
{
//Tell our 'OnLevelFinishedLoading' function to
//start listening for a scene change as soon
//as the script is enabled
SceneManager.sceneLoaded += OnLevelFinishedLoading;
}
void OnDisable()
{
//Tell OnLevelFinishedLoading to stop listening
//for a scene change as soon as the script
//is disabled
SceneManager.sceneLoaded -= OnLevelFinishedLoading;
}
//Coroutine to move our enemies in sequence
IEnumerator MoveEnemies()
{
enemiesMoving = true;
yield return new WaitForSeconds(turnDelay);
//Pause if there's no enemies
if (enemies.Count == 0)
yield return new WaitForSeconds(turnDelay);
//Loop through enemies list and move them
for (int i = 0; i < enemies.Count; i++)
{
enemies[i].MoveEnemy();
yield return new WaitForSeconds(enemies[i].moveTime);
}
playersTurn = true;
enemiesMoving = false;
}
public void GameOver()
{
levelText.text = "After " + level + " days, you starved.";
levelImage.SetActive(true);
enabled = false;
}
// Update is called once per frame
void Update()
{
if (playersTurn || enemiesMoving || doingSetup)
return;
StartCoroutine(MoveEnemies());
}
//Have enemies register with GameManager so they
//follow orders to move
public void AddEnemyToList(Enemy script)
{
enemies.Add(script);
}
}
Answer by KittenSnipes · Jun 29, 2018 at 12:25 AM
I have come back to this question and your GameManager script does have some slight changes but I am pretty sure that it should not effect how it runs although I have never used the methods you used. OnEnable is called when the object becomes enabled and active. Plus the original script never does anything when the object becomes disabled and inactive yet yours does. Maybe it is the fact you do something on disable or maybe it is because the OnEnable method is slower than the original method the tutorial uses. Plus you never use the current instances level to increment and to run the function InitGame(). So after all that here is the updated version of your GameManager Script:
private BoardManager boardScript;
private int level = 1;
public static GameManager instance = null;
public int playerFoodPoints = 100;
[HideInInspector] public bool playersTurn = true;
public float turnDelay = .1f;
private float levelDelay = 2f;
private Text levelText;
private GameObject levelImage;
private List<Enemy> enemies;
private bool enemiesMoving;
//Prevents Player from moving during setup
private bool doingSetup = true;
// Use this for initialization
void Awake()
{
if (instance == null)
instance = this;
else if (instance != this)
Destroy(gameObject); //destroys duplicate GameManager
//When we load a new scene, don't want to destroy score
DontDestroyOnLoad(gameObject);
enemies = new List<Enemy>();
boardScript = GetComponent<BoardManager>();
InitGame();
}
void InitGame()
{
doingSetup = true;
levelImage = GameObject.Find("LevelImage");
levelText = GameObject.Find("LevelText").GetComponent<Text>();
levelText.text = "Day " + level;
levelImage.SetActive(true);
Invoke("HideLevelImage", levelDelay);
//Clear out enemies between levels
//GameManager never exits
enemies.Clear();
boardScript.SetupScene(level);
}
private void HideLevelImage()
{
levelImage.SetActive(false);
doingSetup = false;
}
//The 2 functions I added. These were different than the ones you had in your script.
//this is called only once, and the paramter tells it to be called only after the scene was loaded
//(otherwise, our Scene Load callback would be called the very first load, and we don't want that)
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static public void CallbackInitialization()
{
//register the callback to be called everytime the scene is loaded
SceneManager.sceneLoaded += OnSceneLoaded;
}
//This is called each time a scene is loaded.
static private void OnSceneLoaded(Scene arg0, LoadSceneMode arg1)
{
instance.level++;
instance.InitGame();
}
//Hopefully these make it work because this was the only difference.
//Coroutine to move our enemies in sequence
IEnumerator MoveEnemies()
{
enemiesMoving = true;
yield return new WaitForSeconds(turnDelay);
//Pause if there's no enemies
if (enemies.Count == 0)
yield return new WaitForSeconds(turnDelay);
//Loop through enemies list and move them
for (int i = 0; i < enemies.Count; i++)
{
enemies[i].MoveEnemy();
yield return new WaitForSeconds(enemies[i].moveTime);
}
playersTurn = true;
enemiesMoving = false;
}
public void GameOver()
{
levelText.text = "After " + level + " days, you starved.";
levelImage.SetActive(true);
enabled = false;
}
// Update is called once per frame
void Update()
{
if (playersTurn || enemiesMoving || doingSetup)
return;
StartCoroutine(MoveEnemies());
}
//Have enemies register with GameManager so they
//follow orders to move
public void AddEnemyToList(Enemy script)
{
enemies.Add(script);
}
Your answer
Follow this Question
Related Questions
2d roguelike tutorial board manager issues 2 Answers
OverslapSphere dont detect enemyhealth 0 Answers
Something wrong with enemy tilting in space shooter extending tutorial? 0 Answers
Problems with enemies 1 Answer
RogueLike 2D Tutorial Help 0 Answers