TileMapLayer (Godot 4.3+)
Godot 4.3 新增了 TileMapLayer 節點,並棄用了 TileMap。每個 TileMapLayer 代表一個圖層,因此一個含有地面、牆壁與裝飾的關卡,會由三個共享同一個 TileSet 的兄弟 TileMapLayer 節點組成。請勿針對 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 中新增自訂資料圖層(檢查器 > 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 觸碰了哪個圖塊,請使用碰撞點。除非 physics_quadrant_size 為 1(Godot 4.5+ 預設會將最多 16 x 16 個細胞合併為一個碰撞體),否則 get_coords_for_body_rid() 回傳的是物理象限的座標,而非細胞的座標:
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 底部面板,點選工具箱圖示並選擇「Extract TileMap layers as individual TileMapLayer nodes」。然後更新腳本:所有原本需要 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。