JvlS Dev Labyrinth: Difference between revisions

From læi game development
Jump to navigation Jump to search
(Dev & code page for Labyrinth in Unity)
 
Line 7: Line 7:
Dedalus is quite simple and works in two stages:
Dedalus is quite simple and works in two stages:
== Create a grid floor ==
== Create a grid floor ==
The first version utilizes plane primitive. This is a problem since Unity's WebGL compiler will NOT generate primitives, for some obscure reason.
<SyntaxHighlight lang="c#" line='line'>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Dedalus: Maze builder for J'ai vu le soleil
/// Code by laei
/// </summary>
public enum linkPosition{
//which wall is that link on
north,
east,
south,
west
}
public class link{
public cell nearCell;
public bool openWall;
public linkPosition linkPos;
}
public class cell{
public GameObject GO;
public float size;
public List<link> links;
public int distanceIndex;
}
public class Dedalus : MonoBehaviour {
public int seed;
public int width;
public int length;
public float CellSurfaceSize;
public Vector2 StartCell = new Vector2(0,0);
public Vector2 EndCell = new Vector3(1,1);
public cell[,] floor;
private int seedIndex;
public void reset(){
foreach(cell C in floor){
DestroyImmediate(C.GO);
}
floor = new cell[width, length];
}
public void makeFloor(){
floor = new cell[width, length];
for(int i = 0; i < width; i++){
for(int j = 0; j < length; j++){
floor[i,j] = new cell();
//floor[i,j].GO = new GameObject();
floor[i,j].GO = GameObject.CreatePrimitive(PrimitiveType.Plane);
GameObject go = floor[i,j].GO;
go.transform.localScale = new Vector3(CellSurfaceSize / 10f,CellSurfaceSize /10f,CellSurfaceSize /10f);
go.name = "Cell_[" + i + "," + j +"]";
//move gameobject in its place
go.transform.position = new Vector3(i * CellSurfaceSize + this.gameObject.transform.position.x, this.gameObject.transform.position.y, j * CellSurfaceSize + this.gameObject.transform.position.z);
go.transform.parent = this.transform;
floor[i,j].links = new List<link>();
floor[i,j].distanceIndex = -1;
}
}
{
//leftWall
GameObject LW = GameObject.CreatePrimitive(PrimitiveType.Cube);
LW.name = "LeftWall";
LW.transform.parent = this.transform;
LW.transform.localScale = new Vector3(CellSurfaceSize * width, 3f, 1f);
LW.transform.localPosition = new Vector3((CellSurfaceSize * width/ 2f) - (0.5f * CellSurfaceSize), 1.5f, - CellSurfaceSize / 2f);
//RightWall
GameObject RW = GameObject.CreatePrimitive(PrimitiveType.Cube);
RW.name = "RightWall";
RW.transform.parent = this.transform;
RW.transform.localScale = new Vector3(CellSurfaceSize * width, 3f, 1f);
RW.transform.localPosition = new Vector3((CellSurfaceSize * width / 2f) - (0.5f * CellSurfaceSize), 1.5f, CellSurfaceSize * length - CellSurfaceSize / 2f);
//North
GameObject NW = GameObject.CreatePrimitive(PrimitiveType.Cube);
NW.name = "NorthWall";
NW.transform.parent = this.transform;
NW.transform.localScale = new Vector3(1f, 3f, CellSurfaceSize * length);
NW.transform.localPosition = new Vector3(CellSurfaceSize * width - CellSurfaceSize / 2f, 1.5f, (CellSurfaceSize * length / 2f) - (0.5f * CellSurfaceSize));
//South
GameObject SW = GameObject.CreatePrimitive(PrimitiveType.Cube);
SW.name = "SouthWall";
SW.transform.parent = this.transform;
SW.transform.localScale = new Vector3(1f, 3f, CellSurfaceSize * length);
SW.transform.localPosition = new Vector3( - CellSurfaceSize / 2f, 1.5f, (CellSurfaceSize * length / 2f) - (0.5f * CellSurfaceSize));
//Goal
GameObject Goal = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
Goal.name = "Goal";
Goal.transform.parent = this.transform;
Goal.transform.localScale = new Vector3(1f, 200f, 1f);
Goal.transform.localPosition = floor[floor.GetLength(0) - 1, floor.GetLength(1) - 1].GO.transform.position + Vector3.up * 100f;
}
// Populating the links
for(int i = 0; i < width; i++){
for(int j = 0; j < length; j++){
if(i > 0){
link daLink = new link();
daLink.nearCell = floor[i - 1, j];
daLink.openWall = true;
daLink.linkPos = linkPosition.east;
floor[i,j].links.Add(daLink);
}
if(i < (width - 1)){
link daLink = new link();
daLink.nearCell = floor[i + 1, j];
daLink.openWall = true;
daLink.linkPos = linkPosition.west;
floor[i,j].links.Add(daLink);
}
if(j > 0){
link daLink = new link();
daLink.nearCell = floor[i, j - 1];
daLink.openWall = true;
daLink.linkPos = linkPosition.north;
floor[i,j].links.Add(daLink);
}
if(j < (length - 1)){
link daLink = new link();
daLink.nearCell = floor[i, j + 1];
daLink.openWall = true;
daLink.linkPos = linkPosition.south;
floor[i,j].links.Add(daLink);
}
}
}
}
public void evaluate(){
List<cell> toVisit = new List<cell>();
List<cell> toVisitNext = new List<cell>();
int currentDistance = 0;
toVisit.Add(floor[(int)StartCell.x, (int)StartCell.y]);
while(toVisit.Count > 0 && currentDistance < width*length){
toVisitNext.Clear();
foreach(cell C in toVisit){
C.distanceIndex = currentDistance;
foreach(link nxt in C.links){
if(nxt.openWall && nxt.nearCell.distanceIndex == -1) //checks the wall is open & nearCell is not already visited.
toVisitNext.Add(nxt.nearCell);
}
}
toVisit.Clear();
toVisit.AddRange(toVisitNext);
currentDistance++;
}
//Checking
if(toVisit.Count !=0){
Debug.Log("Incomplete visit - " + toVisit.Count);
foreach(cell c in toVisit){
Debug.Log(c.GO.name + " is unchecked");
}
}
for(int i = 0; i < width; i++){
for(int j = 0; j < length; j++){
if(floor[i,j].distanceIndex == -1)
Debug.Log("Warning: " + this.name + " has innaccessible cell@ " + i + ", " + j);
}
}
Debug.Log("Maze shortest path is " + floor[(int)EndCell.x, (int)EndCell.y].distanceIndex + " cells long");
}
public void amazeMe(){
//Recursive Backtracking Algorithm
//Erase all distances
foreach(cell C in floor){
C.distanceIndex = -1;
}
seedIndex = 0;
cell current = floor[(int)StartCell.x, (int)StartCell.y];
recursion(current, 0);
foreach(cell C in floor){
foreach(link L in C.links){
L.openWall = (Mathf.Abs(C.distanceIndex - L.nearCell.distanceIndex) == 1);
if(!L.openWall){
//build wall
GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
wall.name = L.linkPos.ToString() + "WallOn_" + C.GO.name;
wall.transform.localScale = new Vector3(CellSurfaceSize + 1.5f ,3f , 1f);
wall.transform.parent = C.GO.transform;
switch(L.linkPos){
case linkPosition.east :
wall.transform.localPosition = Vector3.left * (CellSurfaceSize + 0.75f)+ Vector3.up * 3f;
wall.transform.Rotate(new Vector3(0f,90f,0f));
break;
case linkPosition.west :
wall.transform.localPosition = Vector3.right* (CellSurfaceSize + 0.75f) + Vector3.up * 3f;
wall.transform.Rotate(new Vector3(0f,90f,0f));
break;
case linkPosition.north :
wall.transform.localPosition = Vector3.back * (CellSurfaceSize + 0.75f) + Vector3.up * 3f;
break;
case linkPosition.south :
wall.transform.localPosition =  Vector3.forward * (CellSurfaceSize + 0.75f) + Vector3.up * 3f;
break;
}
}
}
}
materializeMe();
}
private int seedRead(){
int i = seedIndex;
seedIndex++;
if(seedIndex == seed.ToString().Length)
seedIndex = 0;
return int.Parse(seed.ToString().Substring(i, 1));
}
private void recursion(cell C, int currentDist){
bool deadend = false;
C.distanceIndex = currentDist;
while(!deadend){
bool canVisitNeighbour = false;
for(int i = 0; i < C.links.Count && !canVisitNeighbour; i++)
canVisitNeighbour = C.links[i].openWall && C.links[i].nearCell.distanceIndex == -1;
if(canVisitNeighbour){
List<cell> visitable = new List<cell>();
for(int i = 0; i < C.links.Count; i++){
if(C.links[i].openWall && C.links[i].nearCell.distanceIndex == -1)
visitable.Add(C.links[i].nearCell);
}
//May require the double check for bidirectionality
int alea = seedRead() % 4;
recursion(visitable[alea % visitable.Count], currentDist + 1);
}else
deadend = true;
}
}
public void materializeMe(){
Material Blank = Resources.Load<Material>("Blank");
//List<MeshRenderer> MRl = new List<MeshRenderer>(GetComponents<MeshRenderer>());
List<Transform> children =  new List<Transform>(this.transform.GetComponentsInChildren<Transform>());
children.Remove(this.transform);
foreach(Transform child in children){
Material[] m = child.gameObject.GetComponent<Renderer>().materials;
if(m != null){
m[0] = Blank;
child.gameObject.GetComponent<MeshRenderer>().materials = m;
}
}
}
// Use this for initialization
void Start () {
CellSurfaceSize = 5f;
seed = Random.Range(12021, int.MaxValue);
width = Random.Range(8,75);
length = Random.Range(8,75);
makeFloor();
amazeMe();
}
// Update is called once per frame
void Update () {
if(Input.GetKeyDown(KeyCode.T)){
List<Transform> toDestroy =  new List<Transform>(this.transform.GetComponentsInChildren<Transform>());
toDestroy.Remove(this.transform);
foreach(Transform GO in toDestroy){
Destroy(GO.gameObject);
}
reset();
seed = Random.Range(12021, int.MaxValue);
width = Random.Range(8,75);
length = Random.Range(8,75);
makeFloor();
amazeMe();
}
}
}
</SyntaxHighlight>
== Make a maze of it ==
== Make a maze of it ==

