GLTFState in Godot – Complete Guide

Understanding the intricacies of digital content creation is an essential skill in the modern world of game development. One aspect that has become increasingly important is the ability to manage and manipulate 3D models and animations—particularly when it comes to transferring these assets between different software tools or game engines. Enter the GLTF format, a powerful solution for this exact problem, and Godot 4’s GLTFState class, an essential tool for handling GLTF files within one of the most popular open-source game engines. In this tutorial, we’ll dive into the world of GLTFState in Godot 4, exploring how it can be utilized to manage and manipulate the data of GLTF files effectively.

What Is GLTFState?

GLTFState is a class within the Godot 4 game engine’s infrastructure, representing the complete set of data stored in a GLTF (Graphics Language Transmission Format) file. GLTF is often described as the “JPEG of 3D” for its ability to efficiently transmit 3D models and scenes with their accompanying textures, shaders, and animations. The GLTFState class serves as a centralized data structure to hold all the nodes, materials, textures, and animations of a GLTF file, making it straightforward to manage this information within Godot.

What Is It For?

The primary purpose of GLTFState is to act as a container when importing or exporting GLTF files in Godot. Whether you’re bringing in a detailed model from a 3D design tool or converting your carefully crafted Godot scene into a GLTF file to use elsewhere, GLTFState is the backbone that organizes all the relevant assets and properties. With this robust class, developers and artists can enable a seamless exchange of data, ensuring that their 3D content is transferable across different platforms and tools while maintaining its original integrity and features.

Why Should I Learn It?

Learning to work with GLTFState can significantly enhance your ability to handle 3D assets in your game development projects. By mastering this class, you can:

– Import complex 3D scenes into Godot, complete with animations and textures.
– Export your Godot scenes to GLTF format, making it easier to share and collaborate with others using different tools.
– Intercept the import/export process to include custom data, providing increased flexibility for unique game features or optimizations.

Essentially, understanding GLTFState empowers you to work more efficiently with 3D content, allowing for a more flexible and streamlined game development workflow. Whether you’re just starting out or looking to sharpen your existing skills, grasping how GLTFState operates will open up a world of possibilities in 3D game development.

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

Loading and Parsing a GLTF File

To begin working with GLTFState in Godot 4, the first step is to load and parse a GLTF file. This involves using the GLTFDocument class along with GLTFState to access the data within the file.

Firstly, create an instance of the GLTFDocument class and then call the function to load a GLTF file into a GLTFState object:

var gltf_document = GLTFDocument.new()
var gltf_state = GLTFState.new()
var file_path = "res://path_to_your_file.gltf"

var error = gltf_document.load_gltf(file_path, gltf_state)
if error == OK:
    print("GLTF file loaded successfully!")
else:
    print("Failed to load GLTF file!")

Once you have the GLTF file loaded, you can access the loaded data from the GLTFState object:

print("Number of nodes in GLTF file: ", gltf_state.nodes.size())

You can iterate over the nodes to get more information:

for node in gltf_state.nodes:
    print("Node name: ", node.name)

It’s also possible to access the materials defined in the GLTF file:

for material in gltf_state.materials:
    print("Material name: ", material.name)

Manipulating Node Transforms

In Godot, every object in a scene is a “node”, and GLTFState stores each node from the GLTF file. You can modify the transform of these nodes before adding them to the scene.

For example, if you want to scale all the nodes in the GLTFState:

for node_index in gltf_state.nodes.size():
    var node = gltf_state.nodes[node_index]
    node.transform.basis = node.transform.basis.scaled(Vector3(2, 2, 2))  # Scaling by 2 in all axis

If you need to rotate a specific node around its y-axis by 90 degrees, you would do:

var node_to_rotate = gltf_state.nodes[node_index]  # Replace node_index with the actual index of the node
var rotation_degrees = 90
var rotation_radians = deg2rad(rotation_degrees)
node_to_rotate.transform = node_to_rotate.transform.rotated(Vector3(0, 1, 0), rotation_radians)

Adjusting Materials and Textures

The GLTFState also contains lists of materials and textures that can be customized. For instance, you can change the metallic and roughness properties of all materials:

for material_index in gltf_state.materials.size():
    var material = gltf_state.materials[material_index]
    material.set_metallic(0.5)  # Set metallic value
    material.set_roughness(0.1)  # Set roughness value

Or you can link a specific texture to a material’s albedo (base color) texture slot:

var texture = preload("res://path_to_your_texture.png")
var material = gltf_state.materials[material_index]  # Replace material_index with the index of your material
material.set_texture(StandardMaterial.TEXTURE_ALBEDO, texture)

Saving GLTFState to a New GLTF File

After making changes to the GLTFState, it might be necessary to save this state to a new GLTF file. This is how you can export your modified data back to the GLTF format:

