Skip to main content

Automating Level imports from Blender to Godot

 

Recently I've been making some levels in Blender an importing them into Godot. There are only about 7 or 8 shaders for each level, not counting dynamic objects which will be added later. But to improve rendering performance, it can be a good idea to split the meshes up into sections.

At that point you might be faced with a list like this:

Or it might be even more chaotic, if you didn't use simple names for the objects in your level. So it can take a long time to sort out all the meshes, make them unique and add textures and so on.

Blender imports with simple Blender textures, or with placeholder materials. This is sometimes OK, but if your Godot shaders are very different to those used by Blender, it means applying new materials to every mesh object in the level when you import the scene.

I found that during the design process, I was importing and readying a level several times before I was happy with the final layout. So at first I was wasting a lot of time.

In Blender, I use python scripts to handle this sort of repetitive work, and I've decided to do the same thing in Godot. In order to automate my pipeline, I'm taking advantage of the "tool" and "export" features in gdscript.

Tool scripts can be run in the editor, and export variables can be set without modifying the script, just from the side bar in the editor. Together, these offer a lot of strong automation options. 

Here's an example:

My shaders are quite complex, often featuring animated textures or other features that won't be included in an export from Blender. In the following example, I've set up a script with export slots for materials. The script will search through the objects in the scene and match materials to names.

So for example:

extends Spatial
tool

export(bool) var run_script

export(Material) var vehicle
export(Material) var decals
export(Material) var junk
export(Material) var lcars
export(Material) var glass
export(Material) var props  
export(Material) var screens
export(Material) var structure
export(Material) var tech
export(Material) var cave
export(Material) var window

Here I've set up the export slots to accept materials. And next I use the ready function to match materials to names of meshes. It also makes the meshes unique, so that there's no worry about them still being attached to the old imported file.

var all_children = []

func get_child_recursive(current_node):
    var children = current_node.get_children()
    if len(children) > 0:
        for child in children:
            all_children.append(child)
            get_child_recursive(child)

Here's a function to get all children nodes and add them to a child list for processing. 

And finally the code for applying materials and making the meshes unique. Just add this script to a node, save it as a scene, then drop it into your imported asset scene, drag and drop to parent all the meshes to this node, then hit the "run_script" check box. As soon as the script is run, the check box reverts back to false, and your meshes have the right materials.

func _process():
    
    if Engine.editor_hint:
       
        if run_script == true:

            get_child_recursive(self)
            for child in
all_children:
               
                var tex_dict = {"vehicle":vehicle,
                                "decals":decals,
                                "glass":glass,
                                "junk":junk,
                                "lcars":lcars,
                                "props":props,
                                "screens":screens,
                                "structure":structure,
                                "tech":tech,
                                "walls":cave,
                                "window":window}
               
                if not child.mesh.is_local_to_scene():
                    child.mesh.set_local_to_scene(true)
               
                for tex_key in tex_dict:
                   
                    var tex = tex_dict[tex_key]
                    print(tex_key, tex, child.name)
                    if tex_key in child.name:
                        if tex != null:
                            child.set_surface_material(0, null)
                            child.mesh.surface_set_material(0, tex)
                            print("changed:", tex_key)
               
            run_script = false

Drag and drop those newly unique mesh instances onto a new spatial node, and save it as a new scene. Now you have your fully prepared level in seconds.

Of course the meshes have to be named in a logical way. All the objects which are going to get the "junk" material, are named {level_name}_junk and so on.

The run_script variable makes sure it only happens once, and doesn't keep running in the editor every time we open the scene.

If you're going to be making a full game, I recommend checking out tool and export features to see if you can find a way to speed up your workflow.

Comments

  1. This comment has been removed by the author.

    ReplyDelete
  2. Yup, automating things with tool scripts etc. is great! I'd say try go even further.

    Originally I rewrote the blender-godot-exporter for exactly this purpose: to allow seamless level conversion from blender -> godot. In particular using things like "project://path/to/material.tres" as a material name works as you'd expect. (Actutally I can't remember if that particular PR made it into the public version). Then I used a SConstruct pipeline to do blend -> escn as well as xcf -> png, build datastructures representing levels etc. etc.

    My aim for every asset pipeline is that there should be zero manual work involved so that everything can be reproduced from source files. In the VCS you only store the source files (xcf, blend, etc.) and all in-between-files (gltf, png, escn) should be generated automatically (along with appropriate .gitignores). If you need to click a button or store a "copy of the meshes not linked to the originals" then the chances of you losing the source files for those meshes is high.

    ReplyDelete
    Replies
    1. Hi sdfgeoff, I just saw this comment. Thanks for reading.
      I'd love to write a proper exporter that handles things just the way I want it, but I usually have specific things I want to do on import, so this way works out OK.
      For example, if a mesh has transparent textures, I like to add a shadows only material override object to cast shadows for it. And some of my objects need to be added to groups in the game (TV screens and such, so they can be turned on and off).
      I'm not sure if I've got the skills to write an exporter anyway, nor and be sure it would work without adding more bugs than it fixes...

      Delete

Post a Comment

Popular posts from this blog

Advice needed on tilesets...

I need some advice on which is the best way to handle building the dungeon. Right now I'm using prefabs for my dungeon, they have a north south east and west section for each "room": The basic tileset. This has several advantages, and also several disadvantages. Firstly I can have curved rooms, I can have tunnels and other interesting shapes. The tilesets can look quite nice with a little work. On the other hand I can't easily get the navigation data before building the map and once the map has been built I can't make changes to the layout, like having active pit traps or believable secret doors. Although the rooms are interesting, they are quite repetitive, and it takes a lot of effort to make even a few different variations. Also rooms are constrained to one size. A newer version of the tileset with a lot of variant parts for making more interesting rooms. To create a tile set is a real headache too. Planning how to lay out the UVs, trying to cra...

Upstairs / Downstairs.

I've decided to make my prefabs multilevel. Later this should allow me to add pit traps and other great stuff. It also makes it easier to line up stairs so that you can exit them on the same co-ordinates where you entered them. The prefab editor is pretty much finished, it just needs some code for loading up prefabs from a saved dictionary, so that they can be checked or edited. The entries will need to be forwards compatible, so I'll be loading each tile and then translating the indexes to a new array, that way if I add extra indexes or extra info (like traps or puzzles) I'll be able to update existing prefabs to work with the new standard. Click for a video.

Video Diary 8

Things are moving along well, there's been a lot of progress on the action manager side of things. Actions have finally moved to the UI, so you can initiate actions by clicking the appropriate button. I've set up some dummy actions to show what happens visually when actions are taken, but the actual dice rolls and such are yet to be integrated. The UI objects are also being added, though some are non functional or empty at the moment. Click on the image to see this week's development video. Every time I add something big I also add about a dozen small things. Like the selection box visualization. Previously this was using render.drawline, and old fashioned Blender function which can be impossible to see at certain resolutions, or at certain frequencies. I replaced it with a function that adds planes of the right size and scale in the right location. I also made all characters a little bigger. I still need to do some work with vectors and final target locations t...