- Home /
Level generation using a "tree" type structure, why when i add a cell i get an error argument out of range?
Hey there, i have been struggling for a while trying to get a generator working. I already have a working level generator using a 2D array. But that one just places nodes on random slots adjacent to already placed nodes.
(i call them nodes, but the nodes resembles eventual rooms)
I wanted to go for a different structure which would help me out in the long run.(when placing puzzles like: keys, boss rooms etc...) And a tree structure would do the trick perfectly. (since closing 1 node, would close all the nodes beyond it)
Anyway, i have a sort of working method. A generator script(which basically just places a simple 2D array, and generates the first node) Once the first node has been placed, i have another script/class. This resembles all nodes/rooms. It checks for adjacent open slots, and how many children this node is going to have. Once decided, it will try to add (a) node(s) in a random direction on one of the previously checked open slots. And here is it, where it goes wrong. Or at least sometimes it works but most of the times it leaves you with half of the level which it was originally going to generate.
Here is the generator script:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Map : MonoBehaviour
{
public static Map _instance;
public static Map instance
{
get
{
if(_instance == null)
_instance = GameObject.FindObjectOfType<Map>();
return _instance;
}
}
public int width;
public int height;
public int iterations;
[HideInInspector]
public int nodes;
public List<Node> node = new List<Node>();
public Node[,] grid;
public bool insideGrid(int x, int y)
{
if(x >= 0 && x < width && y >= 0 && y < height)
{
return true;
}
else return false;
}
public bool validPosition(int x, int y)
{
if(grid[x,y].type == 0) return true;
else return false;
}
public Node addNode(int x, int y, int id, int type, int children)
{
Node addedNode = new Node(x, y, id, type, children);
grid[x,y] = addedNode;
node.Add(new Node());
node[grid[x,y].id] = addedNode;
return addedNode;
}
void Start()
{
grid = new Node[width,height];
nodes = 0;
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
grid[x,y] = new Node();
grid[x,y].type = 0;
grid[x,y].x = x;
grid[x,y].y = y;
}
}
Generate();
}
void Generate()
{
Node firstNode = addNode(width/2,height/2,nodes,2,Random.Range(2,4));
firstNode.GenerateChildren();
}
void OnGUI()
{
float screenX = Screen.width;
float screenY = Screen.height;
float iconX = screenX/width;
float iconY = screenY/height;
//float nodeList = screenY/iterations;
int i = 0;
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
if(grid[x,y].type != 0)
{
GUI.Box(new Rect(x*iconX,y*iconY,iconX,iconY), "" + grid[x,y].id);
}
}
}
while(i < node.Count)
{
//GUI.Box(new Rect(0,i*nodeList,250,nodeList), "Node: " + node[i].id + " | " + node[i].x + "." + node[i].y);
i++;
}
}
}
And here is the class Node script:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class Node
{
public int id;
public int type;
public int x;
public int y;
public int children;
public int connections;
public bool top;
public bool bottom;
public bool left;
public bool right;
public Texture2D icon;
public List<Node> childs = new List<Node>();
public Node(int _x, int _y, int _id, int _type, int _children)
{
x = _x;
y = _y;
id = _id;
type = _type;
children = _children;
}
public List<int[]> setNeighbors()
{
List<int[]> neighbors = new List<int[]>();
if(Map.instance.insideGrid(x+1,y))
{
if(Map.instance.validPosition(x+1,y)) neighbors.Add(new int[]{x+1,y});
}
if(Map.instance.insideGrid(x-1,y))
{
if(Map.instance.validPosition(x-1,y)) neighbors.Add(new int[]{x-1,y});
}
if(Map.instance.insideGrid(x,y+1))
{
if(Map.instance.validPosition(x,y+1)) neighbors.Add(new int[]{x,y+1});
}
if(Map.instance.insideGrid(x,y-1))
{
if(Map.instance.validPosition(x,y-1)) neighbors.Add(new int[]{x,y-1});
}
return neighbors;
}
public void GenerateChildren()
{
List<int[]> neighbors = setNeighbors();
int i = 0;
int selected = 0;
int j = 0;
if(neighbors.Count <= 0)
{
neighbors = setNeighbors();
}
while(i < children)
{
selected = Random.Range(0,neighbors.Count);
Debug.Log("neighbors: " + neighbors.Count + " / childs: " + children + " / selected: " + selected);
childs.Add(new Node());
if(Map.instance.nodes < Map.instance.iterations)
{
Map.instance.nodes++;
childs[i] = addChildren(neighbors[selected][0],neighbors[selected][1],Map.instance.nodes,1,Random.Range(1,3));
neighbors.RemoveAt(selected);
}
i++;
}
while(j < children)
{
if(Map.instance.nodes < Map.instance.iterations)
{
childs[j].GenerateChildren();
}
j++;
}
}
public Node addChildren(int _x, int _y, int _id, int _type, int _children)
{
return Map.instance.addNode(_x,_y,_id,_type,_children);
}
public Node()
{
}
}
It gives me an error on the line where i try to add a child to the child's List. I added a Debug.Log above it, trying to see how it could select a child which is out of range. (but i don't see the problem in the debug log, since it should select within child.Count ' s reach)
I hope someone could help me! The class code is probably messy, i would have to clean this up once i got the problem sorted out.
Any help is appreciated!
Cheers, Darryl
Answer by darryldonohoe · Apr 26, 2015 at 05:16 PM
Okay i seemed to fix the problem. If anyone is wondering how i fixed it. Here are the codes:
The generator:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Map : MonoBehaviour
{
public static Map _instance;
public static Map instance
{
get
{
if(_instance == null)
_instance = GameObject.FindObjectOfType<Map>();
return _instance;
}
}
public int width;
public int height;
public int iterations;
[HideInInspector]
public int nodes;
public List<Node> node = new List<Node>();
public Node[,] grid;
public bool insideGrid(int x, int y)
{
if(x >= 0 && x < width && y >= 0 && y < height)
{
return true;
}
else return false;
}
public bool validPosition(int x, int y)
{
if(grid[x,y].type == 0) return true;
else return false;
}
public Node addNode(int x, int y, int id, int type, int children)
{
Node addedNode = new Node(x, y, id, type, children);
grid[x,y] = new Node(x, y, id, type, children);
node.Add(new Node(x,y,id,type,children));
return addedNode;
}
void Start()
{
grid = new Node[width,height];
nodes = 0;
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
grid[x,y] = new Node();
grid[x,y].type = 0;
grid[x,y].x = x;
grid[x,y].y = y;
}
}
Generate();
}
void Generate()
{
Node firstNode = addNode(width/2,height/2,nodes,2,Random.Range(2,4));
nodes++;
firstNode.GenerateChildren();
}
void OnGUI()
{
float screenX = Screen.width;
float screenY = Screen.height;
float iconX = screenX/width;
float iconY = screenY/height;
//float nodeList = screenY/iterations;
int i = 0;
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
if(grid[x,y].type != 0)
{
GUI.Box(new Rect(x*iconX,y*iconY,iconX,iconY), "" + grid[x,y].id);
}
}
}
while(i < node.Count)
{
//GUI.Box(new Rect(0,i*nodeList,250,nodeList), "Node: " + node[i].id + " | " + node[i].x + "." + node[i].y);
i++;
}
}
}
And the Node/class:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class Node
{
public int id;
public int type;
public int x;
public int y;
public int children;
public int connections;
public bool top;
public bool bottom;
public bool left;
public bool right;
public Texture2D icon;
public List<Node> child = new List<Node>();
public Node(int _x, int _y, int _id, int _type, int _children)
{
x = _x;
y = _y;
id = _id;
type = _type;
children = _children;
}
public List<int[]> setNeighbors()
{
List<int[]> neighbors = new List<int[]>();
if(Map.instance.insideGrid(x+1,y))
{
if(Map.instance.validPosition(x+1,y)) neighbors.Add(new int[]{x+1,y});
}
if(Map.instance.insideGrid(x-1,y))
{
if(Map.instance.validPosition(x-1,y)) neighbors.Add(new int[]{x-1,y});
}
if(Map.instance.insideGrid(x,y+1))
{
if(Map.instance.validPosition(x,y+1)) neighbors.Add(new int[]{x,y+1});
}
if(Map.instance.insideGrid(x,y-1))
{
if(Map.instance.validPosition(x,y-1)) neighbors.Add(new int[]{x,y-1});
}
return neighbors;
}
public void GenerateChildren()
{
List<int[]> neighbors = setNeighbors();
int selected = 0;
int i = 0;
while(i < children)
{
if(neighbors.Count == 0)
{
i++;
}
else
{
selected = Random.Range(0,neighbors.Count);
if(Map.instance.nodes < Map.instance.iterations)
{
child.Add(new Node());
child[i] = addChildren(neighbors[selected][0],neighbors[selected][1],Map.instance.nodes,1,Random.Range(1,3));
}
neighbors.RemoveAt(selected);
i++;
}
}
foreach(Node childs in child)
{
if(childs == null)
{
Debug.Log ("null");
}
else childs.GenerateChildren();
}
}
public Node addChildren(int _x, int _y, int _id, int _type, int _children)
{
Map.instance.nodes++;
return Map.instance.addNode(_x,_y,_id,_type,_children);
}
public Node()
{
}
}
It can generate a level with 5000 nodes in under 2 seconds(on my computer). However since its not optimized for a level with that amount of rooms, i get a 3 - 4 fps once the level has been generated.
I still want to ask if someone can give me tips on this script. Like is this readable for you guys, how would you write certain methods and such.
Anyway thanks!
Cheers, Darryl