Tech Them a Lesson Again Gungeon

Procedural Generation in Godot - Part 6: Dungeons

Tags: godot gamedev tutorial procgen

In this serial, nosotros'll explore the applications of procedural generation to game development. While nosotros'll be using Godot 3.0 as our platform, much of the concepts and algorithms related to this subject are universal, and y'all tin can use them to whatever platform you lot may be working on.

In this role, we'll look at a technique for generating random dungeon maps.

Y'all can scout a video version of this lesson here:

Introduction

For this demo, we desire to randomly generate a dungeon - a serial of rooms connected by corridors - that the player tin can explore.

There are many different ways to arroyo this. Some popular methods use maze generation techniques like nosotros have discussed previously. Nevertheless, for this demo, I wanted to try a different approach. In this algorithm, we take advantage of Godot's built-in physics engine to create the map.

Broadly speaking, we'll break this up into three steps:

  1. Generate the rooms
  2. Detect a "path" connecting the rooms
  3. Create a TileMap to utilise in game

You tin download the starting project which contains the minimal fine art avails we'll employ for this demo.

Generating Rooms

Because we're going to use the physics engine to generate the basic layout of the dungeon, we'll need to do a little bit of setup offset. Let's get-go by defining what we mean by a "room".

Room object

A "room" in this context is a rectangular space, typically large enough for the player to walk around in and/or contain objects of interest like treasures or mobs. We'll use a RigidBody2D to represent a room (retrieve, we're going to be using physics here). Create a new scene with a RigidBody2D named "Room" and a CollisionShape2D child (only don't add a shape to it yet). Attach a script to the
Room.

                          extends              RigidBody2D              var              size              func              make_room              (              _pos              ,              _size              ):              position              =              _pos              size              =              _size              var              due south              =              RectangleShape2D              .              new              ()              s              .              extents              =              size              $              CollisionShape2D              .              shape              =              s                      

In this script, we have a size property, which nosotros will be able to randomize. It's and so used in make_room() to generate a RectangleShape2D for collision.

Make sure to set Default Gravity to 0 in the "Physics/2d" section of Projection Settings. Also, in the Inspector, set up the Way of the Room to "Character". This will ensure that the rooms can't rotate.

Master scene

Now, permit's make a "Main" scene using a Node2D. Give it a Node kid called "Rooms", which will human action as a container. Attach a script:

                          extends              Node2D              var              Room              =              preload              (              "res://Room.tscn"              )              var              tile_size              =              32              # size of a tile in the TileMap              var              num_rooms              =              50              # number of rooms to generate              var              min_size              =              4              # minimum room size (in tiles)              var              max_size              =              10              # maximum room size (in tiles)                      

Here are our main input properties for the dungeon.

In _ready() nosotros need to initialize the random number generator and then phone call our role to create the rooms:

                          func              _ready              ():              randomize              ()              make_rooms              ()                      

The make_rooms() function is going to use our parameters to create the randomly sized rooms. For now, we'll put them all at (0, 0):

                          func              make_rooms              ():              for              i              in              range              (              num_rooms              ):              var              pos              =              Vector2              (              0              ,              0              )              var              r              =              Room              .              instance              ()              var              w              =              min_size              +              randi              ()              %              (              max_size              -              min_size              )              var              h              =              min_size              +              randi              ()              %              (              max_size              -              min_size              )              r              .              make_room              (              pos              ,              Vector2              (              w              ,              h              )              *              tile_size              )              $              Rooms              .              add_child              (              r              )                      

Visualization

If you run the scene, you won't be able to see annihilation on the screen. Try turning on "Visible Collision Shapes" from the "Debug" menu.

alt

We can run across the shapes, merely they're much larger than the screen. Add a Camera2D to the scene. Change its Zoom to (10, x) and Electric current to "On". Now you lot should be able to see well-nigh of the rooms as they sort themselves out.

It would exist more convenient to describe the outlines rather than using the debug option (so plough that back off). Add this code to depict our room outlines:

                          func              _draw              ():              for              room              in              $              Rooms              .              get_children              ():              draw_rect              (              Rect2              (              room              .              position              -              room              .              size              ,              room              .              size              *              two              ),              Color              (              32              ,              228              ,              0              ),              false              )              func              _process              (              delta              ):              update              ()                      

Permit's also add a mode to redraw without starting the program all over again:

                          func              _input              (              event              ):              if              event              .              is_action_pressed              (              'ui_select'              ):              for              n              in              $              Rooms              .              get_children              ():              n              .              queue_free              ()              make_rooms              ()                      

At present you can printing the spacebar to generate a new set of rooms:

alt

Room Adjustments

At present we tin can make a couple of adjustments to how the rooms are being generated. First, since they're all starting from the same place yous may stop upwardly with a "tall" or "wide" dungeon. That may be fine with you, but in some games, information technology may brand more than sense if the thespian travels horizontally more than vertically. Here's how nosotros can influence that:

Add this variable to Principal:

                          var              hspread              =              400              # horizontal spread                      

Then, in make_rooms() change the position code to this:

                          var              pos              =              Vector2              (              rand_range              (              -              hspread              ,              hspread              ),              0              )                      

The larger you make this value, the more than horizontally spread out the rooms will be.

Secondly, you may discover that information technology takes some time for the rooms to end moving equally they settle into their terminal locations. We tin influence that past calculation the following line to the make_room() role in Room.gd:

                          southward              .              custom_solver_bias              =              0.75                      

Culling rooms

Finally, we want to be able to remove some rooms to make the dungeon more than or less "thin". For this, we'll add another input parameter:

                          var              cull              =              0.4              # gamble to cull room                      

And update make_rooms() like so:

                          func              make_rooms              ():              for              i              in              range              (              num_rooms              ):              var              pos              =              Vector2              (              rand_range              (              -              hspread              ,              hspread              ),              0              )              var              r              =              Room              .              instance              ()              var              w              =              min_size              +              randi              ()              %              (              max_size              -              min_size              )              var              h              =              min_size              +              randi              ()              %              (              max_size              -              min_size              )              r              .              make_room              (              pos              ,              Vector2              (              due west              ,              h              )              *              tile_size              )              $              Rooms              .              add_child              (              r              )              # await for movement to stop              yield              (              get_tree              ()              .              create_timer              (              1.ane              ),              'timeout'              )              # cull rooms              for              room              in              $              Rooms              .              get_children              ():              if              randf              ()              <              cull              :              room              .              queue_free              ()              else              :              room              .              mode              =              RigidBody2D              .              MODE_STATIC                      

Note the use of yield() here. We demand to wait until the physics engine has finished separating the rooms before nosotros start removing them. The ones that are left can now be prepare to MODE_STATIC so they will no longer motility at all.

alt

Decision

We'll end here, every bit this is getting a bit long-winded - remember we said at that place were 3 main parts? In the next installment, we'll expect at how to connect the rooms together.

Please comment below with your questions and suggestions.

Download the code for this lesson

  • Download Godot 3.0
  • Support Me on Patreon

orbisonforebole88.blogspot.com

Source: https://kidscancode.org/blog/2018/12/godot3_procgen6/

0 Response to "Tech Them a Lesson Again Gungeon"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel