プラグインのインポート

注釈

このチュートリアルでは、汎用プラグインの作成方法を理解していることを前提としています。よく理解していない場合は、Making pluginsページを参照してください。また、Godotのインポートシステムに精通していることを前提としています。

はじめに

An import plugin is a special type of editor tool that allows custom resources to be imported by Godot and be treated as first-class resources. The editor itself comes bundled with a lot of import plugins to handle the common resources like PNG images, Collada and glTF models, Ogg Vorbis sounds, and many more.

このチュートリアルでは、単純なインポートプラグインを作成して、カスタムテキストファイルをマテリアルリソースとしてロードする方法を示します。このテキストファイルには、色の3つのチャネルを表すコンマで区切られた3つの数値が含まれ、結果の色はインポートされたマテリアルのアルベド(メインカラー)として使用されます。この例では、純粋な青の色(赤がゼロ、緑がゼロ、青がフル)が含まれます:

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

When this plugin is activated, it will create a new instance of the import plugin (which we'll soon make) and add it to the editor using the add_import_plugin() method. We store a reference to it in a class member import_plugin so we can refer to it later when removing it. The remove_import_plugin() method is called when the plugin is deactivated to clean up the memory and let the editor know the import plugin isn't available anymore.

インポートプラグインは参照型なので、 free() 関数を使用してメモリから明示的に解放する必要はありません。スコープ外になると、エンジンによって自動的に解放されます。

エディタインポートプラグイン クラス

The main character of the show is the EditorImportPlugin class. It is responsible for implementing the methods that are called by Godot when it needs to know how to deal with files.

プラグインのコーディングを始めましょう:

# 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.

この名前は、「名前を付けてインポート」の続きとして選択する必要があります。 例えば、 「愚かな素材としてインポート」。 好きな名前を付けることができますが、プラグインにはわかりやすい名前を付けることをお勧めします。

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 "SpatialMaterial"

インポートされたリソースには特定のタイプがあるため、エディタはリソースがどのプロパティスロットに属しているかを知ることができます。これにより、 「ファイルシステム」 ドックから 「インスペクタ」 のプロパティーにドラッグ&ドロップできます。

この例では、3Dオブジェクトに適用できるclass_SpatialMaterialです。

注釈

同じ拡張子から異なるタイプをインポートする必要がある場合は、複数のインポートプラグインを作成する必要があります。インポートコードを別のファイルに抽象化して、重複を避けることができます。

オプションとプリセット

プラグインは、ユーザーがリソースのインポート方法を制御できるように、さまざまなオプションを提供できます。選択したオプションのセットが一般的な場合は、異なるプリセットを作成して、ユーザーが簡単に使用できるようにすることもできます。次の図は、エディタでオプションがどのように表示されるかを示しています:

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

プリセットが多く、数値で識別される可能性があるため、名前を使用して参照できるように列挙型を使用することをお知めします。

tool
extends EditorImportPlugin

enum Presets { PRESET_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):
    match preset:
        PRESET_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では、定義したプリセット数を超えるプリセットは要求されませんが、常に安全側にあることをお勧めします。

プリセットが1つしかない場合は、名前を直接返すだけですが、これを行う場合は、プリセットを追加するときに注意する必要があります。

func get_import_options(preset):
    match preset:
        PRESET_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 列挙値 One of the PropertyHint values to use as hint.
hint_string 文字列 プロパティのヒントテキスト。GDScriptの export ステートメントに追加するのと同じです。
usage 列挙値 One of the PropertyUsageFlags values to define the usage.

name キーと default_value キーは必須で、残りは省略可能です。

Note that the get_import_options method receives the preset number, so you can configure the options for each different preset (especially the default value). In this example we use the match statement, but if you have lots of options and the presets only change the value you may want to create the array of options first and then change it based on the preset.

警告

get_import_options メソッドは、プリセットを定義しない場合でも呼び出されます( get_preset_count を0にして返す)。空であっても配列を返さなければならず、さもないとエラーが発生します。

func get_option_visibility(option, 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 = File.new()
    var err = file.open(source_file, File.READ)
    if err != OK:
        return err

    var line = file.get_line()

    file.close()

The first part of our import method opens and reads the source file. We use the File class to do that, passing the source_file parameter which is provided by the editor.

ファイルを開くときにエラーが発生した場合は、それを返して、インポートが失敗したことをエディタに知らせます。

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]))

