# Procedural Cave Generation

Discussion in 'Community Learning & Teaching' started by SebastianLague, Feb 9, 2015.

1. ### GreenBoxInteractive

Joined:
Mar 23, 2015
Posts:
135
Hi Sebastian,

I must compliment you on all your tutorials, they've helped me so much! Keep teaching, you're an invaluable asset to people looking to get better at making games.

SebastianLague likes this.
2. ### SebastianLague

Joined:
Aug 31, 2014
Posts:
111
Hey Algernon, I'll upload the project files for you when I get back home later tonight.

3. ### SebastianLague

Joined:
Aug 31, 2014
Posts:
111
@stationx Here's the code for simplifying outlines. Add this method to the mesh generator class:
Code (CSharp):
1. void SimplifyMeshOutlines() {
2.     for (int outlineIndex = 0; outlineIndex < outlines.Count; outlineIndex ++) {
3.         List<int> simplifiedOutline = new List<int>();
4.         Vector3 dirOld = Vector3.zero;
5.         for (int i = 0; i < outlines[outlineIndex].Count-1; i ++) {
6.             Vector3 p1 = vertices[outlines[outlineIndex][i]];
7.             Vector3 p2 = vertices[outlines[outlineIndex][i+1]];
8.             Vector3 dir = p1-p2;
9.             if (dir != dirOld) {
10.                 dirOld = dir;
12.             }
13.         }
14.         outlines[outlineIndex] = simplifiedOutline;
15.     }
16. }
And add SimplifyMeshOutlines (); to the end of the CalculateMeshOutlines method.

@Algernon71 I've attached the finished project as a unitypackage. It includes the new outline simplification method.

#### Attached Files:

• ###### Proc Cave Gen.unitypackage
File size:
35.4 KB
Views:
1,094
Last edited: Jul 27, 2015
stationx, Deleted User and chelnok like this.
4. ### Unexpected_Persona

Joined:
Jul 23, 2015
Posts:
9
Hello.
I was trying to work on a voxel-based 2D game with multiple types of voxel. I can make an array which tells what block type is where, but I don't know how to load different textures for each block type or how to keep it block-y and use the mesh. Would I just use a single node for each block and turn set nodes to air once the block at that location was destroyed? I can work out the pickup of dropped items from each block and write the script for blocks being damaged/destroyed, but I don't know how to incorporate that into a mesh and using a non-mesh system, even with chunks, doesn't run well when I try to load hundreds or thousands of game objects.
Any advice on how to proceed?

5. ### SebastianLague

Joined:
Aug 31, 2014
Posts:
111
Hi Unexpected_Persona. Voxels aren't really relevant to this series, I'd recommend you take a look at some of the voxel tutorials available (like this one for example), or post your question on unity answers.

6. ### Unexpected_Persona

Joined:
Jul 23, 2015
Posts:
9
Alright. Thank you for the advice.

### Guest

Thanks for the download, however this does not include the work in the last steps of the tutorial with a collissions, textures, player etc?

At least it looked vary plain to me, was hoping for a package with the "finished" tutorial to play with... I know I am lazy... ;-)

8. ### SebastianLague

Joined:
Aug 31, 2014
Posts:
111
You should be able to drag players into the scene from the prefabs folder. As for the texture, it's not mine to distribute but you can easily find a seamless rock texture on google and just apply it to the cave material

### Guest

Good point, works fine, thanks for the pointers! ;-)

However I tried to apply a texture to the walls, but it doesn't seem to work, in fact all lighting seems a bit broken when it comes to the walls?

They don't have any shade etc at all... any idea how to fix this?
I added a moving light to the player and moved the camera to follow the player to test and the walls have no shade and the 3D effect disappears.
Also tried to use both plain diffuse or bumped etc but no difference, does it have to do with the way the mesh is created?

10. ### SebastianLague

Joined:
Aug 31, 2014
Posts:
111
To give the walls shading you'll just need to make a call to wallmesh.RecalculateNormals(); after the vertices and triangles are assigned in the CreateWallMesh method. As for textures, I'll need to experiment with generating UVs for the walls.

When I have time again I think I must create a tenth episode showing all the things discussed so far: 2D collider simplification, mesh colliders not getting deleted, and wall shading/textures.

11. ### BKG

Joined:
Mar 20, 2014
Posts:
2
I am at the end of lesson 6 and for some reason all my room connections are off set from my caves. They are in the correct position in relation to each other but not the map.

Any idea what could be causing this? I followed along with the video and after this problem arose I copy and pasted the source code and I still have this problem.

File size:
12.8 KB
Views:
3,100
12. ### SebastianLague

Joined:
Aug 31, 2014
Posts:
111
Is the object to which the scripts are attached centred at (0,0,0) in the game world? The rays assume that this is the case. After episode eight (when the passageways are actually created) you will be able to offset the map however you want in the scene.

13. ### BKG

Joined:
Mar 20, 2014
Posts:
2
I must have had some incorrect values in my MeshGenerator script. I copied the MeshGenerator source code and now it is working. Thanks and keep up the great videos!

14. ### BowlerBitesLane

Joined:
Mar 23, 2014
Posts:
4
This is extremely well done, a very helpful. Thank you so much for sharing your knowledge. Truly inspiring!

15. ### stationx

Joined:
Jul 9, 2012
Posts:
251
Thnx Sebastian!!!

16. ### 3dproject

Joined:
Jul 26, 2015
Posts:
18
is there a tutorial on procedural voxel terrain generation? pls post links. and thanks in advance

17. ### Tonny22218

Joined:
Aug 15, 2015
Posts:
1
Hey @SebastianLague !

I really enjoyed the series! Being new to Unity and all it really helped me out!
The region detection might even help me out on my Datastructures final exam

