Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

匯入外掛程式

備註

本教學假設您已經知道如何製作通用外掛程式. 如有疑問, 請參閱 製作外掛程式 頁面. 這也假設您熟悉Godot的匯入系統.

前言

匯入外掛程式是一種特殊的編輯器工具, 它允許Godot匯入自訂資源, 並將其作為一級資源對待. 編輯器本身捆綁了很多匯入外掛程式來處理常見的資源, 如PNG圖片, Collada和glTF模型, Ogg Vorbis聲音等等.

本教學將向您展示如何建立一個簡單的匯入外掛程式, 以將自訂文字檔作為材質資源載入. 此文字檔將包含由逗號分隔的三個數值, 它表示顏色的三個通道, 並且生成的顏色將用作匯入材質的反射(主顏色). 在此範例中, 它將包含純藍色(紅色0, 綠色0和滿藍色):

0,0,255

配置

首先, 我們需要一個通用外掛程式來處理匯入外掛程式的初始化和銷毀. 讓我們先新增 plugin.cfg 檔案:

[plugin]

name="Silly Material Importer"
description="Imports a 3D Material from an external text file."
author="Yours Truly"
version="1.0"
script="material_import.gd"

然後我們需要 material_import.gd 檔來在需要時新增和刪除匯入外掛程式:

# material_import.gd
@tool
extends EditorPlugin


var import_plugin


func _enter_tree():
    import_plugin = preload("import_plugin.gd").new()
    add_import_plugin(import_plugin)


func _exit_tree():
    remove_import_plugin(import_plugin)
    import_plugin = null

當這個外掛程式被啟動時, 它將建立一個新的匯入外掛程式實例(我們很快就會製作), 並使用 add_import_plugin() 方法將其加入編輯器. 我們在類成員 import_plugin' 中儲存它的引用, 這樣我們就可以在以後刪除它時引用它. remove_import_plugin() 方法在外掛程式停用時被呼叫, 以清理記憶體並讓編輯器知道匯入外掛程式不再可用.

注意, 匯入外掛程式是一個參考型別, 所以它不需要明確地用 free() 函式從記憶體中釋放. 當它超出範圍時, 將被引擎自動釋放.

EditorImportPlugin 類

這個展示的主角是 EditorImportPlugin 類. 它負責實作Godot需要知道如何處理檔時呼叫的方法.

讓我們開始編寫我們的外掛程式, 一個方法:

# import_plugin.gd
@tool
extends EditorImportPlugin


func _get_importer_name():
    return "demos.sillymaterial"

The first method is the _get_importer_name(). This is a unique name for your plugin that is used by Godot to know which import was used in a certain file. When the files needs to be reimported, the editor will know which plugin to call.

func _get_visible_name():
    return "Silly Material"

The _get_visible_name() method is responsible for returning the name of the type it imports and it will be shown to the user in the Import dock.

你選擇的名字應該可以接到“匯入為”後面,例如*“匯入為 Silly Material”*。你可以隨心所欲地命名,但我們建議為你的外掛程式起一個描述性的名字。

func _get_recognized_extensions():
    return ["mtxt"]

Godot's import system detects file types by their extension. In the _get_recognized_extensions() method you return an array of strings to represent each extension that this plugin can understand. If an extension is recognized by more than one plugin, the user can select which one to use when importing the files.

小訣竅

許多外掛程式可能會使用像 .json.txt 這樣的常見擴充. 此外, 專案中可能存在僅作為遊戲資料的檔, 不應匯入. 匯入時必須小心以驗證資料. 永遠不要指望檔案格式正確.

func _get_save_extension():
    return "material"

匯入的檔被保存在專案根部的 .import 資料夾中. 它們的副檔名應與你要匯入的資源型別相配對, 但由於Godot不能告訴你將使用什麼(因為同一資源可能有多個有效的副檔名), 你需要宣告將在匯入時使用的內容.

由於我們正在匯入材質, 因此我們將對此類資源型別使用特殊擴充. 如果要匯入場景, 可以使用 scn . 通用資源可以使用 res 副檔名. 但是, 引擎不會以任何方式強制執行此操作.

func _get_resource_type():
    return "StandardMaterial3D"

匯入的資源具有特定型別,編輯器可以據此知道它屬於哪個屬性槽。這樣就能夠將其從檔案系統面板拖放到屬性面板的屬性之中。

在我們的範例中, 它是 class_SpatialMaterial, 可以應用於3D物件.

備註

如果需要從同一擴充中匯入不同型別, 則必須建立多個匯入外掛程式. 您可以在另一個檔上抽象匯入程式碼, 以避免在這方面出現重複.

選項和預設

您的外掛程式可以提供不同的選項, 以允許使用者控制資源的匯入方式. 如果一組選定的選項很常見, 您還可以建立不同的預設以使使用者更容易. 下圖顯示了選項在編輯器中的顯示方式:

../../../_images/import_plugin_options.png

由於可能有許多預設並且它們用數位識別碼, 因此使用列舉是一個很好的做法, 因此您可以使用名稱來引用它們.

@tool
extends EditorImportPlugin


enum Presets { DEFAULT }


...

既然定義了列舉, 讓我們繼續看一下匯入外掛程式的方法:

func _get_preset_count():
    return Presets.size()

The _get_preset_count() method returns the amount of presets that this plugins defines. We only have one preset now, but we can make this method future-proof by returning the size of our Presets enumeration.

func _get_preset_name(preset_index):
    match preset_index:
        Presets.DEFAULT:
            return "Default"
        _:
            return "Unknown"

Here we have the _get_preset_name() method, which gives names to the presets as they will be presented to the user, so be sure to use short and clear names.

我們可以在這裡使用 match 敘述來使程式碼更加結構化. 這樣, 將來很容易新增新的預設. 我們使用catch all模式來返回一些東西. 雖然Godot不會要求超出您定義的預設計數的預設, 但最好是安全起見.

如果您只有一個預設, 則可以直接返回其名稱, 但如果您這樣做, 則在新增更多預設時必須小心.

func _get_import_options(path, preset_index):
    match preset_index:
        Presets.DEFAULT:
            return [{
                       "name": "use_red_anyway",
                       "default_value": false
                    }]
        _:
            return []

This is the method which defines the available options. _get_import_options() returns an array of dictionaries, and each dictionary contains a few keys that are checked to customize the option as its shown to the user. The following table shows the possible keys:

型別

說明

name

字串

選項的名稱. 顯示時, 底線變為空格, 首字母大寫.

default_value

任何型別

此預設的選項的預設值.

property_hint

列舉值

PropertyHint 中的一個值, 作為提示使用.

hint_string

字串

屬性的提示文字. 與您在GDScript中的 export 敘述中新增相同.

usage

列舉值

PropertyUsageFlags 中的一個值來定義用途.

namedefault_value 鍵是 強制 , 其餘是可選的.

請注意, get_import_options 方法接收預設編號, 因此您可以為每個不同的預設(尤其是預設值)配置選項. 在這個範例中, 我們使用 match 敘述, 但是如果您有很多選項並且預設只改變了您可能想要首先建立選項陣列的值, 然後根據預設更改它.

警告

即使您沒有定義預設(通過使 get_preset_count 返回零), 也會呼叫 get_import_options 方法. 您必須返回一個陣列, 即使它是空的, 否則您可能會得到錯誤.

func _get_option_visibility(path, option_name, options):
    return true

For the _get_option_visibility() method, we simply return true because all of our options (i.e. the single one we defined) are visible all the time.

如果只有在使用某個值設定了另一個選項時才需要使某個選項可見, 則可以在此方法中新增邏輯.

import 方法

The heavy part of the process, responsible for converting the files into resources, is covered by the _import() method. Our sample code is a bit long, so let's split in a few parts:

func _import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var file = FileAccess.open(source_file, FileAccess.READ)
    if file == null:
        return FileAccess.get_open_error()

    var line = file.get_line()

我們匯入方法的第一部分是打開並讀取原始檔案. 使用 File 類來做到這一點, 傳遞 source_file 參數, 該參數由編輯器提供.

如果打開檔時出錯, 我們將其返回以讓編輯器知道匯入不成功.

var channels = line.split(",")
if channels.size() != 3:
    return ERR_PARSE_ERROR

var color
if options.use_red_anyway:
    color = Color8(255, 0, 0)
else:
    color = Color8(int(channels[0]), int(channels[1]), int(channels[2]))

此程式碼獲取之前讀取的檔行, 並將其拆分為以逗號分隔的片段. 如果有多於或少於三個值, 則認為該檔無效並報告錯誤.

然後它建立一個新的 Color 變數, 並根據輸入檔設定其值. 如果啟用了 use_red_anyway 選項, 那麼它將顏色設定為純紅色.

var material = StandardMaterial3D.new()
material.albedo_color = color

這一部分製作了一個新的 SpatialMaterial , 這是匯入的資源. 我們建立一個新的實例, 然後將其反射顏色設定為我們之前得到的值.

return ResourceSaver.save(material, "%s.%s" % [save_path, _get_save_extension()])

這是最後一個部分, 也是相當重要的部分, 因為在這裡我們把製作好的資源保存到磁片上. 保存檔的路徑由編輯器通過 save_path 參數生成並告知. 注意, 這個參數 沒有 副檔名, 所以我們用 string formatting 新增它. 為此, 我們呼叫前面定義的 get_save_extension 方法, 這樣我們就可以確保它們不會丟失同步.

我們還返回 ResourceSaver.save() 方法的結果, 所以如果這一步有錯誤, 編輯器會知道.

平臺變體和生成的檔

您可能已經注意到我們的外掛程式忽略了 import 方法的兩個參數。那些是*返回參數*(因此它們的名稱以 r 開頭),這意味著編輯器會在呼叫您的 import 方法之後讀取它們。它們都是可以填充資訊的陣列。

r_platform_variants 參數用於需要根據目標平臺匯入不同的資源. 雖然被稱為 平臺 變體, 但它是基於 feature tags 的存在, 所以即使是同一個平臺也可以有多個變體, 這取決於設定.

要匯入平臺變體, 需要在副檔名之前使用feature標記保存它, 然後將標記推送到 r_platform_variants 陣列, 以便編輯可以知道您做了.

例如, 假設我們為移動平臺保存一個不同的材質. 我們將需要做如下的事情:

r_platform_variants.push_back("mobile")
return ResourceSaver.save(mobile_material, "%s.%s.%s" % [save_path, "mobile", _get_save_extension()])

r_gen_files 參數用於在匯入過程中生成並需要保留的額外檔. 編輯器將查看它以瞭解依賴關係並確保不會無意中刪除額外檔.

這也是一個陣列, 應該填充您保存的檔的完整路徑. 例如, 讓我們為下一個傳遞建立另一個材質並將其保存在不同的檔中:

var next_pass = StandardMaterial3D.new()
next_pass.albedo_color = color.inverted()
var next_pass_path = "%s.next_pass.%s" % [save_path, _get_save_extension()]

err = ResourceSaver.save(next_pass, next_pass_path)
if err != OK:
    return err
r_gen_files.push_back(next_pass_path)

試試這個外掛程式

這是理論上的, 但是現在匯入外掛程式已經完成了, 讓我們來測試一下. 確保您建立了範例檔(包含介紹部分中描述的內容)並將其另存為 test.mtxt . 然後在 "專案設定" 中啟動外掛程式.

如果一切順利, 匯入外掛程式將新增到編輯器中並掃描檔案系統, 使自訂資源顯示在FileSystem基座上. 如果選擇它並聚焦匯入面板, 則可以看到選擇該選項的唯一選項.

在場景中建立一個 MeshInstance 節點,為其 Mesh 屬性設定一個新的 SphereMesh。在“屬性面板”中展開 Material 部分,然後將檔從“檔案系統”面板拖動到材質屬性。物件將在視口中使用匯入材質的藍色進行更新。

../../../_images/import_plugin_trying.png

轉到匯入面板, 啟用 "強制使用紅色" 選項, 然後按一下 "重新匯入". 這將更新匯入的材質, 並應該自動更新顯示紅色的視圖.

就是這樣! 你的第一個匯入外掛程式已經完成! 現在就發揮創造力,為自己心愛的格式製作外掛程式吧。這對於以自訂格式編寫資料然後在 Godot 中使用它就像它們是本機資源一樣非常有用。這顯示了匯入系統如何強大和可擴充。