このコードは、前に読み込んだファイルの行を取得し、コンマで区切られた断片に分割します。 3つよりも多いまたは少ない場合、ファイルは無効であると見なされ、エラーが報告されます。

Then it creates a new Color variable and sets its values according to the input file. If the use_red_anyway option is enabled, then it sets the color as a pure red instead.

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

This part makes a new SpatialMaterial that is the imported resource. We create a new instance of it and then set its albedo color as the value we got before.

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

This is the last part and quite an important one, because here we save the made resource to the disk. The path of the saved file is generated and informed by the editor via the save_path parameter. Note that this comes without the extension, so we add it using string formatting. For this we call the get_save_extension method that we defined earlier, so we can be sure that they won't get out of sync.

We also return the result from the ResourceSaver.save() method, so if there's an error in this step, the editor will know about it.

プラットフォームバリアントと生成されたファイル

お気付きかもしれませんが、このプラグインは import メソッドの2つの引数を無視しています。これらは戻り引数(したがって、名前の先頭に r があります)で、これはエディタがimportメソッドを呼び出した後にこれらの引数から読み込むことを意味します。どちらも情報を格納できる配列です。

The r_platform_variants argument is used if you need to import the resource differently depending on the target platform. While it's called platform variants, it is based on the presence of feature tags, so even the same platform can have multiple variants depending on the setup.

プラットフォームバリアントをインポートするには、拡張機能の前に機能タグを付けて保存してから、タグを r_platform_variants 配列にプッシュして、エディタがそれを認識できるようにする必要があります。

たとえば、モバイルプラットフォーム用に別のマテリアルを保存したとします。次のような処理を行う必要があります:

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

r_gen_files 引数は、インポートプロセス中に生成され、保持する必要がある追加のファイル用です。 エディタはこれを見て依存関係を理解し、余分なファイルが誤って削除されないようにします。

これは配列でもあるので、保存するファイルの完全なパスで埋められます。たとえば、次のパス用に別のマテリアルを作成し、別のファイルに保存してみましょう:

var next_pass = SpatialMaterial.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_path, next_pass)
if err != OK:
    return err
r_gen_files.push_back(next_pass_path)

プラグインを試す

This has been theoretical, but now that the import plugin is done, let's test it. Make sure you created the sample file (with the contents described in the introduction section) and save it as test.mtxt. Then activate the plugin in the Project Settings.

すべてがうまくいけば、インポートプラグインがエディタに追加され、ファイルシステムがスキャンされ、カスタムリソースがファイルシステムドックに表示されます。選択して「インポート」ドックにフォーカスを合わせると、選択する唯一のオプションが表示されます。

Create a MeshInstance node in the scene, and for its Mesh property set up a new SphereMesh. Unfold the Material section in the Inspector and then drag the file from the FileSystem dock to the material property. The object will update in the viewport with the blue color of the imported material.

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

「インポートドック]」に移動し、「とにかく赤を使用」 オプションを有効にし、「再インポート」をクリックします。これにより、読み込まれたマテリアルが更新され、代わりに赤い色を示すビューが自動的に更新されます。

以上です!最初のインポートプラグインが完了しました。自分の好きなフォーマットのプラグインを作ってみましょう。これは、カスタム・フォーマットでデータを作成し、それをあたかもネイティブ・リソースであるかのようにGodotで使用する場合に非常に便利です。これは、インポート・システムがいかに強力で拡張可能であるかを示しています。