© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
C. PittProcedural Generation in Godothttps://doi.org/10.1007/978-1-4842-8795-8_9

9. Collective Nodes in Generated Maps

Christopher Pitt1  
(1)
Durbanville, South Africa
 

I don’t know if you’ve noticed, but all our pixel-art-to-level code converts individual pixel into individual tiles or nodes. We’ve yet to take clusters of the same color pixels and convert them into a single complex node.

That's what we're going to tackle in this chapter. It's a trick that is going to come in handy for the next game we make. I want to take a quick detour to talk about the different approaches and skills we’ll need to be able to do this.

Refreshing Our Memory

Do you remember the code that we used to draw nodes from pixel art?

This is from Chapter 5.
for row in layout:
        for cell in row:
            if tiles.has(cell):
                _tiles.set_cell(0, Vector2i(x, y), 0, tiles[cell])
            if nodes.has(cell):
                var new_node = nodes[cell].instantiate()
                _nodes.add_child(new_node)
                new_node.position = Vector2(x * 16, y * 16)

This code shows the 1:1 relationship between pixels in the layout image and the nodes or tiles they represent in a map. I want us to develop this idea further.

Let’s create a new experiment. We’ll call this one CollectiveNodesExperiment. Be sure to set it as the experiment that loads on PlayScreen.

To this, we’ll add a couple OptionButton nodes for width and height. We could use LineEdit nodes, but that would allow invalid inputs. I think it’s better to stick with the safer list of allowed values:

A screenshot of the scene window on the left with the entire collective nodes experiment within a box is highlighted. The collective nodes experiment window, which has width and height options, is on the right.

Creating UI for our experiment

I’ve gone ahead and set minimum widths on the HBoxContainer, VBoxContainer, and ColorRect nodes. Being descendants of a Node2D, they cannot assume their size so they begin at 0px width and 0px height.

I’ve added the following units to the OptionButton nodes, but you can add different ones if you prefer:
  • 1 unit

  • 2 units

  • 3 units

You can think of these values as they relate to the pixels of whatever pixel art image we are drawing from. We’re not actually going to attach these to an image, but you should be well familiar with how that works by now.

As either of these OptionButton nodes changes, we can call a render method, which can switch out the visible nodes:

This is from experiments/collective_nodes_experiment.gd
extends GameExperiment
var width := 1
var height := 1
func _ready() -> void:
    render()
func _on_width_option_button_item_selected(index: int) -> void:
    width = index + 1
    render()
func _on_height_option_button_item_selected(index: int) -> void:
    height = index + 1
    render()
func render() -> void:
    print(str(width) + " wide, " + str(height) + " high")

Don’t forget to link these methods up to their respective nodes:

A screenshot of the node window. The function on the height option button item under the options button is highlighted.

Connecting OptionButton signals

Now, when either of the OptionButton nodes changes, we should see a debug message describing the intended width and height.

Selecting the Appropriate Node(s)

The basic idea behind a node like this is that we want to show or hide sprites and colliders based on how high and wide the node should be. We’re actually going to have many possible variations but only display one that fits the intended size.

Let’s create a few variations:

A screenshot of the scene window. Various tile maps are mentioned under the colorRect node.

Tile map variations

The configuration I’ve chosen is a bunch of Node2D nodes, nested below the ColorRect node. Each contains possible variations matching the name of their parent.

So the TileMap nodes below 1x3 would all be one unit wide and three units high. They might have a different visual style and colliders; but they fit within the size dictated by their parent Node2D.

We can select from among these by composing the name of the intended Node2D:

This is from experiments/collective_nodes_experiment.gd
@onready var _color_rect := $HBoxContainer/ColorRect as ColorRect
func render() -> void:
    for group in _color_rect.get_children():
        for child in (group as Node2D).get_children():
            (child as TileMap).visible = false
    var intended_name := str(width) + "x" + str(height)
    var intended_node := _color_rect.get_node(intended_name)
    var index = randi() % intended_node.get_child_count()
    (intended_node.get_child(index) as TileMap).visible = true
We could achieve this in many different ways:
  1. 1.

    Giving every TileMap a group of the composite name, like tile_1x1, and finding all nodes within the same group to select from

     
  2. 2.

    Adding all TileMap nodes to an exported array of node paths

     

The bottom line is that we’re achieving a level of randomization with the constraints of an intended width and height.

This will allow us to tell a “house” node how wide and high we want it to be. It'll have a fixed size based on a cluster of pixels in our pixel art layout image, but still be somewhat random.

We don’t have to stick to using TileMap nodes, either. We’ve spent an equal amount of time learning about nodes and colliders so we could show or hide node trees. You can choose the approach you prefer, or mix and match.

Summary

I hope this short chapter has given you a little palette cleanse before we dive into recreating another game.

In the next chapter, we’re going to use the navigation and collective nodes to make something even more intricate than Bouncy Cars.

If you’re looking for more of a challenge, try to change the drawing code, from Chapter 4 or 5 or 7, to account for clusters of pixels. We’ll see what this code looks like in the next chapter, so don’t stress if you can’t figure it out immediately.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset