首页 > 解决方案 > 玩家无法在程序生成的地图中正确生成

问题描述

我关注了 KidsCanCode @https://www.youtube.com/watch?v=o3fwlk1NI-w 的“Godot 中的程序生成:地牢生成”,发现自己无法调试当前问题。

这个特定的提交有代码,但我会在下面尝试更详细地解释。

我的主要场景有一个 Camera2D 节点,一个通用的 Node2D 调用 Rooms 和一个 TileMap,一切都是空的。

当脚本启动时,它运行一个

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

几次,它$Rooms使用.add_child(r)wherer是具有该make_room()功能的节点的一个实例来填充。然后它将迭代$Rooms.get_children()几次以创建一个AStar节点来链接所有房间:

雕刻成tilemap之前的地图

之后被调用时,魔法就来make_map()了,它用不可行走的块填充地图,然后雕刻空白空间,这也很好用:

卡维尼奥之后的地图进入瓷砖地图

有一个find_start_room()被调用来找到初始房间,它还为主脚本设置了一个全局变量start_room,用于在地图上写“开始”使用draw_string(font, start_room.position - Vector2(125,0),"start",Color(3,4,8))

当我点击“esc”时,它运行这个简单的代码来实例化播放器:

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

玩家在不可能的地形上产卵

产生播放器时出现问题。我尝试进行一些“盲目”修复,例如添加或减去Vector2(start_room.size.x/2, start_room.size.y/2)player.position 以查看是否可以使其落在房间内,但无济于事。

转向调试器并没有帮助,因为变量检查器表达的位置似乎没有任何意义。

我尝试实现一个简单的“鼠标点击打印位置”:

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

还有一个“start_room”打印位置:

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

还有一个当玩家移动打印位置时,直接写入角色脚本:

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

得到如下结果:

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

因此,玩家不会在与 start_room 相同的位置生成,并且鼠标位置信息与其他任何信息都不相同。

为什么玩家现在正确生成?我该如何调试这种情况?

EDIT1:用户 Theraot 提到了 RigidBody2D 如何进行一些奇怪的碰撞,据我所知,改变它们的碰撞行为应该可以解决整个问题。

代码中有一段 - 在生成随机房间后 - 它会删除一些这样的房间:

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))

据我了解,如果房间是随机选择的,它将使用queue_free()OR 删除,它将被附加到 aroom_positions以进行进一步处理。这意味着如果我将所有房间转移到不同的碰撞层,玩家/角色实例将与同一碰撞层上的 TileMap 单独存在。

所以我只是添加了一个简单room.collision_layer = 3的将这部分代码更改为

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))

它似乎没有改变任何东西,玩家仍然在房间外产生。

标签: game-developmentgodotprocedural-generationgdscript

解决方案


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.


推荐阅读