var new_gltf_path = "res://path_to_save_new_file.gltf"
var error = gltf_document.save_gltf(new_gltf_path, gltf_state)
if error == OK:
    print("New GLTF file saved successfully!")
else:
    print("Failed to save new GLTF file!")

This allows you to effectively modify GLTF files within the Godot environment and export these changes for use in other applications or for sharing with colleagues and collaborators.

Animating Nodes with GLTFState

The GLTFState class not only allows for static transformations but also manages animations stored in a GLTF file. Here’s how you can iterate through animations and play them:

for animation_index in gltf_state.animations.size():
    var animation_player = AnimationPlayer.new()
    var animation = gltf_state.animations[animation_index]
    animation_player.add_animation(animation.name, animation)
    animation_player.play(animation.name)
    add_child(animation_player)

This snippet adds an `AnimationPlayer` node for each animation, adds the animation to the player, and starts playing it sequentially.

Managing the Scene Tree with Nodes from GLTFState

After loading and possibly manipulating the GLTFState data, you might want to instantiate the nodes into your scene tree. You can create a new instance for each node like this:

for node_index in gltf_state.nodes.size():
    var node = gltf_state.nodes[node_index]
    var instance = node.instance()
    if instance:
        get_parent().add_child(instance)

This instantiates each node and adds it to the scene tree under the current node’s parent.

Creating a Custom Importer Using GLTFState

Custom importers can be created to handle specific requirements for the GLTF file. Below is an example of how to set up a custom importer method that applies custom logic to each imported node:

func custom_import_gltf(file_path):
    var gltf_document = GLTFDocument.new()
    var gltf_state = GLTFState.new()

    var error = gltf_document.load_gltf(file_path, gltf_state)
    if error != OK:
        print("Failed to load GLTF file!")
        return
    
    for node in gltf_state.nodes:
        apply_custom_logic(node)
    
    var scene_root = instantiate_gltf_scene(gltf_state)
    add_child(scene_root)

func apply_custom_logic(node):
    # Custom logic that modifies node properties or applies specific changes

func instantiate_gltf_scene(gltf_state):
    var scene_root = Spatial.new()
    
    for node_index in gltf_state.nodes.size():
        var node = gltf_state.nodes[node_index]
        var instance = node.instance()
        if instance:
            scene_root.add_child(instance)
    
    return scene_root

Modifying Animation Tracks

If you need to modify certain aspects of an animation track, such as the keyframes or the interpolation mode, you can do so by directly accessing the animation in the GLTFState:

var animation = gltf_state.animations[animation_index]  # Replace animation_index with the index of your animation
var track_index = 0  # Replace this with the index of your track within the animation
var keyframe_index = 0  # Replace with the index of the keyframe you want to adjust

var track = animation.track_get_key_value(track_index, keyframe_index)
# Modify the track's key value here

animation.track_set_key_value(track_index, keyframe_index, track)

This allows for subtle tweaks in the animations, such as adjusting timing or the intensity of certain movements.

Extracting Textures from GLTFState

Textures within GLTF files can be extracted and saved or manipulated in Godot. Here’s an example of how to save a GLTF texture to a PNG file:

for texture_index in gltf_state.textures.size():
    var texture = gltf_state.textures[texture_index]
    var image = texture.get_data()
    image.flip_y()  # GLTF textures are usually flipped on the y-axis
    
    var save_path = "res://extracted_texture_" + str(texture_index) + ".png"
    image.save_png(save_path)

In this snippet, we are traversing through each texture present in the GLTFState, flipping it along the y-axis (because GLTF uses a different coordinate system), and saving it to a .png file in the project’s resource directory. This process can be particularly useful when needing to edit textures externally or apply them to other materials.

By using these code examples, you can begin to explore the capabilities of the GLTFState class in Godot 4, adapting and extending them to fit the particular needs of your project. As you’ll discover, GLTFState is a highly flexible tool, capable of simplifying the management of 3D content and achieving greater compatibility between different software used in game development.Continuing on with GLTFState, we can delve into how to manage the skinning information for character models and other animated objects. Skinning data typically includes information on bones (joints) and how vertices of the model are weighted to these bones.

To access the skin for a particular node, you can use:

var node_index = 0 # Replace with the index of your node
var node = gltf_state.nodes[node_index]
if node.skin != -1:
    var skin = gltf_state.skins[node.skin]
    # Do something with the skin data

With the skin data available, you can traverse through the bones and adjust their rest transforms if needed:

for bone_index in skin.joints.size():
    var bone = gltf_state.nodes[skin.joints[bone_index]]
    var original_rest = bone.transform
    var modified_rest = original_rest.affine_inverse() # Modify the rest transform as needed
    skin.joints[bone_index] = modified_rest