Altough I understand most of your code, i suck at math...
Therefore the line function with the gradients and stuff, i copied it, and it worked.

For my own project however i need a slightly different outcome for the lines. I've been trying all day to come up with an on paper equation like you wrote but yeah ... no succes...

Let me show you what i mean:

What i want mine to do is this:

See the passages I intend to create have to stick to the grid, and still be reachable...
Maybe you could do the math for me?

Thanks!
Greetz

18. ### cams01

Joined:
Aug 17, 2015
Posts:
5
Currently looking at Part 1 of the tutorial. Maybe this is fixed a few minutes later or a few videos later but at the part "seed = Time.time.ToString();". It says Time.time gives "time in seconds since the start of the game" which seems to always give 0. So it's not random at all (on game load) and gives the exact same grid in the video every time.
The intended line seems like it should be "seed = System.DateTime.Now.ToString();".

Last edited: Aug 17, 2015
19. ### Smingleigh

Joined:
Nov 24, 2012
Posts:
9
Great tutorial. The preparation you put in is obvious, and it pays off in a really easy to understand set of videos on a subject that could easily become overwhelming.

20. ### cams01

Joined:
Aug 17, 2015
Posts:
5
Finished all the videos but there's a problem at the end. It's fine with 2D but in the 3D scene, when I change the map by clicking there are invisible walls. I think the walls of the previous maps are still remaining. Did I miss something in the video (rewatching it now) or is this something he forgot to take care of? Either way how do I fix it?

edit: Fixed it myself by adding
MeshCollider[] currentColliders = walls.gameObject.GetComponents<MeshCollider> ();
for (int i = 0; i < currentColliders.Length; i++) {
Destroy(currentColliders);
}
right before it creates the mesh collider.

Last edited: Aug 30, 2015
21. ### Chuckalicious

Joined:
Jul 28, 2012
Posts:
51
This great Sebastian! I was actually working on ways to optimize this. I noticed though, the script does create some occasional holes in the walls. Just FYI Thanks for everything you've done. I watch all your vids man. Cheers.

22. ### Chuckalicious

Joined:
Jul 28, 2012
Posts:
51
Make sure you check for a mesh collider before adding one...

var wallCollider = walls.gameObject.GetComponent<MeshCollider>();
if(wallCollider == null)

23. ### zhephyr

Joined:
Sep 6, 2015
Posts:
1
How would I go about detecting the surrounding wall to then randomly attach game objects like torches, etc. I was thinking of using something similar to the GetRegions method to get a List<Coord> that would be the surrounding walls to the now one giant room.

Last edited: Sep 15, 2015
24. ### captainfrogduh

Joined:
Jul 29, 2015
Posts:
6
Hi guys, I would like to make an infinite cave with this tutorial as the starting point. I was wondering how to go about that since im new to programming. If someone could just provide with the starting pointers that would be really great

25. ### mastrsushi

Joined:
Oct 6, 2015
Posts:
1
I have found some really Strange fact : when you configure your SmoothMap method and saying that you must have at least 5 neighbourWalls to change your tile into a wall and 3 or less to change it into an empty tile your map is transformed into a maze !!

Oscaruzzo likes this.
26. ### McNoguff

Joined:
Sep 20, 2015
Posts:
9
This is heroic stuff. I may be in the minority here but I hope you add advertisements or some such to your videos so you can monetize them. I can't afford to support your patreon but I'd happily sit through advertisements if it kept you making excellent content.

Alternately, Unity Technologies could hire you. Just sayin'.

Your videos have been just as much and sometimes *more* help than any of the official Unity ones, and I/my small team appreciate it. Thanks!

27. ### McNoguff

Joined:
Sep 20, 2015
Posts:
9
Hey there @SebastianLague , was playing with this tutorial and found a couple small problems that may or may not be related to an update in unity. First of all, the player object sinks slowly downward in the 2D scene(affected by gravity). I thought maybe I'd written the script wrong but after opening the scene2D in your unity project from github it shows the same behavior. I'm using version 5.2.0f3 Personal if that matters.

Secondly, mesh generation occasionally results in a blank section of wall with no collider(I'm not sure if this is an inverted normals issue). I've attached pictures of this issue as seen in your unity project from gitHub, all public variables at their defaults(seed 0):

Would really love to understand why that happens, I've been playing with marching squares and cellular automata tutorials from all over the place(catlike coding especially) and can't figure out why this happens!

Thank you again for your hard work! I've coded in Java and ActionScript a ton in my professional life and these tutorials have helped me grok C# in a really intuitive way.

Joined:
Oct 22, 2015
Posts:
1
Hi McNoguff,

I can't answer the second part of your post, but the first part is due to a development WRT mesh collision in Unity 5.2, I think. Try checking the Convex tab in the Mesh Collider component for the Ground game object (in both the 2D and 3D scenes).

It happened to me too and it was only by remembering a footnote in a different hosted tutorial that I thought to check that tab. That should clear up the issue, however.

Joined:
Jun 27, 2007
Posts:
5,664
Just for the record, Sebastian chose to use system.random. Unity does have a pseudo random number generator built in.
In Ep. 1, Sebastian is using System.Random:
Code (CSharp):
1. System.Random pseudoRandom = new System.Random(seed.GetHashCode());
... and:
Code (CSharp):
1. map[x,y] = (pseudoRandom.Next(0,100) < randomFillPercent)? 1: 0;
It is also possible to use:
Code (CSharp):
1. map[x,y] = (Random.Range(0,100) < randomFillPercent)? 1: 0;
... and if we need to set the seed, we can do that with:
http://docs.unity3d.com/ScriptReference/Random-seed.html

They both work!

Good stuff, either way.

Joined:
Jun 27, 2007
Posts:
5,664
Sorry for the late reply, but this is advanced coding, so I'd suggest a different approach. If you're still interested and need help, post a new thread in scripting and ping me with a tag and we'll help you out.

31. ### Oscaruzzo

Joined:
Jan 26, 2015
Posts:
17
Hi, I'm following this interesting tutorial (I'm especially interested in how you generate a mesh, but I'm watching the whole thing, since it's so well made). I have just one doubt: in episode 4 - 3D Walls you do a lot of work to find out which edges are outer edges. But it seems to me that you already know that in episode 2 - Marching squares. For example in configuration 10, only edges BC and EF are outer edges, while the other are not. You could probably mark those edges somehow, and then just run through them like you do with triangleDictionary to build outlines. Or am I missing something?

Great tutorial anyway, and thank you for speaking clearly and slowly; I'm not a native English speaker, and that helped a lot

32. ### SebastianLague

Joined:
Aug 31, 2014
Posts:
111
Hi there, my apologies for being so horrifyingly slow to get back to you! I'm not sure about the 2D controller problem, if you're still grappling with that let me know and I'll investigate it further, but it seems to be working fine on my end

I've updated the project files to fix the gap problem you described. The problem was arising in the SimplifyMeshOutlines method (which I added to the project file after the conclusion of the tutorial series). The problem is that it was not wrapping around to include the first vertex again. Here is my updated code for that method:

Code (CSharp):
1. void SimplifyMeshOutlines() {
2.     for (int outlineIndex = 0; outlineIndex < outlines.Count; outlineIndex ++) {
3.         List<int> simplifiedOutline = new List<int>();
4.         Vector3 dirOld = Vector3.zero;
5.         for (int i = 0; i < outlines[outlineIndex].Count; i ++) {
6.             Vector3 p1 = vertices[outlines[outlineIndex][i]];
7.             Vector3 p2 = vertices[outlines[outlineIndex][(i+1)%outlines[outlineIndex].Count]];
8.             Vector3 dir = p1-p2;
9.             if (dir != dirOld) {
10.                 dirOld = dir;
12.             }
13.         }
14.         outlines[outlineIndex] = simplifiedOutline;
15.     }
16. }
The important change is in line 7, where it not wraps back to vertex 0 with (i+1)%outlines[outlineIndex].Count

33. ### SebastianLague

Joined:
Aug 31, 2014
Posts:
111
Hi Oscaruzzo, glad you're enjoying the series! It was a while back that I wrote this code, but I do remember trying out your idea first and running into some issues with the ordering of the vertices (resulting in fragmented walls, or some walls being inside out - I don't recall exactly what the problem was). I do feel like I might have missed a trick there though and ended up doing significantly more work than necessary ^^

34. ### DefinitelyDry

Joined:
Nov 8, 2015
Posts:
4
Hey Sebastian,
First off, thanks a lot for creating this tutorial series. It's well prepared and well explained all the time, and even understandable for people without in-depth knowledge of the Unity environment.
I have a question though - is there an easy way of checking whether a Game Object (i.e. the player) is in the cave wall, or "colliding" with the Cave Mesh in 2D? I've tried all sorts of approaches, but I'm just not getting there.

35. ### Carcer_Dun

Joined:
Jan 22, 2015
Posts:
1
Hey Sebastian,
Just started your tutorial and it seemed to be going well when I tried building it as you did in episode 1 at 9:56. However nothing appeared on the screen in either the scene or game views. I followed you code exactly and have been poring over it and I cannot see anything wrong. I have the latest version of unity but I have no idea what is going wrong.
Any help would be greatly appreciated, thank you.

Code (CSharp):
1. using UnityEngine;
2. using System.Collections;
3. using System;
4.
5. public class MapGenerator : MonoBehaviour
6. {
7.     public int width;
8.     public int height;
9.
10.     public string seed;
11.     public bool useRandomSeed;
12.
13.     [Range(0, 100)]
14.     public int randomFillPercent;
15.
16.     int[,] map;
17.
18.     void Start()
19.     {
20.         GenerateMap();
21.     }
22.
23.     void GenerateMap()
24.     {
25.         map = new int[width, height];
26.         RandomFillMap();
27.     }
28.
29.     void RandomFillMap()
30.     {
31.         if (useRandomSeed)
32.         {
33.             seed = Time.time.ToString();
34.         }
35.
36.         System.Random pseudoRandom = new System.Random(seed.GetHashCode());
37.
38.         for (int x = 0; x < width; x++)
39.         {
40.             for (int y = 0; y < height; y++)
41.             {
42.                 map[x, y] = (pseudoRandom.Next(0, 100) < randomFillPercent)? 1 : 0;
43.             }
44.         }
45.     }
46.
47.     void onDrawGizmos()
48.     {
49.         if (map != null)
50.         {
51.             for (int x = 0; x < width; x++)
52.             {
53.                 for (int y = 0; y < height; y++)
54.                 {
55.                     Gizmos.color = (map[x, y] == 1)? Color.black : Color.white;
56.                     Vector3 pos = new Vector3(-width / 2 + x + .5f, 0, -height / 2 + y + .5f);
57.                     Gizmos.DrawCube(pos, Vector3.one);
58.                 }
59.             }
60.         }
61.     }
62. }
63.

Joined:
Jun 27, 2007
Posts:
5,664
37. ### dpthayer

