# Turning On-Chain Dungeon Layouts into Playable Unity Levels [Heroes](https://heroes.fun) are a set of 3,333 character NFTs stored on the Ethereum blockchain. Their stats were generated as they were minted and are stored on the blockchain thus enabling anyone to build on top of the Heroes. Heroes isn't the first project to add to the library of on-chain assets that can be used for open world building. Another such project is called Crypts and Caverns, described as another "generative, on-chain, Lootverse ‘lego’." These are set of 9,000 generative dungeon maps stored on the Etheruem blockchain which include a matrix configuration representing floors, walls, doors, and points of interest. The Heroes team repurposed the Crypts and Caverns NFTs for the Hero universe, created custom tile sets, generated images from base configurations, and programatically turned these simple configurations into playable Unity game levels in the Merlin's Dungeons game. Base configuration example which is stored on chain: ``` ['X', 'X', 'X', 'X', 'X'], ['X', ' ', ' ', 'X', ' '], ['X', ' ', 'X', 'd', ' '], ['X', 'd', ' ', ' ', 'X'], ['X', ' ', 'p', ' ', 'X'], ['X', 'X', ' ', 'X', 'X'] ``` We turned that into this: {%youtube 0iLyQAzOwsw %} And these ![](https://i.imgur.com/EXMFi34.gif) ## Creating static dungeon images The first challenge was to write some code that would programatically render dungeon images from the base configurations. The idea was to create [Hero Dungeons](https://market.heroes.fun/dungeons) for people interested in the Heroes Universe to mint on the Ethereum blockchain using our premium currency, Hearts. The grand vision for dungeons was to create playable game levels where Heroes could defeat hordes of enemies, collect loot, gold, and other valuables, level up their Hero, and most importantly have fun. These dungeons would then tie into the larger Heroes Universe in time. We created the following tile sets. Five based on the original Crypts and Caverns environments and one special environment for the Heroes universe called The Ghoul Underworld. | | | | | |-----|-----|-----|-----| |![](https://i.imgur.com/xLfr3Qv.png)|![](https://i.imgur.com/nDSTlU6.png)|![](https://i.imgur.com/xSfVEhg.png)|![](https://i.imgur.com/1b6XTMM.png)| |![](https://i.imgur.com/xcqMrvW.png)|![](https://i.imgur.com/MLYsDQP.png)|![](https://i.imgur.com/JEPIyDa.png)| This was only the first part of the equation. Now we had to write the code to place tiles in the proper positions based on the on-chain dungeon configurations. In order to do this we manually noted the rows and columns of various tile categories from our tile sets. Each tile set had the same tiles except were themed differently for each environment. For example we noted the floor tiles, rock tiles, door tiles, point tiles etc. The end result did not use all the tiles available to us, but we used enough to generate interesting maps. You can find the code here https://github.com/0xdeployer/dungeon-renderer ## Creating Unity Levels We modified the static dungeon image code to output JSON config files for each dungeon. We already had the coordinates of each tile. We realized if we just output the coordinates we could then draw the same image in unity and have fine tuned control over which area was walkable and which area was not. An example JSON output can be seen here https://raw.githubusercontent.com/0xdeployer/dungeon-renderer/main/dungeon-config.json We utilized the NavMesh2D Unity package to create walkable areas within the dungeons for enemy AI. Once we loaded the tile set and JSON into unity we used the following code to draw the dungeon and place the nav mesh: ```csharp= void DrawDungeon() { List<Vector3> floorTiles = new List<Vector3>(); //Set layers for each Tilemap walls.gameObject.layer = 10; foreach (var cell in data.dungeon) { int spriteIdx = cell.sy * 12 + cell.sx; Tile tile = Tile.CreateInstance<Tile>(); tile.sprite = spriteDictionary[addressableId][spriteIdx]; Tilemap activeTilemap; bool isFloor = cell.sprite.type == "floor" || cell.sprite.type == "inset"; if (cell.sprite.type == "wall") { activeTilemap = walls; } else if (isFloor) { activeTilemap = floor; } else { activeTilemap = map; } Vector3Int localTilePlace = new Vector3Int(cell.x, cell.y * -1, 0); activeTilemap.SetTile(localTilePlace, tile); Vector3 place = activeTilemap.CellToWorld(localTilePlace); if (cell.sprite.type == "floor") { floorTiles.Add(place); } } try { TilemapCollider2D wallCollider = walls.gameObject.AddComponent<TilemapCollider2D>(); TilemapCollider2D floorCollider = floor.gameObject.AddComponent<TilemapCollider2D>(); floorCollider.usedByComposite = true; NavMeshModifier floorNavMeshMod = floor.gameObject.AddComponent<NavMeshModifier>(); NavMeshModifier wallNavMeshMod = walls.gameObject.AddComponent<NavMeshModifier>(); floorNavMeshMod.overrideArea = true; floorNavMeshMod.area = 0; wallNavMeshMod.overrideArea = true; // not walkable wallNavMeshMod.area = 1; AsyncOperation loadNavMesh = surface2D.BuildNavMeshAsync(); loadNavMesh.completed += (AsyncOperation arg) => { Store().floorTiles = floorTiles; Actions().MapLoadedAction(); }; } catch { } } ``` We created a global data store so we can access floor tiles at any time. ## Wrapping Up Creating playable dungeon maps from on-chain data was only one of the interesting technical challenges we have gotten the opportunity to work on. We are only laying the foundation for what's to come! Other fun challenges that have come from working on the Heroes project are layering each unique 2D Hero in game and creating a system to securely allow many players to connect and play through a single player dungeon experience while still having server authority to control dropping valuable loot. Follow along at https://heroes.fun.