TileMapLayer (Godot 4.3+)
Godot 4.3 添加了 TileMapLayer 节点并弃用了 TileMap。每个 TileMapLayer 是一个图层,因此一个包含地面、墙壁和装饰的关卡是三个同级 TileMapLayer 节点共享一个 TileSet。不要针对 TileMap 编写新代码,也永远不要使用 Godot 3 的 API(set_cellv、world_to_map、set_cell(x, y, tile_id))。
单元格 API
单元格由整数地图坐标(Vector2i)标识。一个图块由三个值标识:
source_id:TileSet中源的 ID(图集纹理或场景集合)。第一个图集通常为0;请检查 TileSet 编辑器或TileSet.add_source()的返回值。atlas_coords:图块在图集中的列和行(以图块为单位,非像素)。alternative_tile:除非您创建了翻转或旋转的替代图块,否则为0。
extends TileMapLayer
const SOURCE_ID := 0
const GRASS := Vector2i(0, 0)
const WALL := Vector2i(1, 0)
func _ready() -> void:
for x in range(10):
set_cell(Vector2i(x, 5), SOURCE_ID, GRASS)
set_cell(Vector2i(3, 4), SOURCE_ID, WALL)
var source_id := get_cell_source_id(Vector2i(3, 4)) # 单元格为空时返回 -1
var atlas := get_cell_atlas_coords(Vector2i(3, 4)) # 为空时返回 Vector2i(-1, -1)
print(source_id, atlas)
erase_cell(Vector2i(3, 4))
print(get_used_cells().size(), " 个单元格,范围 ", get_used_rect())
func cell_at_global(global_point: Vector2) -> Vector2i:
return local_to_map(to_local(global_point))
func center_of_cell(coords: Vector2i) -> Vector2:
# map_to_local 返回该图层局部空间中单元格的中心。
return to_global(map_to_local(coords))
重要事实:
- 使用默认参数调用
set_cell(coords)会擦除该单元格(source_id默认为-1)。放置图块时始终要传递source_id和atlas_coords。 - 设置图集中不存在的
atlas_coords会存储该单元格而不报错(get_cell_source_id()返回源),但之后调用get_cell_tile_data()会记录The TileSetAtlasSource atlas has no tile at (x, y).并返回null。 local_to_map()和map_to_local()在图层的局部空间中工作。当图层被移动或缩放时,请使用to_local()/to_global()进行转换。get_used_cells_by_id(source_id, atlas_coords)查找使用某个图块的所有单元格。- 更改会批量处理到帧末尾。如果希望碰撞或场景图块立即更新,请调用
update_internals()。
每个图块的自定义数据
在 TileSet 中添加自定义数据层(Inspector > TileSet > Custom Data Layers,例如类型为 int 的 damage),在 TileSet 编辑器中绘制值,然后读取:
extends TileMapLayer
func damage_at(coords: Vector2i) -> int:
var data := get_cell_tile_data(coords)
if data == null:
return 0
return data.get_custom_data("damage")
get_cell_tile_data() 对于空单元格和场景集合图块返回 null,因此调用其方法前请进行检查。
地形(自动拼接)
Godot 3 的自动图块位掩码已转为地形集合。在 TileSet 中,添加地形集合和地形,然后在 TileSet 编辑器中绘制每个图块的地形衔接位。在代码中,传入需要填充的每个单元格:
extends TileMapLayer
const TERRAIN_SET := 0
const DIRT := 0
func paint_room(rect: Rect2i) -> void:
var cells: Array[Vector2i] = []
for x in range(rect.position.x, rect.end.x):
for y in range(rect.position.y, rect.end.y):
cells.append(Vector2i(x, y))
set_cells_terrain_connect(cells, TERRAIN_SET, DIRT)
set_cells_terrain_connect() 选择图块以使边缘连接;set_cells_terrain_path() 用于绘制路径形状的道路和河流。如果结果显示图块错误或缺失,说明 TileSet 中的衔接位不完整;代码无法修复此问题。
碰撞
只有 TileSet 具有物理层(TileSet 检查器 > Physics Layers > Add Element)且图块在该层上具有碰撞多边形(TileSet 编辑器,选择图块,Physics > Physics Layer 0;按 F 键可添加一个满图块矩形)时,图块才会发生碰撞。图层的 collision_enabled 属性默认开启。物理层还带有 collision_layer 和 collision_mask,这些在 TileSet 上设置,而不是在节点上。
要查找 CharacterBody2D 接触了哪个图块,请使用碰撞点。get_coords_for_body_rid() 返回物理象限的坐标,而不是单元格的坐标,除非 physics_quadrant_size 为 1(Godot 4.5+ 默认将最多 16x16 个单元格合并为一个物体):
extends CharacterBody2D
@export var level: TileMapLayer
func _physics_process(delta: float) -> void:
velocity += get_gravity() * delta
move_and_slide()
for i in get_slide_collision_count():
var collision := get_slide_collision(i)
if collision.get_collider() == level:
# 稍微进入图块内部,因为接触点在其边缘上。
var inside := collision.get_position() - collision.get_normal()
var coords := level.local_to_map(level.to_local(inside))
print("接触图块 ", coords)
在代码中构建 TileSet
适用于程序化关卡和无法使用 TileSet 编辑器的测试。碰撞多边形点相对于图块中心。
extends Node2D
func _ready() -> void:
var tile_set := TileSet.new()
tile_set.tile_size = Vector2i(16, 16)
tile_set.add_physics_layer()
var source := TileSetAtlasSource.new()
source.texture = load("res://tiles.png") as Texture2D
source.texture_region_size = Vector2i(16, 16)
source.create_tile(Vector2i(0, 0))
var source_id := tile_set.add_source(source)
var data := source.get_tile_data(Vector2i(0, 0), 0)
data.add_collision_polygon(0)
data.set_collision_polygon_points(0, 0, PackedVector2Array([
Vector2(-8, -8), Vector2(8, -8), Vector2(8, 8), Vector2(-8, 8),
]))
var layer := TileMapLayer.new()
layer.tile_set = tile_set
add_child(layer)
layer.set_cell(Vector2i(0, 0), source_id, Vector2i(0, 0))
从 TileMap 迁移
在编辑器中:选择 TileMap,打开 TileMap 底部面板,点击工具箱图标并选择“将 TileMap 图层提取为独立的 TileMapLayer 节点”。然后更新脚本:每个接受 layer 索引的 TileMap 方法都将变为该图层节点上的方法,无需索引。
| 旧代码 | TileMapLayer |
|---|---|
tile_map.set_cell(0, coords, source_id, atlas_coords) | ground.set_cell(coords, source_id, atlas_coords) |
tile_map.get_cell_source_id(1, coords) | walls.get_cell_source_id(coords) |
tile_map.get_used_cells(0) | ground.get_used_cells() |
tile_map.set_layer_enabled(2, false) | decoration.enabled = false |
tile_map.set_layer_z_index(1, 5) | walls.z_index = 5 |
Godot 3 set_cellv(pos, id), world_to_map(p), map_to_world(c) | set_cell(coords, source_id, atlas_coords), local_to_map(p), map_to_local(c) |
常见错误
- 将像素位置传递给
set_cell()。请先使用local_to_map()转换。 - 用单个数字调用
set_cell(coords, tile_index):第二个参数是源 ID,而atlas_coords默认为Vector2i(-1, -1),这会擦除单元格。 - 期望从没有物理层的 TileSet 或没有绘制多边形的图块产生碰撞。
- 使用默认
physics_quadrant_size时调用get_coords_for_body_rid()得到的是象限坐标。 - 未进行
null检查就调用get_cell_tile_data()的方法。 - 因为较旧的教程使用了
TileMap节点而继续用于新工作。每个图层请使用一个TileMapLayer。