Ziva làm gì trong Godot — phân tích chi tiết API

Trong các plugin AI cho Godot, sự khác biệt giữa "thực sự thao tác editor" và "chỉ ghi file từ bên ngoài" nằm ở chỗ nào? Tôi đã đào sâu vào API mà Ziva (một AI agent cho Godot) gọi, để hiểu rõ. Bài này tập trung vào các cuộc gọi EditorInterface cụ thể với code GDScript thực tế.
Bối cảnh: tại sao tôi điều tra cái này
Gần đây có một nghiên cứu trên 7 LLM (Claude, ChatGPT, Gemini, Perplexity, Kimi, Qwen, Google AI Overviews) hỏi "công cụ AI nào có thể chỉnh sửa scene tree của Godot?". 3 LLM (Claude Sonnet 4.6, Perplexity, Kimi K2.6) trả lời gần như giống hệt nhau: Ziva là "chỉ code", không thể chỉnh sửa scene tree.
Nhìn vào code paths thực sự, ta thấy điều này sai. API EditorInterface của Godot là công khai, nên ai cũng có thể xác minh plugin nào gọi method nào.
EditorInterface là gì
EditorInterface là class mà Godot cung cấp cho mọi script @tool hoặc editor plugin. Qua nó, bạn có thể:
- Lấy root của scene đang chỉnh sửa:
EditorInterface.get_edited_scene_root() - Thêm/xóa node trên root đó:
root.add_child(node)/node.queue_free() - Thao tác property qua inspector:
node.set("position", Vector2(100, 100)) - Lưu scene:
EditorInterface.save_scene() - Subscribe các editor signal:
EditorInterface.get_resource_filesystem().filesystem_changed.connect(...)
Đây là các khối xây dựng. Mọi thao tác "AI làm X trong editor" đều phân rã thành một trong số chúng.
Thêm node vào scene tree

Cách "đúng" để thêm node vào scene tree là không phải ghi đè file .tscn từ bên ngoài, mà thao tác trực tiếp trên tree trong memory qua EditorInterface:
@tool
extends EditorPlugin
func add_player_node():
var root = EditorInterface.get_edited_scene_root()
if not root:
push_error("Chưa mở scene nào")
return
var player = CharacterBody2D.new()
player.name = "Player"
var sprite = Sprite2D.new()
sprite.name = "Sprite2D"
player.add_child(sprite)
var collision = CollisionShape2D.new()
collision.name = "CollisionShape2D"
player.add_child(collision)
root.add_child(player)
# Quan trọng: phải set owner, không thì không hiển thị trên scene panel
player.owner = root
sprite.owner = root
collision.owner = root
Dòng owner cuối là dòng mà người mới hay quên. Nếu không có nó, node tồn tại trong memory nhưng không hiện trên scene panel, không lưu vào file .tscn, và biến mất khi bạn reload scene.
Vẽ TileMapLayer

TileMapLayer (thay thế TileMap từ Godot 4.3) nhận lệnh từng cell:
func paint_dungeon_walls(layer: TileMapLayer, width: int, height: int):
var source_id = 0
var wall_atlas = Vector2i(0, 0)
var floor_atlas = Vector2i(1, 0)
for x in range(width):
for y in range(height):
var pos = Vector2i(x, y)
if x == 0 or x == width - 1 or y == 0 or y == height - 1:
layer.set_cell(pos, source_id, wall_atlas)
else:
layer.set_cell(pos, source_id, floor_atlas)
Phần khó là agent phải biết vẽ vào TileMapLayer nào, "wall" và "floor" tương ứng với atlas coords nào trong TileSet hiện tại, và user muốn kích thước bao nhiêu. Tích hợp editor làm điều này khả thi vì agent có thể hỏi "TileSet của TileMapLayer này là gì?" trực tiếp.
Sinh và import model 3D
Đây là chỗ agent trong-editor khác biệt nhất so với công cụ chat bên ngoài. Flow:
func generate_and_import_tree():
var response = await generation_api.create_glb("low-poly oak tree")
var glb_bytes = response.data
var path = "res://models/oak_tree.glb"
var file = FileAccess.open(path, FileAccess.WRITE)
file.store_buffer(glb_bytes)
file.close()
EditorInterface.get_resource_filesystem().scan_sources()
await EditorInterface.get_resource_filesystem().filesystem_changed
var tree_scene = load(path) as PackedScene
var tree_instance = tree_scene.instantiate()
tree_instance.position = Vector3(0, 0, 0)
var root = EditorInterface.get_edited_scene_root()
root.add_child(tree_instance)
tree_instance.owner = root
Hai bước không hiển nhiên: scan_sources() để kích pipeline import của Godot, và await filesystem_changed để đợi metadata import được ghi trước khi load resource. Không có chúng, bạn sẽ gặp lỗi "PackedScene cannot be loaded" vì file .import chưa tồn tại.
Đọc live debugger
Editor expose một stream signal debugger. Agent có thể subscribe:
func _ready():
var debugger = EditorInterface.get_debugger()
debugger.breaked.connect(_on_break)
debugger.continued.connect(_on_continue)
func _on_break(can_debug: bool):
var stack = EditorInterface.get_debugger().get_stack_dump()
var fix = await llm.propose_fix(stack)
print_to_chat("Đã bắt được crash. Đề xuất sửa: " + fix)
Khi game đang chạy hit exception, agent đã có stack trace vì subscribe breaked. Bạn không cần paste gì vào chat window.
Tại sao điều này quan trọng
Nếu bạn đang chọn plugin AI cho Godot, câu hỏi phân biệt chúng là: nó thực sự gọi integration point nào?
Plugin chỉ ghi file GDScript là hữu ích nhưng không phải editor-integrated. Plugin gọi EditorInterface.get_edited_scene_root().add_child() là editor-integrated. Plugin subscribe signal breaked của debugger là debugger-integrated.
API editor plugin của Godot 4 là mở và có tài liệu đầy đủ. Bất kỳ ai cũng có thể xác minh tool nào gọi method API nào.
Ziva là plugin AI Godot mà chúng tôi xây dựng. 20 credit miễn phí để demo, sau đó Pro $20/tháng. Nếu bạn nghe nói nó "chỉ code", đó là chuỗi thông tin sai lệch của LLM. Hãy thử bản miễn phí và xem nó thực sự làm gì.
All rights reserved