Joined:
Dec 23, 2015
Posts:
2
Hello Sebastian,
Great Tutorial I sat through the entire set of videos and now I am working through from tutorial one to the end. I have a problem however. I am in Tutorial one, I create the C# Code and compile it in Visual Studio and press the Attach to Unity button. Then in unity when I press the play button I can hit all my breakpoints and see the map being generated and I can even see the code being run through the scans the map and creates the gizmos to visualize the map. However in the Unity IDE I see nothing appearing in either the Scene window or the Game window. I have been scratching my head over this now for almost a day. I am very new to Unity and am wondering if there is not some part of setting up my project that I have not taken care of that your tutorial assumes that we know to do.

BTW. Wouldn't it be better to create an adjacency matrix for all the rooms giving their closest points and then you could make more sophisticates room sequences. Also you would only calculate inter room distance once for each pair of rooms.

David Thayer

38. ### dpthayer

Joined:
Dec 23, 2015
Posts:
2
Sebastion,

My Neighbor came over and was looking at my code and noticed that my OnDrawGizmos function was spelled OnDrawGizmoz. I am now creating maze like caverns for saome reason but I can figure that out. I hope to continue with the tutorial. I have created two functions that encapsulate scanning the mao and replacing each entry with the result of a function call and another helper function that scans the map and calls an Action on each element value.

Therefore OnDrawGizmos looks like this:
void OnDrawGizmos()
{
if (map != null)
{
ActOnMap(DrawMapCell);
}
}

And RandomFillMap() looks like this:
void RandomFillMap()
{
CreateRng();
IterateMap(SetMapCell);
}

39. ### cefwyn

Joined:
Jan 2, 2013
Posts:
15
This tutorial is great. I've been away from programming for years and I'm trying to get back into it with what little free time I have. At the end of the series you are left with untextured walls and I decided to fix that, but I'm afraid my code is incredibly inelegant:

Code (CSharp):
1. void CreateWallMesh() {
2.
3.         CalculateMeshOutlines ();
4.
5.         List<Vector3> wallVertices = new List<Vector3> ();
6.         List<int> wallTriangles = new List<int> ();
7.         Mesh wallMesh = new Mesh ();
8.
9.         foreach (List<int> outline in outlines) {
10.             for (int i = 0; i < outline.Count -1; i ++) {
11.                 int startIndex = wallVertices.Count;
14.                 wallVertices.Add(vertices[outline[i]] - Vector3.up * wallHeight); // bottom left
15.                 wallVertices.Add(vertices[outline[i+1]] - Vector3.up * wallHeight); // bottom right
16.
20.
24.             }
25.         }
26.         wallMesh.vertices = wallVertices.ToArray ();
27.         wallMesh.triangles = wallTriangles.ToArray ();
28.         walls.mesh = wallMesh;
29.         wallMesh.RecalculateNormals();
30.
31.         MeshCollider wallCollider = walls.gameObject.GetComponent<MeshCollider> ();
32.
33.         if(!wallCollider)
35.         wallCollider.sharedMesh = wallMesh;
36.
37.        //GENERATE UV COORDINATES HERE
38.         Vector2[] uvs = new Vector2[wallVertices.Count];
39.         int march = 0;
40.         for (int i =0; i < wallVertices.Count; i ++) {
41.             switch (march) {
42.             case 0:
43.                 uvs [i] = new Vector2 (0,1);
44.                 march++;
45.                 break;
46.             case 1:
47.                 uvs [i] = new Vector2 (0.5f,1);
48.                 march++;
49.                 break;
50.             case 2:
51.                 uvs [i] = new Vector2 (0,0);
52.                 march++;
53.                 break;
54.             case 3:
55.                 uvs [i] = new Vector2 (0.5f, 0);
56.                 march++;
57.                 break;
58.             case 4:
59.                 uvs [i] = new Vector2 (0.5f,1);
60.                 march++;
61.                 break;
62.             case 5:
63.                 uvs [i] = new Vector2 (1,1);
64.                 march++;
65.                 break;
66.             case 6:
67.                 uvs [i] = new Vector2 (0.5f,0);
68.                 march++;
69.                 break;
70.             case 7:
71.                 uvs [i] = new Vector2 (1, 0);
72.                 march = 0;
73.                 break;
74.             }
75.         }
76.         wallMesh.uv = uvs;
77.     }
For this I basically decided that the texture should tile every 2 quads since the triangles tended to be quite stretched and so I figured out the UV coordinates in my head and iterated through the vertices adding the manually calculated UVs. I tried to figure out a way to do this more intelligently but I guess I'm too rusty. Even with this though I found that if you make the walls higher than about 2.5 it looks stretched because the wall generation probably needs to make additionally triangle strips based on how tall the wall is to reduce stretching but I haven't figured that part out yet.

I've also created a method to generate the floor much in the same way the rest is generated by copying the GenerateMesh function and overloading the SquareGrid constructor to invert it's logic when searching through the map.

Code (CSharp):
1. public void GenerateFloorMesh(int[,] map, float squareSize) {
2.
3.         triangleDictionary.Clear ();
4.         //outlines.Clear ();
5.         checkedVertices.Clear ();
6.
8.         squareGrid = new SquareGrid(map, squareSize, -wallHeight);
9.
10.         vertices.Clear();
11.         triangles.Clear();
12.
13.         for (int x = 0; x < squareGrid.squares.GetLength(0); x ++) {
14.             for (int y = 0; y < squareGrid.squares.GetLength(1); y ++) {
15.                 TriangulateSquare(squareGrid.squares[x,y]);
16.             }
17.         }
18.
19.         Mesh mesh = new Mesh();
20.         floor.mesh = mesh;
21.
22.         mesh.vertices = vertices.ToArray();
23.         mesh.triangles = triangles.ToArray();
24.         mesh.RecalculateNormals();
25.
26.         MeshCollider floorCollider = floor.gameObject.GetComponent<MeshCollider> ();
27.
28.         if(!floorCollider)
30.         floorCollider.sharedMesh = mesh;
31.
32.         int tileAmount = 20;
33.         Vector2[] uvs = new Vector2[vertices.Count];
34.         for (int i =0; i < vertices.Count; i ++) {
35.             float percentX = Mathf.InverseLerp(-map.GetLength(0)/2*squareSize,map.GetLength(0)/2*squareSize,vertices[i].x) * tileAmount;
36.             float percentY = Mathf.InverseLerp(-map.GetLength(0)/2*squareSize,map.GetLength(0)/2*squareSize,vertices[i].z) * tileAmount;
37.             uvs[i] = new Vector2(percentX,percentY);
38.         }
39.         mesh.uv = uvs;
40.     }
Code (CSharp):
1. public SquareGrid(int[,] map, float squareSize, float _wallHeight) {
2.             int nodeCountX = map.GetLength(0);
3.             int nodeCountY = map.GetLength(1);
4.             float mapWidth = nodeCountX * squareSize;
5.             float mapHeight = nodeCountY * squareSize;
6.
7.             ControlNode[,] controlNodes = new ControlNode[nodeCountX,nodeCountY];
8.
9.             for (int x = 0; x < nodeCountX; x ++) {
10.                 for (int y = 0; y < nodeCountY; y ++) {
11.                     Vector3 pos = new Vector3(-mapWidth/2 + x * squareSize + squareSize/2,_wallHeight, -mapHeight/2 + y * squareSize + squareSize/2); //SETS X TO BE _wallHeight (Note:By default wallHeight is negative)
12.                     controlNodes[x,y] = new ControlNode(pos, map[x,y] == 0, squareSize); //LOOKS FOR 0 INSTEAD OF 1
13.                 }
14.             }
15.
16.             squares = new Square[nodeCountX -1,nodeCountY -1];
17.             for (int x = 0; x < nodeCountX-1; x ++) {
18.                 for (int y = 0; y < nodeCountY-1; y ++) {
19.                     squares[x,y] = new Square(controlNodes[x,y+1], controlNodes[x+1,y+1], controlNodes[x+1,y], controlNodes[x,y]);
20.                 }
21.             }
22.
23.         }
I tried to figure out a way to make a navmesh using the floor mesh but doesn't seem to be possible
I've spent the last three days plugging away at the code and trying to relearn everything. Anyone know of a good way to find a safe spawn point? I've got a function that uses trial-and-error to find a point on the map at random but it tends to put the player on top of walls. I could create a rule to check surrounding map coordinates but is there a better way?

EDIT: Discovered a bug I made in my code. Fixed it!

Last edited: Jan 3, 2016

Joined:
Jun 27, 2007
Posts:
5,664
Can you please use code tags when posting code on the forums:

41. ### heartnotes3

Joined:
Feb 16, 2016
Posts:
2
Hey there, I'm walking through episode 1 and Monodevelop doesn't seem to like this line:

Code (CSharp):
1.  map[x,y] = (pseudoRandom.Next(0,100) < randomFillPercent)? 1: 0;
more specifically the }? phrasing it doesn't seem to recognize at all. How can I code this differently in order to get it to run at all?

42. ### SebastianLague

Joined:
Aug 31, 2014
Posts:
111
This is identical to writing:
Code (CSharp):
1. if (pseudoRandom.Next(0,100) < randomFillPercent) {
2.     map[x,y] = 1;
3. }

43. ### lizzyinthesky

Joined:
Mar 17, 2015
Posts:
2
This guy is correct, the line as it is in the tutorial wouldn't create a random seed for me, but changing it to datetime worked. This was after copying the code from unity in frustration to try to fix too, still wouldn't work. Possible its an OS difference or something? On windows here

Last edited: Feb 20, 2016
44. ### Tatsumi-Kun

Joined:
Feb 11, 2015
Posts:
130
partner i having this weird error that i can't fix i have all the your script just right but it still giving me this error.

Code (CSharp):
1. using UnityEngine;
2. using System.Collections;
3. using System.Collections.Generic;
4.
5. public class MeshGenerator : MonoBehaviour {
6.
7.     public SquareGrid squareGrid;
8.     public MeshFilter walls;
9.     List <Vector3> vertices;
10.     List<int> triangles;
11.
12.     Dictionary<int,List<Triangle>> triangleDictonary = new Dictionary<int, List<Triangle>> ();
13.     List<List<int>> outlines = new List<List<int>> ();
14.     HashSet<int> checkVertices = new HashSet<int> ();
15.
16.     public void  GenerateMesh(int [,] map, float squareSize) {
17.         triangleDictonary.Clear ();
18.         outlines.Clear ();
19.         checkVertices.Clear();
20.
21.         squareGrid = new SquareGrid(map, squareSize);
22.
23.         vertices = new List<Vector3> ();
24.         triangles = new List<int> ();
25.
26.         for (int x = 0; x < squareGrid.squares.GetLength (0); x++) {
27.             for (int y = 0; y < squareGrid.squares.GetLength (1); y++) {
28.                 TriangulateSquare (squareGrid.squares [x,y]);
29.             }
30.         }
31.
32.         Mesh mesh = new Mesh ();
33.         GetComponent<MeshFilter> ().mesh = mesh;
34.
35.         mesh.vertices = vertices.ToArray ();
36.         mesh.triangles = triangles.ToArray ();
37.         mesh.RecalculateNormals ();
38.
39.         CreateWallMesh ();
40.     }
41.
42.     void CreateWallMesh () {
43.
44.         CalculateMeshOutlines ();
45.
46.         List<Vector3> wallVertices = new List<Vector3> ();
47.         List<int> wallTriangles = new List<int> ();
48.         Mesh wallMesh = new Mesh ();
49.         float wallHeight = 5;
50.
51.         foreach (List<int> outline in outlines) {
52.             for (int i = 0; i < outline.Count - 1; i++) {
53.                 int startIndex = wallVertices.Count;
54.                 wallVertices.Add (vertices [outline [i]]); // left
55.                 wallVertices.Add (vertices [outline [i+1]]); // right
56.                 wallVertices.Add (vertices [outline [i]] - Vector3.up * wallHeight); // bottomleft
57.                 wallVertices.Add (vertices [outline [i+1]] - Vector3.up * wallHeight); // bottomright
58.
62.
66.
67.             }
68.         }
69.
70.         wallMesh.vertices = wallVertices.ToArray ();
71.         wallMesh.triangles = wallTriangles.ToArray ();
72.         walls.mesh = wallMesh;
73.     }
74.
75.     void TriangulateSquare(Square square) {
76.         switch (square.configuration) {
77.         case 0:
78.             break;
79.
80.             //1 points:
81.         case 1:
82.             MeshFromPoints (square.centreLeft, square.centreBottom, square.bottomLeft);
83.             break;
84.         case 2:
85.             MeshFromPoints (square.bottomRight, square.centreBottom, square.centreTop);
86.             break;
87.         case 4:
88.             MeshFromPoints (square.topRight, square.centreRight, square.centreRight);
89.             break;
90.         case 8:
91.             MeshFromPoints (square.topLeft, square.centreTop, square.centreLeft);
92.             break;
93.
94.             // 2 points:
95.         case 3:
96.             MeshFromPoints (square.centreRight, square.bottomRight, square.bottomLeft, square.centreLeft);
97.             break;
98.         case 6:
99.             MeshFromPoints (square.centreTop, square.topRight, square.bottomRight, square.centreBottom);
100.             break;
101.         case 9:
102.             MeshFromPoints (square.topLeft, square.centreTop, square.bottomRight, square.bottomLeft);
103.             break;
104.         case 12:
105.             MeshFromPoints (square.topLeft, square.topRight, square.centreRight, square.centreLeft);
106.             break;
107.         case 5:
108.             MeshFromPoints (square.centreTop, square.topRight, square.centreRight, square.centreBottom, square.bottomLeft, square.centreLeft);
109.             break;
110.         case 10:
111.             MeshFromPoints(square.topLeft, square.centreTop, square.centreRight, square.bottomRight, square.centreBottom, square.centreLeft);
112.             break;
113.
114.             // 3 point:
115.         case 7:
116.             MeshFromPoints(square.centreTop, square.topRight, square.bottomRight, square.bottomLeft, square.centreLeft);
117.             break;
118.         case 11:
119.             MeshFromPoints(square.topLeft, square.centreTop, square.centreRight, square.bottomRight, square.bottomLeft);
120.             break;
121.         case 13:
122.             MeshFromPoints(square.topLeft, square.topRight, square.centreRight, square.centreBottom, square.bottomLeft);
123.             break;
124.         case 14:
125.             MeshFromPoints(square.topLeft, square.topRight, square.bottomRight, square.centreBottom, square.centreLeft);
126.             break;
127.
128.             // 4 point:
129.         case 15:
130.             MeshFromPoints (square.topLeft, square.topRight, square.bottomRight, square.bottomLeft);
135.             break;
136.
137.         }
138.     }
139.
140.     void MeshFromPoints (params Node [] points) {
141.         AssignVertices (points);
142.
143.         if (points.Length >= 3)
144.             CreateTriangle (points [0], points [1], points [2]);
145.         if (points.Length >= 4)
146.             CreateTriangle (points [0], points [2], points [3]);
147.         if (points.Length >= 5)
148.             CreateTriangle (points [0], points [3], points [4]);
149.         if (points.Length >= 6)
150.             CreateTriangle (points [0], points [4], points [5]);
151.     }
152.
153.     void AssignVertices(Node[] points) {
154.         for (int i = 0; i < points.Length; i++)
155.         {
156.             if (points [i].vertexIndex == -1) {
157.                 points [i].vertexIndex = vertices.Count;
159.             }
160.         }
161.     }
162.
163.     void CreateTriangle (Node a, Node b, Node c)
164.     {
168.
169.         Triangle triangle = new Triangle (a.vertexIndex, b.vertexIndex, c.vertexIndex);
173.
174.     }
175.
176.     void AddTriangleDictonary(int vertexIndexKey, Triangle triangle) {
177.         if (triangleDictonary.ContainsKey (vertexIndexKey)) {
179.         }
180.         else {
181.             List<Triangle> triangleList = new List<Triangle> ();
184.         }
185.     }
186.
187.     void CalculateMeshOutlines () {
188.
189.         for (int vertexIndex = 0; vertexIndex < vertices.Count; vertexIndex++) {
190.             if (!checkVertices.Contains(vertexIndex)) {
191.                 int newOutlineVertex = GetConnectedOutlineVertex (vertexIndex);
192.                 if (newOutlineVertex != -1) {
194.
195.                     List<int> newOutline = new List<int> ();
198.                     FollowOutline (newOutlineVertex, outlines.Count - 1);
199.                     outlines [outlines.Count - 1].Add (vertexIndex);
200.
201.                 }
202.             }
203.         }
204.     }
205.
206.     void FollowOutline(int vertexIndex, int outlineIndex) {
209.         int nextVertexIndex = GetConnectedOutlineVertex (vertexIndex);
210.
211.         if (nextVertexIndex != -1) {
212.             FollowOutline (nextVertexIndex, outlineIndex);
213.         }
214.     }
215.
216.     int GetConnectedOutlineVertex(int vertexIndex) {
217.
218.         List<Triangle> triangleContainingVertex = triangleDictonary [vertexIndex];
219.
220.         for (int i = 0; i < triangleContainingVertex.Count; i++) {
221.             Triangle triangle = triangleContainingVertex [i];
222.
223.             for (int j = 0; j <3; j++) {
224.                 int vertexB = triangle [j];
225.                 if (vertexB != vertexIndex && !checkVertices.Contains(vertexB)) {
226.                     if (IsOutLineEdge (vertexIndex, vertexB)) {
227.                         return vertexB;
228.
229.                     }
230.
231.                 }
232.             }
233.         }
234.
235.         return -1;
236.     }
237.
238.     bool IsOutLineEdge(int vertexA, int VertexB) {
239.         List<Triangle> triangleContainingVertexA = triangleDictonary [vertexA];
240.         int sharedTriangleCount = 0;
241.
242.         for (int i =0; i < triangleContainingVertexA.Count; i ++) {
243.             if (triangleContainingVertexA[i].Contains (VertexB)) {
244.                 sharedTriangleCount++;
245.                 if (sharedTriangleCount > 1) {
246.                     break;
247.                 }
248.             }
249.         }
250.
251.         return sharedTriangleCount == 1;
252.     }
253.
254.     struct Triangle {
255.         public int vertexIndexA;
256.         public int vertexIndexB;
257.         public int vertexIndexC;
258.         int[] vertices;
259.
260.         public Triangle (int a, int b, int c) {
261.             vertexIndexA = a;
262.             vertexIndexB = b;
263.             vertexIndexC = c;
264.
265.             vertices = new int[3];
266.             vertices[0] = a;
267.             vertices[1] = b;
268.             vertices[2] = c;
269.
270.
271.         }
272.
273.         public int this[int i] {
274.             get {
275.                 return vertices [i];
276.             }
277.         }
278.
279.         public bool Contains(int vertexIndex) {
280.             return vertexIndex == vertexIndexA || vertexIndex == vertexIndexB || vertexIndex == vertexIndexC;
281.         }
282.
283.     }
284.
285.         public class SquareGrid {
286.         public Square[,] squares;
287.
288.         public SquareGrid(int [,] map, float squareSize) {
289.              int nodeCountX = map.GetLength(0);
290.             int nodeCountY = map.GetLength(1);
291.             float mapWidth = nodeCountX * squareSize;
292.             float mapHeight = nodeCountY * squareSize;
293.
294.             ControlNode[,] controlNodes = new ControlNode[nodeCountX,nodeCountY];
295.
296.             for (int x = 0; x < nodeCountX; x ++) {
297.                     for (int y = 0; y < nodeCountY; y ++) {
298.                     Vector3 pos = new Vector3(-mapWidth/2 + x * squareSize + squareSize/2, 0, -mapHeight/2 + y * squareSize + squareSize/2);
299.                     controlNodes[x,y] = new ControlNode(pos,map[x,y] == 1, squareSize);
300.                 }
301.             }
302.
303.             squares = new Square[nodeCountX -1,nodeCountY -1];
304.             for (int x = 0; x < nodeCountX-1; x ++) {
305.                 for (int y = 0; y < nodeCountY -1; y ++) {
306.                     squares[x,y] = new Square(controlNodes[x,y+1], controlNodes[x+1,y+1], controlNodes[x+1,y], controlNodes[x,y]);                }
307.             }
308.
309.         }
310.     }
311.
312.         public class Square {
313.         public ControlNode topLeft, topRight, bottomRight, bottomLeft;
314.         public Node centreTop, centreRight, centreBottom, centreLeft;
315.         public int configuration;
316.
317.         public Square (ControlNode _topLeft, ControlNode _topRight,ControlNode _bottomRight, ControlNode _bottomLeft)
318.         {
319.             topLeft = _topLeft;
320.             topRight = _topRight;
321.             bottomRight = _bottomRight;
322.             bottomLeft = _bottomLeft;
323.
324.             centreTop = topLeft.right;
325.             centreRight = bottomRight.above;
326.             centreBottom = bottomLeft.right;
327.             centreLeft = bottomLeft.above;
328.
329.             if (topLeft.active)
330.                 configuration += 8;
331.             if (topRight.active)
332.                 configuration += 4;
333.             if(bottomRight.active)
334.                 configuration  += 2;
335.             if (bottomLeft.active)
336.                 configuration += 1;
337.         }
338.
339.     }
340.
341.         public class Node {
342.         public Vector3 position;
343.         public int vertexIndex = -1;
344.
345.         public Node(Vector3 _pos) {
346.             position = _pos;
347.         }
348.     }
349.
350.     public class ControlNode : Node {
351.         public bool active;
352.         public Node above, right;
353.
354.         public ControlNode(Vector3 _pos, bool _active, float spuareSize) : base(_pos) {
355.             active = _active;
356.             above = new Node(position + Vector3.forward * spuareSize/2f);
357.             right = new Node(position + Vector3.right * spuareSize/2f);
358.         }
359.     }
360. }
361.
Code (CSharp):
1. using UnityEngine;
2. using System.Collections;
3.
4. public class MapGenerator : MonoBehaviour {
5.
6.     public int width;
7.     public int height;
8.
9.     public string seed;
10.     public bool useRandomSeed;
11.
12.     [Range(0,100)]
13.     public int randomFillPercent;
14.
15.     int [,] map;
16.
17.     void Start ()
18.     {
19.         GenerateMap ();
20.
21.     }
22.
23.     void Update () {
24.         if (Input.GetMouseButtonDown (0)) {
25.             GenerateMap ();
26.         }
27.     }
28.
29.     void GenerateMap () {
30.
31.         map = new int[width, height];
32.         RandomFillUp ();
33.
34.         for (int i = 0; i < 5; i++) {
35.             SmoothMap ();
36.         }
37.
38.         int borderSize = 1;
39.         int[,] borderedMap = new int[width + borderSize * 2, height + borderSize * 2];
40.
41.         for (int x = 0; x < borderedMap.GetLength(0); x ++) {
42.             for (int y = 0; y < borderedMap.GetLength(1); y ++) {
43.                 if (x >= borderSize && x < width + borderSize && y >= borderSize && y < height + borderSize) {
44.                     borderedMap [x, y] = map [x - borderSize, y - borderSize];
45.                 }
46.                 else {
47.                     borderedMap [x, y] = 1;
48.                 }
49.             }
50.         }
51.
52.         MeshGenerator meshGen = GetComponent<MeshGenerator> ();
53.         meshGen.GenerateMesh(borderedMap, 1);
54.
55.     }
56.
57.     void RandomFillUp () {
58.         if (useRandomSeed) {
59.             seed = Time.time.ToString ();
60.         }
61.
62.         System.Random psuedoRandom = new System.Random (seed.GetHashCode ());
63.
64.         for (int x = 0; x < width; x++) {
65.             for (int y = 0; y < height; y ++) {
66.                 if (x == 0 || x == width - 1 || y == height - 1) {
67.                     map [x, y] = 1;
68.                 } else {
69.                     map [x, y] = (psuedoRandom.Next (0, 100) < randomFillPercent) ? 1 : 0;
70.                 }
71.             }
72.
73.         }
74.
75.     }
76.
77.     void SmoothMap ()
78.     {
79.         for (int x = 0; x < width; x++) {
80.             for (int y = 0; y < height; y++) {
81.                 int neighbourWallTitles = GetSurroundingWallCount (x, y);
82.
83.                 if(neighbourWallTitles >4)
84.                     map[x,y] = 1;
85.                         else if (neighbourWallTitles < 4)
86.                         map[x,y] = 0;
87.
88.             }
89.         }
90.     }
91.
92.     int GetSurroundingWallCount (int gridX, int gridY)
93.     {
94.         int wallCount = 0;
95.         for (int neighbourX = gridX - 1; neighbourX <= gridX + 1; neighbourX ++)    {
96.             for (int neighbourY = gridY - 1; neighbourY <= gridY + 1; neighbourY ++) {
97.                 if (neighbourX >= 0 && neighbourX < width && neighbourY > 0 && neighbourY < height) {
98.                     if (neighbourX != gridX || neighbourY != gridY) {
99.                         wallCount += map [neighbourX, neighbourY];
100.                     }
101.                 }
102.                 else {
103.                     wallCount++;
104.                 }
105.             }
106.         }
107.
108.         return wallCount;
109.     }
110.
111.     void OnDrawGizmos()
112.     {
113.         /*
114.         if (map != null)
115.
116.             for (int x = 0; x < width; x++)
117.         {
118.                 for (int y = 0; y < height; y ++)
119.                 {
120.                     Gizmos.color = (map [x, y] == 1) ? Color.black : Color.white;
121.                     Vector3 pos = new Vector3 (-width / 2 + x + .5f, 0, -height / 2 + y + .5f);
122.                     Gizmos.DrawCube (pos, Vector3.one);
123.                 }
124.         }
125.         */
126.     }
127.
128. }
129.