Revision as of 09:34, 1 January 2017

Development page for the Labyrinth system in J'ai vu le Soleil

Stage One: Dedalus

Well, at first, my goal was to build a simple procedural Labyrinth. So, because I'm a smartass, I called it Dedalus.

Dedalus is quite simple and works in two stages:

Create a grid floor

The first version utilizes plane primitive. This is a problem since Unity's WebGL compiler will NOT generate primitives, for some obscure reason.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


/// <summary>
/// Dedalus: Maze builder for J'ai vu le soleil
/// Code by laei
/// </summary>

public enum linkPosition{
	//which wall is that link on
	north,
	east,
	south,
	west

}

public class link{
	public cell nearCell;
	public bool openWall;
	public linkPosition linkPos;

}

public class cell{
	public GameObject GO;
	public float size;
	public List<link> links;
	public int distanceIndex;
}




public class Dedalus : MonoBehaviour {
	public int seed;
	public int width;
	public int length;
	public float CellSurfaceSize;

	public Vector2 StartCell = new Vector2(0,0);
	public Vector2 EndCell = new Vector3(1,1);
	public cell[,] floor;

	private int seedIndex;

	public void reset(){
		foreach(cell C in floor){
			DestroyImmediate(C.GO);
		}
		floor = new cell[width, length];
	}

