Procedurally generate a night skybox

I started to experiment on a fully procedurally generated game. Along with the models and textures, I decided to procedurally create the skybox textures to have more flexibility and a minimize the game build size. Here is a simple example of the generated skybox :

Procedurally generated skybox with stars

Procedurally generated skybox with stars

The code is pretty simply and self-explanatory. The header initialize a couple of variables. Then, 6 textures are created by looping trough each pixel individually and setting their color with the Texture2D.SetPixel() function. The code set some single pixel as grey-white and some 2-by-2 pixels as a yellowish color with their texture varying too. A random number is evaluated on each pixel to decide if its a black,yellow or grey pixel . Once all texture are created, they are assigned to the skybox material and the material is defined as the skybox to use in the RenderSettings.

Texture2D texture = new Texture2D(1024, 1024, TextureFormat.ARGB32, false); // Create a new 1024x1024 texture ARGB32 (32 bit with alpha) and no mipmaps
Material skyboxMaterial = new Material (Shader.Find("Mobile/Skybox")); // Create a new material for the skybox.
string[] skyboxTextures = new string[] {"_FrontTex", "_BackTex", "_LeftTex", "_RightTex", "_UpTex", "_DownTex"}; // The skybox textures names
Color color; // Color that will be set for each pixel

// Create 6 different textures to use as skybox
for (int i = 0;i < 6; i++)
{
	// Loop horizontally on all pixel
 	for(int x = 0; x < texture.width; x++)
 	{
		// Loop vertically on all pixel
		for(int y = 0; y < texture.height; y++)
 		{
 			float randomValue = Random.Range (0.0f,1.0f); // Random value between 0 and 1
 			if(randomValue < 0.001f) // 0.1% Of the pixel are set to a grey/white pixel with varying transparency
 			{
 				float colorValue = Random.Range (0.6f,1.0f); //Random value to use as RGB value for color
 				color = new Color(colorValue,colorValue,colorValue,Random.Range (0.5f,0.9f)); // Set the pixel to a white/grey color with variying transparency
 				texture.SetPixel(x, y, color); // Set the pixel at (x,y) coordinate as the Color in color
			}
 			else if(randomValue < 0.0015f && x > 0 && y > 0) // 0.05% Of the pixels that aren't on the first row/column
 			{
 				float colorValue = Random.Range (0.85f,1.0f); //Random value to use as RGB value for color
				color = new Color(colorValue,colorValue,0.0f,Random.Range (0.6f,0.9f)); // Set the pixel to a yellowish color with variying transparency

				// Set a square of 4 pixels to the current color. The 3 other pixel have been stepped on earlier so their color won't be modified afterward
 				texture.SetPixel(x, y, color);
 				texture.SetPixel(x-1, y, color);
 				texture.SetPixel(x, y-1, color);
 				texture.SetPixel(x-1, y-1, color);
 			}
 			else
 			{
 				texture.SetPixel(x, y, Color.black); // Set the pixel as black for the default background color
 			}
 		}
	}

// Apply all SetPixel calls
texture.Apply();

// Set the current texture on one side of the skybox
skyboxMaterial.SetTexture(skyboxTextures[i],texture);
}

// Set the RenderSettings skybox to the created material
RenderSettings.skybox = skyboxMaterial;

Once you have this going, you can try more complex patterns such as using perlin noise for pixel color or more complex object such as galaxy or star clusters!

For the interested, here is a first screenshot of the game I’m working on. It shows the terrain, the ground of a sci-fi base and a wood table. All models and texture are procedurally created. The ground texture come from perlin noise, the table use Voronoi noise and the sci-fi base ground are sinuses to make the lines that crosses.

First screenshot of new procedural game

First screenshot of new procedural game

First try on a C++/OpenGL physic engine

Around 1 year ago, I spent a lot of time working on a basic OpenGL physic engine. This was my first experience with OpenGL  and I’ve learned a lot from it. The project was coded using Visual Studio 2012(C++) and Freeglut(OpenGL). The only external library used is for rendering text. All the physic has been coded by myself and all models/structure are procedurally created at run-time. This project isn’t updated anymore and still have bugs in its current state. The goal of this project was to learn OpenGL and prototype an engine.

Features : 

  • AABB collision detection for all boxes
  • Adjustable gravity
  • Interaction with moving platforms(Horizontal and Vertical)
  • Ability to shoot/drop boxes of different size/speed
  • Freeze the time, move around and start the time back to have a different point of view on something.

I figured video-editing would be a good skill to learn if I want to make trailers for game or video tutorial and gave it a shot with this project. Here is a video of my OpenGL engine:

*Note : This video was my first real experience with video-editing. Video quality should increases in the following videos.

To be implemented in next version

I’ve learned a lot working on this project and on the other project I’ve worked on since then. Here is a list of things I would make like to implement in a new version of this engine.

  • I would like to use a Test Driven Development approach to make it easier to maintain/scale the project. I’m considering using the Google C++ Testing Framework.
  • Use an entity/component system to have a better separation of the data and logic of an object.  I would be interested in using something like Artemis that already has 2 C++ port.
  • Implement or Find a library to use Quaternion instead of Euler angles to avoid any Gimbal Locking issues. Quaternion also easily allow smooth rotation between any starting/ending orientation by using Slerp. Quaternion workflow is really nice to use once you get the hang of it.
  • Change my integration method from Euler to a more precise integrator like Runge-Kutta4. This article from GafferOnGames(I would recommend reading all post from this blog to anyone interested in game development)  explain why Euler is an imprecise integrator and is a bad solution if you want to have a realist simulation.
  • Evaluate the best approach for Timestep management. The project currently use a completely variable time step that could lead to unwanted behavior in certain cases. Again, This Article from GafferOnGames is a really good resource for this question.
  • Use a more robust approach to collision detection and response to the collision. Use a vectorial approach that is completely independent from the gravity used. Gravity should become a vector to allow intensity as well as direction changes at runtime.
  • Evaluate the utilization of a bool for object that are grounded on something else for physic evaluation. It seems this would make some platform movement much easier.
  • Use a sleeping mechanism for objects that are not moving. This would greatly reduce the collision calculation.
  • When testing collision, use rough estimates to eliminate groups of object that are sure to not collide together, or far one from another.

This project was really interesting and I’ve learned a lot from the experience. Coding everything from the beginning (except for using C++/OpenGL)  is a different experience than using a featured rich engine like Unity3D.

Doing this gave me a better understanding of the way Triangles are rendered on the screen and the differents matrix used in graphical development, such as Model, Projection and Persepective matrics. This was also my first project featuring Procedural Generation and it got me really interested. I’ll try to push this aspect farther in my current game in development, Dungeon Grind.

Hope you enjoyed looking at the video and reading my thoughts on this project. I’ll make sure to post it on my blog if I make a second version of this OpenGL Physics Engine.

Procedural Dungeon Generation Part3

During the Part2, we updated the dungeonMap to have all coordinates in the same room share the same numerical value. However, all rooms are unconnected and that would make a pretty boring dungeon. We’ll correct that!

Add corridors
The goal of this step is to add corridor and make sure that all rooms are connected together and are all accessible. In the following image, you can see an updated version of the previously created dungeon with corridors. Each room now have a corridor starting from a random position in it(C#_Start) going to another random position in a random room/corridor(C#_End).

PDG Step3

Procedural Dungeon Generation – Step 3

Here is the whole CreateDungeonHalls() method that update the dungeonMap with added corridors. The code below will be split in 3 part below and explained individually.

// Create a hall starting from each room, connecting to another room or corridor
static private int[,] CreateDungeonHalls(int[,] _dungeonMap, int _sizeX,int _sizeZ, int nbrRoom)
{
	int _x1; // x coordinate of the starting position
	int _z1; // z coordinate of the starting position
	int _x2; // x coordinate of the ending position
	int _z2; // z coordinate of the ending position

	// Start a corridor from each room
	for(int _curRoomNbr = 1; _curRoomNbr <= nbrRoom; _curRoomNbr++)
	{
		int nbrRoomsTry = 0; // Counter is used to avoid looping forever if there is a bug in the program.
		int _nbrRoomsTryMax = 5000;

		// Find a random coordinate in the room with number _curRoomNbr
		_x1 = Random.Range (1, _sizeX-1);
		_z1 = Random.Range (1, _sizeZ-1);

		// Find a random position with the current room number
		while(_dungeonMap[_x1,_z1] != _curRoomNbr && nbrRoomsTry < _nbrRoomsTryMax)
		{
			_x1 = Random.Range (1, _sizeX-1);
			_z1 = Random.Range (1, _sizeZ-1);
			nbrRoomsTry++;
		}
		nbrRoomsTry = 0;

		// Find a random coordinate in any room/corridor that isn't the first room
		_x2 = Random.Range (1, _sizeX-1);
		_z2 = Random.Range (1, _sizeZ-1);

		// Find a random position in a different room or corridor created from a previous room
		while((_dungeonMap[_x2,_z2] == 0 || _dungeonMap[_x2,_z2] == _curRoomNbr) && nbrRoomsTry < _nbrRoomsTryMax)
		{
			_x2 = Random.Range (1, _sizeX-1);
			_z2 = Random.Range (1, _sizeZ-1);
			nbrRoomsTry++;
		}

		// Difference between both coordinates on each axis. This is used to find direction of corridor to create
		int _diffX = _x2 - _x1;
		int _diffZ = _z2 - _z1;

		int _xDirection = 1; // Coefficient used to loop in the right direction on x axis(-1 or 1)
		int _zDirection = 1; // Coefficient used to loop in the right direction on z axis(-1 or 1)

		// Evaluate direction of the corridor on the X and Z axis
		if(_diffX != 0)
		{
			_xDirection = _diffX/Mathf.Abs(_diffX); // 1 = Left to Right (->) ... -1 =  Right to Left (<-)
		}
		else
		{
			_xDirection = 0; // No horizontal corridor if they are aligned on X axis
		}

		if(_diffZ != 0)
		{
			_zDirection = _diffZ/Mathf.Abs(_diffZ); // 1 = Top to Bottom( \/ ) ... -1 = Bottom to Top ( /\ )
		}
		else
		{
			_zDirection = 0; // No vertical corridor if they are aligned on Z axis
		}

		// randomize corridor portion and height
		int _hallWidth  = Random.Range (4,10);
		int _hallHeight = Random.Range (4,10);

		//Create vertical part of the hall
		for(int i = _x1; i != _x1 + _xDirection*_hallWidth; i += _xDirection)
		{
			for(int j = _z1; j != _z2; j += _zDirection)
			{
				if(i >= 0 && i < _sizeX && j >= 0 && j < +_sizeZ) // Make sure that the index is within the map range
				{
					if(_dungeonMap[i,j] == 0) // Only modify empty position, not rooms position
					{
						_dungeonMap[i,j] = -1; // Write corridor as -1 in the dungeonMap
					}
				}
			}
		}

		//Create horizontal portion of the hall
		for(int i = _x1; i != _x2; i += _xDirection)
		{
			for(int j = _z2; j != _z2 + _zDirection*_hallHeight; j += _zDirection)
			{
				if(i >= 0 && i < _sizeX && j >= 0 && j < +_sizeZ) // Make sure that the index is within the map range
				{
					if(_dungeonMap[i,j] == 0) // Only modify empty position, not rooms position
					{
						_dungeonMap[i,j] = -1; // Write corridor as -1 in the dungeonMap
					}
				}
			}
		}
	}
	return _dungeonMap; // The _dungeonMap contains 0 for non-room, -1 for corridor and N for rooms
}

¸
First part of the method (line 10-38) simply find a random position to start a corridor from each room and an ending position in another room or in a previously added corridor.

// Start a corridor from each room
	for(int _curRoomNbr = 1; _curRoomNbr <= nbrRoom; _curRoomNbr++)
	{
		int nbrRoomsTry = 0; // Counter is used to avoid looping forever if there is a bug in the program.
		int _nbrRoomsTryMax = 5000;

		// Find a random coordinate in the room with number _curRoomNbr
		_x1 = Random.Range (1, _sizeX-1);
		_z1 = Random.Range (1, _sizeZ-1);

		// Find a random position with the current room number
		while(_dungeonMap[_x1,_z1] != _curRoomNbr && nbrRoomsTry < _nbrRoomsTryMax)
		{
			_x1 = Random.Range (1, _sizeX-1);
			_z1 = Random.Range (1, _sizeZ-1);
			nbrRoomsTry++;
		}
		nbrRoomsTry = 0;

		// Find a random coordinate in any room/corridor that isn't the first room
		_x2 = Random.Range (1, _sizeX-1);
		_z2 = Random.Range (1, _sizeZ-1);

		// Find a random position in a different room or corridor created from a previous room
		while((_dungeonMap[_x2,_z2] == 0 || _dungeonMap[_x2,_z2] == _curRoomNbr) && nbrRoomsTry < _nbrRoomsTryMax)
		{
			_x2 = Random.Range (1, _sizeX-1);
			_z2 = Random.Range (1, _sizeZ-1);
			nbrRoomsTry++;
		}

Second part of the method (line 40-64) calculate the difference between the start/end position on the X and Z axis. It also calculate a coefficient to increment in the good direction while creating the corridor.

		// Difference between both coordinates on each axis. This is used to find direction of corridor to create
		int _diffX = _x2 - _x1;
		int _diffZ = _z2 - _z1;

		int _xDirection = 1; // Coefficient used to loop in the right direction on x axis(-1 or 1)
		int _zDirection = 1; // Coefficient used to loop in the right direction on z axis(-1 or 1)

		// Evaluate direction of the corridor on the X and Z axis
		if(_diffX != 0)
		{
			_xDirection = _diffX/Mathf.Abs(_diffX); // 1 = Left to Right (->) ... -1 =  Right to Left (<-)
		}
		else
		{
			_xDirection = 0; // No horizontal corridor if they are aligned on X axis
		}

		if(_diffZ != 0)
		{
			_zDirection = _diffZ/Mathf.Abs(_diffZ); // 1 = Top to Bottom( \/ ) ... -1 = Bottom to Top ( /\ )
		}
		else
		{
			_zDirection = 0; // No vertical corridor if they are aligned on Z axis
		}

Last part of the method (line 66-101) start by calculating a random width and height for the new corridor. The map is then updated with the vertical portion of the corridor first and the horizontal portion of the corridor second. Corridor are a simple corner for now. It would be possible to add randomness/more corner or connect them in a different way by modifying this part. It would also be possible to customize the corridors width/height depending on the room size/distance between rooms and such.

To create the corridors, we take the previously evaluated direction on the X axis and step toward that direction with the corridor width. This corridor is filled on the Z axis from z1 to z2. Same thing is done with the horizontal part of the corridor.

		// randomize corridor portion and height
		int _hallWidth  = Random.Range (4,10);
		int _hallHeight = Random.Range (4,10);

		//Create vertical part of the hall
		for(int i = _x1; i != _x1 + _xDirection*_hallWidth; i += _xDirection)
		{
			for(int j = _z1; j != _z2; j += _zDirection)
			{
				if(i >= 0 && i < _sizeX && j >= 0 && j < +_sizeZ) // Make sure that the index is within the map range
				{
					if(_dungeonMap[i,j] == 0) // Only modify empty position, not rooms position
					{
						_dungeonMap[i,j] = -1; // Write corridor as -1 in the dungeonMap
					}
				}
			}
		}

		//Create horizontal portion of the hall
		for(int i = _x1; i != _x2; i += _xDirection)
		{
			for(int j = _z2; j != _z2 + _zDirection*_hallHeight; j += _zDirection)
			{
				if(i >= 0 && i < _sizeX && j >= 0 && j < +_sizeZ) // Make sure that the index is within the map range
				{
					if(_dungeonMap[i,j] == 0) // Only modify empty position, not rooms position
					{
						_dungeonMap[i,j] = -1; // Write corridor as -1 in the dungeonMap
					}
				}
			}
		}
	}
	return _dungeonMap; // The _dungeonMap contains 0 for non-room, -1 for corridor and N for rooms
}

Now that the map dungeonMap is updated with all the rooms and corridors, we need to find a list of all wall to create and instantiate them in the dungeon scene. This will be the topic of the next 2 posts.

Update : I’m currently updating the way I do part 4 and 5. They are accessible on the Dungeon Grind repository on GitHub but, they can’t be buggy or in an imperfect state. You can however look for inspiration on how to continue. The most important file is DungeonGenerator.cs and DungeonManager.cs can be useful.

Procedural Dungeon Generation Part2

Now that we filled the array with squares(1), we need to find every independent room so we can make sure they are connected by corridors. The rooms are identified by looping through the whole array dungeonMap. Every time a square(1) is encountered, an algorithm find all the adjacent cells that are part of the same room. The algorithm work like a minesweeper game when you hit an empty area and it crawls around to open it.

Find Rooms
The goal of this step is to identify all independent room and modify the dungeonMap with a different value for each room as presented in the next picture.

PDG Step2

PDG Step 2 – Find Rooms

The method FindRooms() take as input the dungeonMap and its size. The first step of the algorithm is to create a copy of the dungeonMap. Then, we loop through the map searching the square(1) that were created by the previous step. The first encountered square is added to a list of coordinate to test.

Every time a coordinate is tested, the 8 coordinate around it are evaluated. Each coordinate that are also a square(1) are added to the list of coordinate to test. They are also put as 0 in the modifiedMap to make sure we don’t add them to the list again. The algorithm then try the next cell on the list until there are no more cell to test. This ensure that all cell are tested and the whole room is identified.

Here is the code for the FindRoom() function:

// Loop through the dungeon map and find rooms created by touching squares
static int FindRooms(int[,] _dungeonMap, int _sizeX, int _sizeZ)
{
	
	List<Vector2> ListCoordToTest = new List<Vector2>(); // List of all coordinate that need to be evaluated for the current room.
	int[,] _modifiedMap  = new int[_sizeX,_sizeZ]; // This array will be a copy of _modifiedMap where all known room cell are set as 0 to be ignored by the next steps
	int    _nbrRoomFound = 0;
	
	System.Array.Copy(_dungeonMap,_modifiedMap, _sizeX*_sizeZ); // Create a copy of the _dungeonMap in _modifiedMap
	
	//Loop through all position of the map
	for(int i = 0; i < _sizeX; i++)
	{
		for(int j = 0; j < _sizeZ; j++)
		{
			// If a room is found
			if(_modifiedMap[i,j] == 1)
			{
				ListCoordToTest.Add(new Vector2(i, j)); // Add the coordinate to the list to test
				
				while(ListCoordToTest.Count > 0) // Loop while there are coordinates to test
				{	
					int _x = (int)ListCoordToTest[0].x; // Find x value of coordinate to test
					int _z = (int)ListCoordToTest[0].y; // Find y value of coordinate to test
					ListCoordToTest.RemoveAt (0); // Remove the currently tested coordinate
					
					_dungeonMap[_x,_z] = _nbrRoomFound + 1; // Update the _dungeonMap with the current number of room found
					
					// Look the 8 coordinate around the current coordinate to find for connected rooms
					for(int _xAround = _x - 1; _xAround <= _x + 1; _xAround++)
					{
						for(int _zAround = _z - 1 ; _zAround <= _z + 1; _zAround++)
						{
							// Test if evaluated coordinate is a square and need to be added to the room
							if(_modifiedMap[_xAround,_zAround] == 1)
							{
								ListCoordToTest.Add(new Vector2(_xAround, _zAround)); // Add it to the list of coordinates to test
								_modifiedMap[_xAround,_zAround] = 0; // Remove the room position from the modified map so we don't step on it again
							}
						}
					}
				}
				_nbrRoomFound++;
			}
		}
	}
	return _nbrRoomFound;
}

Now that we have identified all the rooms, we can add corridors so all rooms are accessible.

Procedural Dungeon Generation Part1

Initialization

To generate my dungeons, I use 2 classes :

public class DungeonManager : MonoBehaviour{}
static class DungeonGenerator{}
  • DungeonManager is attached to a GameObject(“__DungeonMaster”) that is only instantiated when the Dungeon scene load. This class is responsible for finding the level information, spawn the dungeon with the DungeonGenerator static class, add monsters and test for victory.
  • DungeonGenerator is a static class that randomize dungeon configuration and instantiate the GameObject

The main method of DungeonGenerator is SpawnDungeon(). This method calculate the dungeon configuration and spawn the walls as GameObject. If you look the step-by-step graphic of the precedent post, you can see each step translated in functions.

// Randomize configuration of dungeon and instantiate it
static public int[,] SpawnDungeon(int _sizeX,int _sizeZ, int _nbrSquareForGeneration)
{
	int[,] _dungeonMap; // 2D Array of int that will hold the dungeon map information

	_dungeonMap = CreateDungeonSquares(_sizeX,_sizeZ, _nbrSquareForGeneration); // Fill the array with different squares of various size
	_nbrRoom = FindRooms (_dungeonMap,_sizeX, _sizeZ); // Loop through the dungeon map and find the square that form rooms together
	if(_nbrRoom &gt; 1)
	{
		_dungeonMap = CreateDungeonHalls(_dungeonMap, _sizeX,_sizeZ,_nbrRoom);  // Add halls between the created room
	}

	SpawnEnvironment(_dungeonMap, _sizeX, _sizeZ, _nbrRoom); // Create environment(Spider eggs for now)

	_ListOfWallsToCreate = EvaluateWallToBuild(_dungeonMap, _sizeX, _sizeZ); // Loop through the dungeon map to find all the wall that need to be created
	InstantiateDungeonWalls(_ListOfWallsToCreate); // Instantiate all the wall found by the EvaluateWallToBuild function

	return _dungeonMap;
}

The array int[,] _dungeonMap is the result of the SpawnDungeon() method.

Square creation
The goal of this step is to fill the array with various squares of different size/position. They are written as 1 in the array.

PDG - Step 1

Procedural Dungeon Generation – Step 1

The first function, CreateDungeonSquares(), fill the _dungeonMap with the inputted number of squares of random size at random position. It’s pretty straightforward and just make sure that all the rooms are on the map. It doesn’t matter if rooms overlap, that’s in fact what we want. This will leads to variously shaped room and more randomness.

// Create the inputted number of square on the map
static int[,] CreateDungeonSquares(int _sizeX, int _sizeZ, int _squareNbr)
{
	int[,] _newMap = new int[_sizeX,_sizeZ]; // Create empty map with imputted dimensions

	for(int i = 0; i &lt; _squareNbr; i++)
	{
		// Calculate random room size and position. Make sure the room is inside the map.
		int _roomSizeX = Random.Range (10,_sizeX/5);
		int _roomSizeZ = Random.Range (10,_sizeZ/5);
		int _roomPosX  = Random.Range (1,_sizeX-_roomSizeX);
		int _roomPosZ  = Random.Range (1,_sizeZ-_roomSizeZ);

		// Add the square to the map. Avoid making square on the external values of the map.
		for(int j = _roomPosX; j &lt; _roomPosX + _roomSizeX; j++)
		{
			for(int k = _roomPosZ; k &lt; _roomPosZ + _roomSizeZ; k++)
			{
				_newMap[j,k] = 1;
			}
		}
	}
	return _newMap;
}

Dungeon Grind – Procedural Dungeon Generation Tutorial

I decided to work on tutorials that explain how are generated the dungeon in my game, Dungeon Grind. The algorithm could be improved at various places and I still get some bugs(missing wall in some case) but it get the jobs done. I’ll update the post whenever i fix any bugs. Here is an example of a randomly generated dungeon with a size of 500×500.

Dungeon 500x500

Generated dungeon of size 500×500

The algorithm will be explained in greater detail in the following post.
Part 1 – Create squares
Part 2 – Find Rooms
Part 3 – Add Corridors
Part 4 – Evaluate walls to build (WIP)
Part 5 – Instantiate walls (WIP)

The basic idea can be understood with this graphic :

PDG Step by step graphic

Step-by-step tutorial for Procedurally generated dungeon

Dungeon Grind – Presentation

Hi!

It’s been a while since my last pot. I’ve been coding the whole time and improved a lot. Since the beginning of the month, I’ve been working on a game and decided to share it here. Since I have around 3 works of works on the project, there are a lot of bugs that need to be fixed and feature that are partially implemented. Most of the fun right now come from fighting in the dungeon.

Game Links

Dungeon Grind WebPlayer (Updated regularly)

Dungeon Grind Client (Updated less often)

Game Forum

I’ve decided to create a forum for the game even if it’s pretty early. This will allow early discussion with the player that might be interested in the project and allow them to follow it. I’m also looking to have bug reports when the game get to a farther stage.

Dungeon Grind Forum

Description

The game resolve around a dungeon that you can enter to fight monster. You can choose dungeon level and difficulties to get stronger enemy as your character grow. The dungeon is procedurally generated with random placement of monster. The more you fight, the stronger you get. There are no overall level, but different things are leveling. For each spell, you can select if you want to level the damage, the cooldown or the mana cost while using it. You also have different skill like woodcutting, fighter or ice mage that level. Last addition to the game has been a level system for your weapon!

There is also a building and crafting system implemented but those need a rework. To craft weapon, build a Crafting Table and use it. The next image is an example of procedurally generated dungeon. There are different spider in the dungeon and the player is the white capsule. Green line represent path taken by spider that are attacking the player.

Procedurally generated dungeon level 8

Procedurally generated dungeon level 8

And here’s an example of what the game looks like from the player perspective :

Spider BBQ!

FireBat spell used on spiders while being cornered

So this is it for the presentation of the game! A post explaining the dungeon generation is coming!