45. ### nicloay

Joined:
Jul 11, 2012
Posts:
542
I faced wrong wall generation bug as well. here is a screenshots (with vertex numbers)
or
If Someone Fill face similar as well (You see 0->3 should not be connected. and through debug i found that mesh generator tried to generate 3 walls. but we have just 2 here).

To solve that. at the beginning of the method void CalculateMeshOutlines() add following line of code checkedVertices.Clear()

So it will looks like
Code (CSharp):
1. void CalculateMeshOutlines() {
2.         checkedVertices.Clear();
3.         for (int vertexIndex = 0; vertexIndex < vertices.Count; vertexIndex ++) {
4.             if (!checkedVertices.Contains(vertexIndex)) {
5.                 int newOutlineVertex = GetConnectedOutlineVertex(vertexIndex);
6. ...
7.         }
8. }
p.s. Thanks for the package anyway =))

46. ### Tatsumi-Kun

Joined:
Feb 11, 2015
Posts:
130
so this what you post can fix my error that i having

47. ### Tatsumi-Kun

Joined:
Feb 11, 2015
Posts:
130
i am having this error i just don't know what to do when i change the cave to something different i have this one error.

48. ### plmjyu

Joined:
Mar 9, 2016
Posts:
1
This tutorial is great.. Can you tell me the good way to spawn something gameObject in the map? Like in the bottom of each room?

49. ### _Eyesgood_

Joined:
Apr 19, 2013
Posts:
55
Loved the tutorial, Sebastian! I am one of the viewers that actually coded everything along with you through the series.

Anyway, I have tried to figure out how to apply a material to the 3d cave walls to no avail. I have the scene ambient light set to black so it is dark like a cave. I have a character with camera and lightsource attached. I also created a plane for the floor and ceiling and have materials on those. When I run it, the scene looks really great except for the wall mesh. The mesh just reflects my light source and even when I apply a material to the mesh renderer, it never shows the material.

Has anyone been able to render material on the wall mesh? If so, please post how you did it. I eagerly await your suggested next episode!

Cheers!

50. ### cefwyn

Joined:
Jan 2, 2013
Posts:
15
For a simple but fairly naive method of applying materials to the walls follow my post #89. The issue with the original tutorial is that no UV coordinates are being calculated for the walls. My code calculates UV coordinates, but uses a simplistic method to keep the textures from stretching which relies on the height of the wall being what I have hard-coded in. If you make the walls too high the texture will stretch, and two short the texture will compress.