+1

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

Ziva editing Godot scenes, generating 3D models, painting TileMaps

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

Ziva đọc và chỉnh sửa scene tree của Godot

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

Ziva vẽ TileMap bên trong Godot

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí