EditorImportPlugin in Godot – Complete Guide

Welcome to this comprehensive tutorial on leveraging the powerful capabilities of the EditorImportPlugin in Godot 4. If you’re delving into game development using Godot, understanding how to extend the editor’s functionality with custom resource importers is an invaluable skill. This knowledge will not only broaden the range of assets and resources you can use in your projects but also streamline your workflow, making your development process more efficient and flexible.

What is EditorImportPlugin?

The EditorImportPlugin is a class in the Godot engine that allows you to expand the editor with your own resource importers. With this class, you can parse files of any kind and transform them into usable resources within Godot. For game developers and content creators, this means you can tailor the importing process to fit custom file formats or enhance existing import workflows with additional options.

What is it for?

This class serves as a bridge between external asset files and the Godot resource ecosystem. By creating custom importers, you can bring assets into Godot that wouldn’t be recognized by the default importers. This feature is especially useful when dealing with proprietary formats or when there is a need to tweak the import process for specific types of assets like meshes, textures, or sounds.

Why Should I Learn It?

Understanding how to create and use EditorImportPlugins in Godot can be a game-changer for your projects. Here are some reasons why learning this is beneficial:

– **Customization**: Tailor the import process to your specific requirements.
– **Efficiency**: Streamline the workflow and save time when dealing with numerous or complex assets.
– **Innovation**: Enhance Godot’s capabilities and potentially share your plugins with the community.

Armed with the ability to customize your asset importing experience, you can focus on the creative aspects of game development, assured that your resources will integrate seamlessly into your projects. Let’s dive in and start coding!

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Creating Your First EditorImportPlugin

Before we dive into the code, ensure you have Godot 4.0 set up for this tutorial. Once you have Godot open, let’s start by creating a new plugin.

First, you need to create a new folder in your project’s `addons` directory. Let’s call this folder `custom_importer`. Inside this folder, create two files: `plugin.cfg` and `custom_importer.gd`.

The `plugin.cfg` file is where you define the plugin’s information. Here’s a basic example of how to set it up:

[plugin]
name="Custom Importer"
description="A custom resource importer for Godot."
author="Your Name"
version="1.0"
script="custom_importer.gd"

In `custom_importer.gd`, we will define the plugin’s script. For the plugin to be recognized as an import plugin, it must extend `EditorImportPlugin`.

tool
extends EditorImportPlugin

func get_importer_name():
    return "custom.format.importer"

func get_visible_name():
    return "Custom Format"

func get_recognized_extensions():
    return ["cstm"]

func get_save_extension():
    return "res"

func get_resource_type():
    return "Resource"

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var custom_resource = Resource.new()
    # Your custom import logic goes here
    # For this example, we are just creating an empty Resource
    return custom_resource

This code snippet gives you a template to start creating your custom importer. The `get_recognized_extensions()` function defines what file extensions your importer works with.

Importing Custom Text-Based Formats

Many games use custom text-based formats for data like levels, configurations, or narrative scripts. Let’s create a simple importer that converts a `.cstm` text file into a Godot String resource.

In `custom_importer.gd`, you would define the `import` function to read the text from the file and create a String resource:

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var file = File.new()
    if file.open(source_file, File.READ) != OK:
        return ERR_CANT_OPEN
    var text = file.get_as_text()
    file.close()

    var string_resource = StringName.new()
    string_resource.set_name(text)

    return string_resource

In this example, we open the file, read its text, and then put that text into a new StringName resource. Be mindful that real-world scenarios would likely involve parsing and interpreting the text data rather than just encapsulating it.

Handling More Complex Formats

For more complex formats like binary or custom structured files, you’ll need to parse the contents within the `import` function.

Let’s say your custom binary format defines a simple structure for colors in RGB format. To convert this into a Godot Color resource, your import function might look something like this:

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var file = File.new()
    if file.open(source_file, File.READ) != OK:
        return ERR_CANT_OPEN

    var color_resource = Color.new()
    color_resource.r = file.get_8() / 255.0
    color_resource.g = file.get_8() / 255.0
    color_resource.b = file.get_8() / 255.0
    file.close()

    return color_resource

This importer reads three consecutive bytes from the binary file, interpreting them as the red, green, and blue channels of a color.

Improving the Workflow with Options

For more versatility, Godot 4’s importer plugins can present custom options to the user in the import window. This way, the user can make selections that change how the resource is imported.

Here’s how to define custom options and retrieve their values in the import function:

func get_option_visibility(option, options):
    return true  # All options are visible by default

func get_import_options():
    return [
        {
            "name": "import_as_reference",
            "default_value": false
        }
    ]

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var as_reference = options["import_as_reference"]

    var custom_resource = Resource.new()
    if as_reference:
        # Implement your logic when the 'import_as_reference' option is true
    else:
        # Implement your logic when the 'import_as_reference' option is false

    return custom_resource

In this snippet, we determine whether to import the custom data “as reference” based on a checkbox in the import settings. The `get_import_options()` method defines this checkbox and its default value. The actual conditional handling would depend on what “as reference” means in the context of your custom format.

Stay tuned for the next installment, where we further refine our importer plugin and tackle more complex examples to empower your Godot 4 projects!

func get_option_visibility(option, options):
    return true

func get_import_options():
    return [
        {"name": "import_as_reference", "default_value": false},
        {"name": "adjust_color_brightness", "default_value": false},
        {"name": "brightness_factor", "default_value": 1.0, "property_hint": PROPERTY_HINT_RANGE, "hint_string": "0.1,2,0.1"},
    ]

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var as_reference = options["import_as_reference"]
    var adjust_brightness = options["adjust_color_brightness"]
    var brightness_factor = options["brightness_factor"]

    var custom_resource = Resource.new()

    if as_reference:
        # Implement logic when the 'import_as_reference' option is true
    else:
        # Implement alternative logic

    if adjust_brightness:
        # Suppose we are dealing with an image, adjust its brightness
        custom_resource = adjust_image_brightness(custom_resource, brightness_factor)

    return custom_resource

func adjust_image_brightness(image_resource, factor):
    # This is a placeholder for the logic to adjust the brightness
    # For demonstration purposes, I'm just returning the unmodified resource
    return image_resource

In this expanded example, we’ve introduced two new options for affecting brightness adjustment on an image resource during import. The `brightness_factor` is a configurable range that the user can adjust. Note that `PROPERTY_HINT_RANGE` and `hint_string` are utilized to create a slider in the importing options.

Taking the example forward, let’s say your game features custom shaders, and you want them to be importable into Godot. We’d need to read the shader code from a file and wrap it in a Shader resource:

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var file = File.new()
    if file.open(source_file, File.READ) != OK:
        return ERR_CANT_OPEN

    var shader_code = file.get_as_text()
    file.close()

    var shader_resource = Shader.new()
    shader_resource.set_code(shader_code)

    return shader_resource

This snippet demonstrates the basic importing of custom shader code. With this importer, users can bring in shaders written outside of Godot and have them automatically wrapped in a Shader resource, ready to be used in the game.

For developers looking to import custom 3D model formats, the importing process becomes more complex, focusing on translating model data into MeshInstance nodes. This requires understanding of the model structure and how to unpack vertices, normals, UVs, etc.:

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    # Placeholder for file opening and error handling...

    var mesh_data = parse_model_file(file)
    file.close()

    var mesh_instance = MeshInstance.new()
    mesh_instance.mesh = mesh_data.to_surface(tool)

    return mesh_instance

func parse_model_file(file):
    # This function would parse your custom model format
    var mesh = ArrayMesh.new()
    # Parse logic for vertices, normals, UVs etc goes here
    return mesh

Parsing and converting model data to an ArrayMesh would be a detailed task requiring knowledge of both the custom format and the Godot Mesh API.

Additionally, audio assets might require special handling, especially if they’re using an unconventional encoding or need metadata attached:

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    # Placeholder for file opening and error handling...

    var audio_stream = decode_audio_file(file)
    file.close()

    var audio_resource = AudioStreamSample.new()
    audio_resource.data = audio_stream.raw_data

    return audio_resource

func decode_audio_file(file):
    # Decode your custom audio format to PCM data
    var pcm_data = PoolByteArray()
    # Decoding logic goes here
    return pcm_data

Here, `decode_audio_file` would be responsible for decoding the audio file into raw PCM data that Godot’s AudioStreamSample resource can use.

Through these examples, we’ve demonstrated a range of possibilities for custom resource importers in Godot. These should give you a solid starting point to consider what custom import logic may be beneficial for the unique requirements of your projects. Remember, the effectiveness of your plugins can significantly augment both the development experience and the capabilities of your games, making learning these techniques a powerful addition to your development toolkit.As we delve deeper into the capabilities and customization available through the EditorImportPlugin in Godot, let’s focus on more complex examples and their associated code snippets. These will help us to handle a variety of scenarios that game developers might encounter, such as importing animations, customizing scene instantiation, dealing with localization, and optimizing assets for different platforms.

Importing Animations:
Suppose your game uses a proprietary animation format. You’ll need to create an importer that can parse animation data and construct an Animation resource out of it.

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    # Placeholder for file opening and error handling...

    var animation_data = parse_animation_file(file)
    file.close()

    var animation_resource = Animation.new()

    # Assuming that animation_data is a dictionary with frame times and values
    for track_name in animation_data.keys():
        var track_data = animation_data[track_name]
        var track = animation_resource.track_insert(track_name)
        
        for frame_time in track_data.keys():
            var frame_value = track_data[frame_time]
            animation_resource.track_insert_key(track, frame_time, frame_value)

    return animation_resource

func parse_animation_file(file):
    var animation_data = {}
    # Logic to read and parse the custom animation file goes here
    return animation_data