	public void makeFloor(){
		floor = new cell[width, length];
		for(int i = 0; i < width; i++){
			for(int j = 0; j < length; j++){
				floor[i,j] = new cell();
				//floor[i,j].GO = new GameObject();
				floor[i,j].GO = GameObject.CreatePrimitive(PrimitiveType.Plane);
				GameObject go = floor[i,j].GO;
				go.transform.localScale = new Vector3(CellSurfaceSize / 10f,CellSurfaceSize /10f,CellSurfaceSize /10f);
				go.name = "Cell_[" + i + "," + j +"]";
				//move gameobject in its place
				go.transform.position = new Vector3(i * CellSurfaceSize + this.gameObject.transform.position.x, this.gameObject.transform.position.y, j * CellSurfaceSize + this.gameObject.transform.position.z);
				go.transform.parent = this.transform;
				floor[i,j].links = new List<link>();
				floor[i,j].distanceIndex = -1;
			}
		}
		{
			//leftWall
			GameObject LW = GameObject.CreatePrimitive(PrimitiveType.Cube);
			LW.name = "LeftWall";
			LW.transform.parent = this.transform;
			LW.transform.localScale = new Vector3(CellSurfaceSize * width, 3f, 1f);
			LW.transform.localPosition = new Vector3((CellSurfaceSize * width/ 2f) - (0.5f * CellSurfaceSize), 1.5f, - CellSurfaceSize / 2f);

			//RightWall
			GameObject RW = GameObject.CreatePrimitive(PrimitiveType.Cube);
			RW.name = "RightWall";
			RW.transform.parent = this.transform;
			RW.transform.localScale = new Vector3(CellSurfaceSize * width, 3f, 1f);
			RW.transform.localPosition = new Vector3((CellSurfaceSize * width / 2f) - (0.5f * CellSurfaceSize), 1.5f, CellSurfaceSize * length - CellSurfaceSize / 2f);

			//North
			GameObject NW = GameObject.CreatePrimitive(PrimitiveType.Cube);
			NW.name = "NorthWall";
			NW.transform.parent = this.transform;
			NW.transform.localScale = new Vector3(1f, 3f, CellSurfaceSize * length);
			NW.transform.localPosition = new Vector3(CellSurfaceSize * width - CellSurfaceSize / 2f, 1.5f, (CellSurfaceSize * length / 2f) - (0.5f * CellSurfaceSize));

			//South
			GameObject SW = GameObject.CreatePrimitive(PrimitiveType.Cube);
			SW.name = "SouthWall";
			SW.transform.parent = this.transform;
			SW.transform.localScale = new Vector3(1f, 3f, CellSurfaceSize * length);
			SW.transform.localPosition = new Vector3( - CellSurfaceSize / 2f, 1.5f, (CellSurfaceSize * length / 2f) - (0.5f * CellSurfaceSize));

			//Goal
			GameObject Goal = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
			Goal.name = "Goal";
			Goal.transform.parent = this.transform;
			Goal.transform.localScale = new Vector3(1f, 200f, 1f);
			Goal.transform.localPosition = floor[floor.GetLength(0) - 1, floor.GetLength(1) - 1].GO.transform.position + Vector3.up * 100f;
		}

		// Populating the links
		for(int i = 0; i < width; i++){
			for(int j = 0; j < length; j++){
				if(i > 0){
					link daLink = new link();
					daLink.nearCell = floor[i - 1, j];
					daLink.openWall = true;
					daLink.linkPos = linkPosition.east;
					floor[i,j].links.Add(daLink);
				}

				if(i < (width - 1)){
					link daLink = new link();
					daLink.nearCell = floor[i + 1, j];
					daLink.openWall = true;
					daLink.linkPos = linkPosition.west;
					floor[i,j].links.Add(daLink);
				}


				if(j > 0){
					link daLink = new link();
					daLink.nearCell = floor[i, j - 1];
					daLink.openWall = true;
					daLink.linkPos = linkPosition.north;
					floor[i,j].links.Add(daLink);
				}

				if(j < (length - 1)){
					link daLink = new link();
					daLink.nearCell = floor[i, j + 1];
					daLink.openWall = true;
					daLink.linkPos = linkPosition.south;
					floor[i,j].links.Add(daLink);
				}
			}
		}
	}