Here’s how to change the vertex weights attributed to the joints for a node’s skin:

# Apply a transformation to each weight in the skin
for weight_index in skin.weights.size():
    # Get the original weight
    var original_weight = skin.weights[weight_index]
    
    # Apply a transformation (e.g. scaling the weight)
    var modified_weight = original_weight * 0.5
    
    # Set the modified weight
    skin.weights[weight_index] = modified_weight

Let’s now see how we deal with animation in greater detail. You can modify specific properties within an animation track, like adjusting the duration of an animation:

var animation = gltf_state.animations[animation_index] # Replace with your animation index
animation.length = 5.0 # Sets the duration of the animation to 5 seconds

It’s also possible to add new animations based on specific nodes or properties by creating an entirely new track:

var new_animation = Animation.new()
new_animation.length = 3.0 # 3 seconds long

# Create a new track
var track_index = new_animation.add_track(Animation.TYPE_TRANSFORM)

# Set the path of the track to a specific node
new_animation.track_set_path(track_index, "PathToYourNode")

# Insert keyframes into the track
new_animation.track_insert_key(track_index, 0.0, Transform.IDENTITY)
new_animation.track_insert_key(track_index, 3.0, Transform.IDENTITY.translated(Vector3(0, 10, 0)))

# Add the new animation to the GLTFState
gltf_state.animations.push_back(new_animation)

In this example, a new track is created for transforming a node from its original position to a position 10 units above over the span of 3 seconds.

When dealing with multiple GLTF files, you may also want to merge states from different files together:

var gltf_document_1 = GLTFDocument.new()
var gltf_document_2 = GLTFDocument.new()

var gltf_state_1 = GLTFState.new()
var gltf_state_2 = GLTFState.new()

gltf_document_1.load_gltf("res://first_file.gltf", gltf_state_1)
gltf_document_2.load_gltf("res://second_file.gltf", gltf_state_2)

# Imagine merging function that combines two GLTFStates
var merged_state = merge_gltf_states(gltf_state_1, gltf_state_2)

func merge_gltf_states(state_1, state_2):
    # Logic to merge the states, handling nodes, materials, textures, animations, etc.
    var merged_state = GLTFState.new()
    merged_state.nodes = state_1.nodes + state_2.nodes
    # More merging logic...
    return merged_state

In the example above, the `merge_gltf_states` function hypothetically combines the nodes, materials, textures, and animations from both GLTFState objects. Note that this is a simplified illustration—you would typically need to handle conflicts or duplicates carefully.

These detailed code examples and explanations provide an in-depth look at how to leverage GLTFState for various common tasks in Godot 4, ranging from animation control to skinning and state management. Each snippet can serve as a starting point for your customization, ultimately enhancing the functional repertoire of your project’s 3D capabilities.

Where to Go Next in Your Godot Learning Journey

If you’ve enjoyed diving into the intricacies of GLTFState and are keen to broaden your horizons with Godot game development, we’ve got just what you need to continue your learning journey. Our Godot Game Development Mini-Degree is a curated program that ramps up from foundational concepts to more advanced topics in a structured, comprehensive style. Through this mini-degree, delve into a wide array of crucial game development areas, such as creating captivating 2D and 3D games, mastering Godot’s GDScript, and building complex gameplay systems.

For learners who seek an even wider selection of topics and want to check out different facets of Godot, our array of Godot courses cater to a variety of interests. Whether you’re just getting started or you’re ready to refine advanced skills, these courses can support your growth at every step. Our commitment at Zenva is to guide you along the path from beginner to professional, enabling you to craft stunning games and grow your portfolio in a way that’s compatible with your own pace and learning style.

Remember, the journey to mastery is continuous, and with the Godot Game Development Mini-Degree and our extensive course offerings, you’re equipped to take your next steps with confidence. Embrace the adventure ahead, leverage the powerful capabilities of Godot, and solidify your place in the world of game development.

Conclusion

Diving into GLTFState in Godot 4 is more than just learning how to manipulate 3D models; it’s about unlocking the potential to create dynamic, visually stunning worlds that captivate players. With this knowledge under your belt, you stand ready to elevate your game designs to professional levels, taking advantage of Godot’s robust features to turn your creative visions into playable realities. As you continue to harness the power of Godot, remember that our Godot Game Development Mini-Degree is here to support and expand your skillset even further.

We at Zenva are excited to see the incredible games you’ll create, and we’re committed to being a part of your growth story. Your journey in game development is as thrilling as the games you’re going to build, and we can’t wait to provide you with the skills, knowledge, and inspiration needed to make your mark in the gaming world. Keep learning, keep experimenting, and above all, keep enjoying every step of this creative process.

FREE COURSES
Python Blog Image

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