Johnny Bigoode
Johnny Bigoode

Reputation: 591

Player doesn't spawn correctly in procedural generated map

I've followed "Procedural Generation in Godot: Dungeon Generation" by KidsCanCode @https://www.youtube.com/watch?v=o3fwlk1NI-w and find myself unable to debug the current problem.

This specific commit has the code, but I'll try to explain in more detail bellow.

My main scene has a Camera2D node, a generic Node2D calles Rooms and a TileMap, everything is empty.

When the script starts, it runs a

func make_room(_pos, _size):
    position = _pos
    size = _size
    
    var s = RectangleShape2D.new()
    s.custom_solver_bias = 0.5
    s.extents = size
    $CollisionShape2D.shape = s

A few times and it fills $Rooms using .add_child(r) where r is a instance of the node that has the make_room() function. It will then iterate over $Rooms.get_children() a few times to create a AStar node to link all the rooms:

Map before carving into tilemap

The magic comes when make_map() is called after afterwards, it fills the map with non-walkable blocks and then it carves the empty spaces, which works fine too:

Map after carvinho into tilemap

There is a find_start_room() that is called to find the initial room, it also sets a global variable to the Main script start_room, which is used to write 'Start' on the map using draw_string(font, start_room.position - Vector2(125,0),"start",Color(3,4,8))

When I hit 'esc' it runs this simple code to instance the player:

player = Player.instance()
add_child(player)
player.position = start_room.position + Vector2(start_room.size.x/2, start_room.size.y/2)
play_mode = true

Player spawning on impossible terrain

The problem comes when spawning the player. I tried doing some 'blind' fixing, such as adding or subtracting a Vector2(start_room.size.x/2, start_room.size.y/2) to player.position to see if I could make it fall within the room, to no avail.

Turning to the debugger didn't help, as the positions expressed by the variable inspectors don't seem to mean anything.

I tried implementing a simple 'mouse click print location':

    print("Mouse Click/Unclick at: ", event.position)
    print("Node thing",get_node("/root/Main/TileMap").world_to_map(event.position))

And also a 'start_room' print location:

print(get_node("/root/Main/TileMap").world_to_map(start_room.position))

And a when player moves print location, written directly into the Character script:

print(get_node("/root/Main/TileMap").world_to_map(self.position))

Getting results like the ones bellow:

Mouse Click/Unclick at: (518, 293)
Node thing(16, 9)
(-142, 0)
(-147, -3)

So, the player doesn't spawn on the same position as the start_room and the mouse position information is not the same as anything else.

Why is the player now spawning correctly? How can I debug this situation?

EDIT1: User Theraot mentioned about how the RigidBody2D is doing some weird collisions, and from what I understood, changing their collision behavior should fix the whole thing.

There's a section on the code that -after generating the random rooms- it removes some of the rooms like this:

for room in $Rooms.get_children():
    if randf() < cull:
        room.queue_free()
    else:
        room.mode = RigidBody2D.MODE_STATIC
        room_positions.append(Vector3(room.position.x, room.position.y, 0))

From what I understand, if the room is randomly selected it will be deleted using queue_free() OR it will be appended to a room_positions for further processing. This means if I shift all the rooms to a different collision layer, the player/character instance would be alone with the TileMap on the same collision layer.

So I just added a simple room.collision_layer = 3 changing this section of the code to

for room in $Rooms.get_children():
    if randf() < cull:
        room.queue_free()
    else:
        room.mode = RigidBody2D.MODE_STATIC
        room.collision_layer = 3
        room_positions.append(Vector3(room.position.x, room.position.y, 0))

It doesn't seem to have changed anything, the player still spawns outside the room.

Upvotes: 1

Views: 401

Answers (1)

Theraot
Theraot

Reputation: 40220

Do you see the rooms spread outwards?

You didn't write code to move the rooms. Sure, the code gives them a random position. But even if you set their position to Vector2.ZERO they move outwards, avoiding overlaps.

Why? Because these rooms are RigidBody2D, and they do push other physics objects. Such as other rooms or the player character.


That's the problem: These rooms are RigidBody2D, and you put your KinematicBody2D player character on top of one of them. The RigidBody2D pushes it out.


The tutorial you followed is exploiting this behavior of RigidBody2Ds to spread the rooms. However you don't need these RigidBody2D after you are done populating your TileMap.

Instead, you can store the start position in a variable for later placing the player character (you don't need offsets - by the way - the position of the room is the center of the room), and then remove the RigidBody2Ds. If you want to keep the code that writes the text, you would also have to modify it, so it does not fail when the room no longer exists.

Alternatively, you can edit their collision layer and mask so they don't collide with the player character (or anything for that matter, but why would you want these RigidBody2Ds that collide with nothing?).


Addendum post edit: Collision layers and mask don't work as you expect.

First of all, the collision layer and mask are flags. The values of the layers are powers of two (1, 2, 4, 8...). So, when you set it to 3, it is the layer 1 plus the layer 2. So it still collides with a collision mask of 1.

And second, even if you changed the collision layer of the rooms to 2 (so it does not match the collision mask of 1 that the player character has). The player character still has a layer 1 which match the collision mask of the rooms.

See also the proposal Make physics layers and masks logic simple and consistent.

Thus, you would need to change the layer and mask. Both. in such way that they don't collide. For example, you can set layer and mask to 0 (which disable all collisions). The algorithm that populates the TileMap does not use the layer and mask.

Upvotes: 1

Related Questions