Thursday, June 20, 2019

Maps of Flap of Daps

So a while ago I had procdeural level generation in Flock of Dogs. This was before I added networking and before I decided that I would draw tiles of a certain, very large size. Now I've got procedural level generation back in the game working with both systems. I'm using the same algorithm I used from before (and I don't remember where I found it). There's a million tutorials out there for random level generation now, and this one's fairly simple, not innovative, but it's effective and works for Flock of Dogs. Here's an outline of how it works:

1) Creates a 2-dimensional array

int width = 12;
int height = 14;
int[,] map = new int[width, height];


2) It iterates thru all elements and randomly assigns it a 0 or a 1, representing a tile that is clear and a tile that is a wall, respectively. At this point, you'd just get a map full of noise. Ther's one other condition: if the element represents an edge tile, (i.e. take an element map[x,y] when x or y is 0 or when x is equal to width - 1 or y is equal to height - 1), it will always fill it with a 1. This guarantees a solid border.

3) Then, it iterates through the whole map again. Starting with the first tile that is clear, it declares a new Room. A room is a collection of tiles that are touching and are clear. To define a new room, you recursively check each adajcent clear tile for their adjacent clear tiles, adding all of them to the same room. For this algorithm, diagonal tiles are not considered adjacent. After the first open tile has been processed and turned into a room, the algorithm continues on through the tiles, looking for more open tiles. When it finds the next one, before it creates a second room, now it has to check if that tile is part of an existing room. If it isn't part of a room, then it can create a new room.

4) After all rooms have been found, which, by the above method of creation are not connected to each other, it then proceeds to connect them all! It defines the first room as the Main Room. Then it uses a straight line drawing algorithm to set a path of tile spots map[x,y] to 0, for some collection of x's and y's that result in a straight-ish line (can look kinda like stairs, if it's diagonal) from the center of the Main Room to the next room. It then marks that next room as connectedToMain = true. I forgot to mention that a Room is defined not just by its collection of tiles that are open and adjacent, but also if it is connected to main!

4b) The straight line drawing algorithm just like checks the x,y coordinate of the center of Room A compared to the center of Room B, and increments or decrements x or y step by step, clearing each tile, until it reaches the center of the Room B. You can tweak this to adjust how wide you want the connecting passage to be.

5) Then it proceeds to each room and draws a straight line to other rooms until it hits a room that is marked connectedToMain. At which point, it marks all the rooms that it has drawn a line through as connectedToMain = true. And voila. All the rooms are connected now! Yayaya.

6) Define a Unity GameObject that has a Unity Grid, upon which it can create a Unity Tilemap, then iterate thru map and if an element equals 1, instantiate a mountain tile, otherwise leave it blank! The mountain tiles are 'smart tiles', by which I mean, whenever a new tile is added to their Tilemap, they check their list of rules (which I set up) and see which rule applies given their current neighbors and decide which mountain sprite (of the 64 mountain tiles sprties I've made) they should actually be (and its corresponding collision polygon).

*
**
********
*******************
**************************************
****************************************************


****************************************************
**************************************
*******************
********
**
*


Yaya. So, as you might've noticed, that gif already has the islands, clouds, and tetris pieces in place too. After I've generated the tile map, I have a big process for how I place all these things. There's sort of two ways. I either go thru each tile and do a thing or I decide how many things I want to do and then pick random tiles.

For islands, I go through the map again and for each tile that is clear. According to some spawn chance, I may or may not spawn an island in that tile. There's some nuance here for bigger islands that take up several tile spaces, and I'm probably going to revisit this bc I'm not crazy about the look of every island being exactly centered in a tile/the exact center of a 2x2 tiles/never more than 1 island per tile.

For tetris pieces and clouds, I don't iterate thru each tile, but rather start with a random number of objects I want to spawn, then randomly pick a tile an open tile, then randomly pick a position within that tile's boundaries amd place the object there. 

The clouds are generated as cloud groups, which comprise up to a few big clouds, then up to a few times some number of medium clouds, then up to a few times some number of medium clouds times some number of small clouds. If it a storm group, then they'll be rain clouds with random amounts of rain levels.

For placing trees, monster dens, and flower traps, since I happened to have a list of all the islands that have been spawned, and since each island happens to have an accessible array of permissable spawn points, I pick a random island, pick a random spawn point on it, and drop the corresponding object. I then remove that spot from the island's available spawn points.

So anyway, that's a lot of stuff. And there's going to be more. Shops, dams, air rivers, cave entrances/exits, fortresses, oases, beast lairs, villages/cities, whale smiths, kennels, inns, camp grounds, treehouses, a festival, race tracks, and more! I PINKY PROMISE NOTHING WILL BE CUT OR CHANGED FROM THIS PLAN EVER AND IN FACT I WILL ONLY ADD MORE.

So anyway, that's a lot of stuff. And performance, even in this mostly shaderless, mostly particle effectless game, does become an issue. So I create an ObjectsInATileHolder object! And whenever I spawn any of the non moving structures I've mentioned above, I associate them with an ObjectsInATileHolder. Then, if I want to 'turn off' a tile, I tell the corresponding ObjectsInATileHolder to 'turn off' all its objects, which, in Unity terms, just means setting them to inactive, which means that Unity will pay no attention to them in its core game loop. I use a coroutine to check once every second the location of the camera and find out which map tile it's over. If it is over a different tile than it was the second before, it then makes sure to turn on all its neighbor tiles (within a range I've currently set to 2) and makes sure any tiles that were on and that are not within 2 tiles, are turned off.

*
**
********
*******************
**************************************
****************************************************

****************************************************
**************************************
*******************
********
**
*

I generate all this using System.Random, which will reproduce the same 'random' set of values if given the same seed. So fo network synchronization, all I need to do is send that seed number to any connecting clients! (Assuming all the parameters match: map width, map height, room-connection-passage-width, chance to spawn an island, number of trees to spawn, number of clouds to spawn, number of tetris pieces to spawn, number of dens to spawn, etc.)

No comments:

Post a Comment