Here is the githup page for this project (MIT License): github.com/uheartbeast/walker-level-gen Hope you all enjoy the video! - Ben P.S. Audio was a bit off in this video. I' guess I'm having mic issues. I'll see if I can get it fixed for the next video.
Benjamin, this video has changed everything for me, for the last four months I have been working on a procedural dungeon crawler in Godot using skills which i gained primarily from your courses online, but I had been struggling to find tutorials on the subject level generation for Godot, this video is exactly what I needed to make a breakthrough in programming my game! Thank you so much, I cannot believe you do all of this out of a passion for the art of games, I admire you more than you can know my friend. I hope one day to release my own game and to embody your passion for the art as well. Again, thank you.
I was having a lot of issues replicating this with the 4.1+ Terrain system, after playing around for over an hour, I was finally able to achieve the results at 4:17 Tips: -Only paint/assign the floors to the terrain, just like at 3:30, this will assign the walls a peering bit value of -1 meaning they will be placed only when adjacent cells are empty. -When going to paint cells, select Connect mode, it looks like an 8 sided arrow, if you select any of the tiles on the right, or path mode, it just acts buggy
I'm interested in seeing more content about your random walker, specifically how to "coherently" place items like doors, chests, power-ups etc... :) Thanks for the interesting content!
Another awesome tutorial. I'm not sure of the exact term, but I would love to see your take on random level generation, but with hand-made rooms. (Connecting pre-existing rooms together) For example like in Binding of Isaac or Enter the Gungeon.
@@xaxaWOW You're right! I just always connect that term a lot more to something like randomization of color and parts for monsters (No Man's Sky, Starbound), but I guess that's kind of the same thing, just on a larger scale. :D
Oh my, it's just amazing. You're making complex stuff simple and INDEED understandable. No screw-your-brain recursive algorithms, just simple and plain code that results in a great system at the end of the end. Man, you're great.
I really could have used this a week ago haha The dungeon generation for my game is pretty different, but this definitely would have helped! I was trying to use your old tumblr post explaining generation you did for an old game you were making, but ended up having to kind of make it from scratch. Great video!
Gonna leave this here in case someone with Godot 4 wants to use this, since figuring it out wasn't very straightforward at least for me. To set a cell you use this, the Atlas Coordinate came from my fully walled tile (using default full tilemap) which is important for the terrain connect function to work properly. tileMap.set_cell(ground_layer, tile_pos, ground_layer, Vector2i(0,3) ) And the other thing you have to do for the autotiling to come to effect is by this code, ground layer is a variable for my base layer ( usually 0 ) var used_cells = tilemap.get_used_cells(ground_layer) tilemap.set_cells_terrain_connect(ground_layer, used_cells, ground_layer, ground_layer) Also with bigger tiles you might need to crank that factor for the spawn points up to 64
This saved my bacon. For clarification, by "fully walled" tile, you mean the one that only has a single spot painted in the middle, which indeed, if you inspect that tile in the tile set should have every one of its bitmask fields set to -1. Thanks for the tip!
This tutorial is one of the most amazing ones that I've seen so far. You pretty much explained in a small sentence what each code does in order for a wide audience to understand. Most of the tutorials don't do that, thinking that it takes too long, but you proved everyone wrong with this video man. I just started like 3 days ago learning Godot, and this video pretty much saved most of my questions that I had. Thank you so much.
Hey Ben, awesome video as always! I noticed that the vibrations from you typing on your keyboard gets picked up by the microphone. I used to put a small thin cushion under my keyboard to avoid this, until I got a shock mount for my mic. Just a tip, you're videos are great either way :)
Great video! I would love to see you expand upon this random generation algorithm and go over other random generation algorithms as well! Loving your tutorials and Godot!
Love this video, teaches important aspects of Procedural Generation without going to heavily into theory or to heavy but rather by teaching a simple algorithm.
You are like the brackeys of Godot, thanks for your awesome tutorials you inspired me to make my first game and release it, have an awesome day further ;)
FOR PEOPLE STRUGGLING IN GODOT 4 i figured out a pretty good way to gibe the same effect in the video without having to have all the tiles on screen to start off with for Godot 4 create a new array called used tiles then use the for loop shown in video but have it append those positions in used tiles like this: for location in map: used tiles.append(location) then this next part is optional but if you dont want to have to place all the tiles in the editor plus itll give you variable room sizes if you want that for x in borders.size.x + 2: for y in borders.size.y + 2: var point = Vector2(x, y) tilemap.set_cell(0, point, 1, Vector2i(3, 3)) I used the fully walled or enclosed tile for the atlas coords because it has a -1 for all of its sides (dont really understand this part) and the plus twos are for the extra tiles so the tiles created dont go off the borders then ypu take the tiles you saved in used tiles, the iterate them in a for loop to erase tiles for the actual level for tile in used_tiles: tilemap.erase_cell(0, tile) then you take the used cells amd use the terrain connect method to connect them all var used_cells = tilemap.get_used_cells(0) tilemap.set_cells_terrain_connect(0, used_cells, 0, 0) this should give you the proper levels
Great stuff again Ben! I was just learning this subject myself atm and your tutorial came in perfectly :D Learned some cool stuff here that was giving me issues. Thanks!
GODOT 4.2.2 USERS: After 3 hours of reading comments and figuring out what exactly the code does, I came up with this to get the same results in the video: func generate_level(): var walker = Walker.new(Vector2(19, 11), borders) var map = walker.walk(500) walker.queue_free() for location in map: tileMap.erase_cell(0, location) await get_tree().create_timer(0.001).timeout # You can delete this, I like to see the level slowly generate. var used_tiles = tileMap.get_used_cells(0) for tile in used_tiles: tileMap.erase_cell(0, tile) tileMap.set_cells_terrain_connect(0, used_tiles, 0, 0) I learned that ".get_used_cells" specifically gives vector coordinates for anything that wasn't erased in the "for location in map" loop. I also learned that the code doesn't want to paint over existing used cells. So the used cells are stored in the variable "used_tiles" after the location loop. Those used cells are then erased in the "for tile in used_tiles" loop, and then redrawn at the very end in the "set_cells_terrain_connect" command. For folks interested in an inverse generator, which means starting with a blank tilemap on your world node: func generate_level(): var walker = Walker.new(Vector2(19, 11), borders) var map = walker.walk(500) walker.queue_free() tileMap.set_cells_terrain_connect(0, map, 0, 0) And the variation to see the walker walk: func generate_level(): var walker = Walker.new(Vector2(19, 11), borders) var map = walker.walk(500) walker.queue_free() for location in map: var walked = [] walked.append(location) tileMap.set_cells_terrain_connect(0, walked, 0, 0) await get_tree().create_timer(0.001).timeout
Awesome! I requested something like this some time ago. I haven't coded for a while but this inspires me to make an Rogue-lite, as I was working on one back in the day. Thanks for the video! If you make a series out of this, it would be awesome!
Nice tutorial, THX 🎉 I started to use a specific wording some years ago to prevent reading and typing errors. Arrays and lists just get pluralized by adding the suffix "List" So the List directions become direction_list this is quicker to read, even when you are tired because of a short night, a log day or you are on a game jam ;) And it is easier to work with non native englisch speaking people, who sometimes need to search for the right plural form of the word. is plural of person, persons? No, it is people. Oh right, my bad. No, it is a person_list 🤷♂️ and you can rename it much easier, when you realize, it's not a bunch of people, it's staff. And you prevent this one weird coworker to give variables funny names loke "Oh, its a bunch of files, I call it a folder" Oh and you can call your const ALLOWED_DIRECTION_LIST This is a longer word, but code completion to the rescue, we don't need to write the whole variable name ;) I personally would add the step_history.append() stuff to the step method, because, yoou COULD access the step method from some ware else in your code and would need to add this step (haha) again, to append it to the history
thank you for the tutorial! Will you consider a segment on putting monsters, biomes or other objects into the level? Most tutorials that talk about level generation leave you with instruction on how to build a randomly generated level, but with no idea on how to populate it. Thank you for all your hard work.
I would suggest getting rid of the habit of Array.shuffle() and pop, which is O(n) and instead do array[randi() % array.size()], which is O(1). And thanks for all your wonderful tutorials! ;-)
Hi HeartBeast, I would really appreciate it if you continue this series, I am trying to make a 2D sandbox game that uses random world generation but I don't know how to code that. Your videos rally helped me thanks.
CORRECTION: "const" in gdscript does not mean immutable. You can freely modify constant arrays/dictionaries. const-bound variables just can't be re-assigned (and must be initially assigned a literal constant expression). The error in the video was a result of DIRECTIONS becoming empty due to repeated erasure of elements.
First Thanks for making another procedural generation tutorial this is the exact thing I’ve been looking for. Your tutorials are awesome and I probably wouldn’t have started game development without your tutorials.
Nice! I am excited for this series, after this series can you possibly do a tutorial for a text based game? Anyways thank you for your amazing tutorials. Hope your channel gets bigger.
Idea for a next video on this series: using this method of generation to create a map for your actual level made of pre-made rooms. You could have pre-made rooms for corners, horizontal and vertical hallways, and open spaces. Maybe even make couple pre-mades for each "type" of room to increase the variety on the final product.
Hey, The bit about DIRECTIONS and constants is incorrect. You cannot change a reference to it (So you can't do DIRECTIONS = []), but you can change what it contains (pop_front and the like). The null error was because you remove an element from directions every time you call change_direction. If you print the value of DIRECTIONS and direction you get: directions [(1, 0), (0, -1), (-1, 0), (0, 1)] dir (-1, 0) directions [(0, -1), (0, 1)] dir (0, -1) directions [(0, 1)] dir (0, 1) directions [] dir Null Calling duplicating the array on every call ensures you don't eventually end up with an empty array.
I love your tutorials, youre so intelligent. I have one quick question tho, is it possible to include walls on the south sides of the auto tiled floors? Almost like a mountain maker.
What about the Hybrid Approach? For my game, I need to spawn Hand-made Scenes a specific distance apart (Spelunky style) as suppose to just generating the level automatically (Minecraft style). That hybrid approach is like giving the Level Designer a lot of pre-made Rooms in the form of actual TSCN files, and ze can also not physically place certain rooms next to other certain rooms, and that Level Designer is a computer algorithm.
Pretty sure your nill error was because directions has the same pointer as DIRECTIONS, so if you remove items from directions, then DIRECTIONS will end up empty as well. I'm not that familiar with gdscript so I can't be sure, but in most languages cosntants are pure for assigning (using the = sign), functions can still change them
Thank you so much for such tutorials. I have been following your rpg series and started development on my own game but I'm facing problem in dialog box for npcs, signs, etc., please make a video on that.
Hey heartbeast, Could you make an tutorial about items and a Inventory system. I've looked around how to make this but I am really struggling with it :(
How do you look at making series about "space invaders"-like game but more complex, like "chicken invaders" series? The hard part in this could be making enemies move in correct groups.
You make a giant rectangle out of tiles at first and than "carve" out the level-map. While i converted this project for godot 4, i noticed, that the new treeain properties, that replace autotile from previous versions, have one single function to place tiles and and connect them by the bithask: TileMap.set_tiles_terrain_connect() That wants an array of all positions, thar should get tiles of the given terrain-set, and also directly connects them to fit with their borders. So instead of drawing a giant rectangle of tiles, i created an array ("rectangle_map") of all these position-vectors in the code and in a seccond step i iterate through the map from the walker and erase all these vectors from the new rectangle_map. The result is now the array, that i pas into the new terrain-connect-function of the tilemap. So i also get a big rectanglo of tiles, where the level is a "hole" in it. Everything else was not a big problem to cinvert
This helped me so much to be honest. I first drew a rect in the 2D editor and edited the script to look like this: var walker : Walker = Walker.new(Vector2(19, 11), borders) var map = walker.walk(500) var all_cells : Array = tileMap.get_used_cells(0) tileMap.clear() walker.queue_free() var using_cells : Array = [] for cell in all_cells: if !map.has(Vector2(cell.x, cell.y)): using_cells.append(cell) tileMap.set_cells_terrain_connect(0, using_cells, 0, 0, false) I get all the tiles in the array "all_cells" it was easiest to create it this way.
henlo. can you teach us how we can interact with like the game environment like chests, npcs, coins... like how we can expand on your rpg tutorial? thanks...
I'm getting an error with the tileMap.set_cellv(direction, -1) command. It's saying set_cellv() is a nonexistant function. Like it's not reading the tileMap node as a $TileMap. It says tileMap is [null]. It doesn't seem to be registering "update_bitmask_region" either. I checked the tileMap variable (set to $TileMap just like in the tutorial). Not sure what the issue is or if I have to copy paste the code here.
If you already know another engine well, and you enjoy using it, and that engine has all the features you want, no. But if Godot has what you need, or you don't have a particular attachment to another engine, then yes.
When I try to instance a player in the demo you uploaded, I get the message:"Script inherits from native type 'KinematicBody2D', so it can't be instanced in object of type: 'Node2D'" in my player's GDScript. I am not sure as to why these are interfering.