	public void evaluate(){
		List<cell> toVisit = new List<cell>();
		List<cell> toVisitNext = new List<cell>();
		int currentDistance = 0;
		toVisit.Add(floor[(int)StartCell.x, (int)StartCell.y]);
		while(toVisit.Count > 0 && currentDistance < width*length){
			toVisitNext.Clear();
			foreach(cell C in toVisit){
				C.distanceIndex = currentDistance;
				foreach(link nxt in C.links){
					if(nxt.openWall && nxt.nearCell.distanceIndex == -1) //checks the wall is open & nearCell is not already visited.
						toVisitNext.Add(nxt.nearCell);
				}
			}
			toVisit.Clear();
			toVisit.AddRange(toVisitNext);
			currentDistance++;
		}
		//Checking
		if(toVisit.Count !=0){
			Debug.Log("Incomplete visit - " + toVisit.Count);
			foreach(cell c in toVisit){
				Debug.Log(c.GO.name + " is unchecked");
			}
		}
		for(int i = 0; i < width; i++){
			for(int j = 0; j < length; j++){
				if(floor[i,j].distanceIndex == -1)
					Debug.Log("Warning: " + this.name + " has innaccessible cell@ " + i + ", " + j);
			}
		}
		Debug.Log("Maze shortest path is " + floor[(int)EndCell.x, (int)EndCell.y].distanceIndex + " cells long");
	}

	public void amazeMe(){
		//Recursive Backtracking Algorithm
		//Erase all distances
		foreach(cell C in floor){
			C.distanceIndex = -1;
		}
		seedIndex = 0;
		cell current = floor[(int)StartCell.x, (int)StartCell.y];
		recursion(current, 0);

		foreach(cell C in floor){
			foreach(link L in C.links){
				L.openWall = (Mathf.Abs(C.distanceIndex - L.nearCell.distanceIndex) == 1);
				if(!L.openWall){
					//build wall
					GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
					wall.name = L.linkPos.ToString() + "WallOn_" + C.GO.name; 
					wall.transform.localScale = new Vector3(CellSurfaceSize + 1.5f ,3f , 1f);
					wall.transform.parent = C.GO.transform;

					switch(L.linkPos){
					case linkPosition.east :
						wall.transform.localPosition = Vector3.left * (CellSurfaceSize + 0.75f)+ Vector3.up * 3f;
						wall.transform.Rotate(new Vector3(0f,90f,0f));
						break;
					case linkPosition.west :
						wall.transform.localPosition = Vector3.right* (CellSurfaceSize + 0.75f) + Vector3.up * 3f;
						wall.transform.Rotate(new Vector3(0f,90f,0f));
						break;
					case linkPosition.north :
						wall.transform.localPosition = Vector3.back * (CellSurfaceSize + 0.75f) + Vector3.up * 3f;

						break;
					case linkPosition.south :
						wall.transform.localPosition =  Vector3.forward * (CellSurfaceSize + 0.75f) + Vector3.up * 3f;
						break;
					}
				}
			}
		}
		materializeMe();
	}

	private int seedRead(){
		int i = seedIndex;
		seedIndex++;
		if(seedIndex == seed.ToString().Length)
			seedIndex = 0;
		return int.Parse(seed.ToString().Substring(i, 1));
	}
		
	private void recursion(cell C, int currentDist){
		bool deadend = false;
		C.distanceIndex = currentDist;
		while(!deadend){
			bool canVisitNeighbour = false;
			for(int i = 0; i < C.links.Count && !canVisitNeighbour; i++)
				canVisitNeighbour = C.links[i].openWall && C.links[i].nearCell.distanceIndex == -1;

			if(canVisitNeighbour){
				List<cell> visitable = new List<cell>();
				for(int i = 0; i < C.links.Count; i++){
					if(C.links[i].openWall && C.links[i].nearCell.distanceIndex == -1)
						visitable.Add(C.links[i].nearCell);
					}
					//May require the double check for bidirectionality
				int alea = seedRead() % 4;
				recursion(visitable[alea % visitable.Count], currentDist + 1);
			}else
				deadend = true;
		}
	}

	public void materializeMe(){
		Material Blank = Resources.Load<Material>("Blank");
		//List<MeshRenderer> MRl = new List<MeshRenderer>(GetComponents<MeshRenderer>());
		List<Transform> children =  new List<Transform>(this.transform.GetComponentsInChildren<Transform>());
		children.Remove(this.transform);
		foreach(Transform child in children){
			Material[] m = child.gameObject.GetComponent<Renderer>().materials;
			if(m != null){
				m[0] = Blank;
				child.gameObject.GetComponent<MeshRenderer>().materials = m;
			}
		}
	}

	// Use this for initialization
	void Start () {
		CellSurfaceSize = 5f;
		seed = Random.Range(12021, int.MaxValue);
		width = Random.Range(8,75);
		length = Random.Range(8,75);
		makeFloor();
		amazeMe();

	}
	
	// Update is called once per frame
	void Update () {
		if(Input.GetKeyDown(KeyCode.T)){
			List<Transform> toDestroy =  new List<Transform>(this.transform.GetComponentsInChildren<Transform>());
			toDestroy.Remove(this.transform);
			foreach(Transform GO in toDestroy){
				Destroy(GO.gameObject);
			}
			reset();

			seed = Random.Range(12021, int.MaxValue);
			width = Random.Range(8,75);
			length = Random.Range(8,75);
			makeFloor();
			amazeMe();

		}
	}
}

Make a maze of it