In this example, `parse_animation_file` is a custom function you’d write that decodes your animation file and returns a structure with animation data. Then, you would iterate through that structure to insert the appropriate frames and values into the new Animation resource.

Customizing Scene Instantiation:
Customizing how scenes are instantiated from imported resources can be crucial if you need to set up nodes in specific ways, such as assigning scripts or setting node properties.

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var scene = PackedScene.new()
    var root_node = Node.new()

    # Set up the root node properties
    root_node.name = "CustomRoot"
    root_node.script = load("res://path_to_script.gd")  # Assigning a script dynamically

    scene.pack(root_node)

    # Add more nodes or set up the scene however you need
    var child_node = Sprite.new()
    root_node.add_child(child_node)
    child_node.texture = load("res://path_to_texture.png")  # Assigning a texture dynamically

    return scene

Here, you’re creating a new scene with a root node and dynamically assigning a script to it. Then, you’re adding a child Sprite node and setting its texture. This level of control is particularly useful when importing assets that need to fit certain patterns or structures within your game’s architecture.

Dealing with Localization:
For games that need to support multiple languages, importing localization files and creating translated resources can be automated using an EditorImportPlugin.

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    # Placeholder for file opening...

    var translation_data = parse_translation_file(file)
    file.close()

    var translation_resource = Translation.new()
    translation_resource.set_locale(translation_data.locale)

    for key in translation_data.keys():
        translation_resource.add_message(key, translation_data[key])

    return translation_resource

func parse_translation_file(file):
    var translation_data = {}
    # Logic to parse translation file goes here
    translation_data.locale = "en"  # Example locale setting
    # Parse logic for translation keys and values goes here
    return translation_data

In this example, `parse_translation_file` reads a custom format file and extracts translation keys and their corresponding text.

Optimizing Assets for Different Platforms:
For projects targeting multiple platforms, it might be necessary to optimize assets differently depending on the platform. The EditorImportPlugin can handle platform-specific variations.

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    # ... file opening and standard importing logic ...

    var texture_resource = load_standard_texture(save_path)

    # Let's optimize the texture for mobile platforms
    if "Android" in r_platform_variants:
        optimize_texture_for_mobile(texture_resource)

    # And then for web platforms
    if "HTML5" in r_platform_variants:
        optimize_texture_for_web(texture_resource)

    return texture_resource

func optimize_texture_for_mobile(texture):
    # Reduce resolution, switch compression formats etc.
    # Placeholder for actual optimization logic
    return texture

func optimize_texture_for_web(texture):
    # Perhaps reduce quality further, change formats etc.
    # Placeholder for actual optimization logic
    return texture

This final snippet shows how to use the `r_platform_variants` parameter to apply different optimizations based on the target platform. Remember that each platform could have unique requirements, and such an importer would be a great time-saver.

By exploring these code snippets, we’ve seen how to extend Godot’s editor to suit numerous asset importing needs. From animations to scene construction, localization, and cross-platform optimization, the EditorImportPlugin is a robust tool that can enhance the game development process in Godot, giving you precise control over how game content is brought into your project. With these tutorials, we’re confident that you’re well-equipped to tackle a range of importing challenges, making your workflow in Godot even more streamlined and customized to your needs.

Continuing Your Godot Journey

After diving deep into the world of EditorImportPlugins in Godot, you’re on the path to mastering the nuances of game development with this powerful engine. But don’t stop here! We at Zenva encourage you to continue expanding your skills and growing your knowledge of game creation.

To further enhance your understanding of Godot and take your development abilities to the next level, consider exploring our Godot Game Development Mini-Degree. This comprehensive collection of courses will not only solidify the basics but also introduce you to advanced game development concepts across various genres. With step-by-step guidance, you can build a robust portfolio of projects, deepen your expertise, and open up more opportunities in game development.

Additionally, if you’re looking to broaden your scope, check out our full range of Godot courses. These allow you to learn at your own pace, providing flexible options to fit your learning style. Regardless of where you are in your learning journey, Zenva has content to propel you from beginner to professional. So, why wait? Take the next step towards becoming a Godot wizard with Zenva today!

Conclusion

As we wrap up this foray into the dynamic world of the EditorImportPlugin in Godot, we hope you feel a surge of excitement for the endless potential that custom importers unlock. With the power to personalize your game development pipeline, you can ensure that every resource fits perfectly into your vision for your project. Remember, what we’ve covered here is just the beginning—you now have the tools to innovate and optimize, enhancing Godot’s impressive capabilities even further.

Don’t let the learning stop here! Come join us at Zenva to continue building your skill set. Whether you’re aspiring to create the next indie hit or looking to sharpen your professional abilities, our Godot Game Development Mini-Degree is your next big leap forward. We’re excited to support you on your game development journey and can’t wait to see the amazing worlds you’ll create. Let’s make your game development dreams a reality.

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.