Node3DGizmo in Godot – Complete Guide

Welcome to our tutorial on the Node3DGizmo class in Godot 4, a powerful tool for creating 3D games and applications. If you’ve ever wanted to dive into the world of 3D game development, understanding how to manipulate and interact with objects in space is crucial. Gizmos in any 3D engine provide visual handles that aid in editing objects, and Godot’s Node3DGizmo is no exception. Through this six-part series, we’ll explore what Node3DGizmo is, how to use it for enhancing your 3D scenes, and why it’s an invaluable skill to learn for burgeoning developers and seasoned coders alike. Let’s embolden your developing toolkit as we delve into this exciting topic!

What is Node3DGizmo?

Node3DGizmo is a class within the Godot game engine designed to provide developers with interactive visual aids, known as gizmos. These gizmos allow you to visually manipulate the transformation of Node3D objects, which are the fundamental elements used to represent 3D entities in Godot.

What is it for?

The primary use of Node3DGizmo is to enhance the experience of scene editing within Godot’s 3D editor. It serves to make spatial manipulations — such as moving, rotating, and scaling objects — more intuitive and precise, which is especially beneficial during the design phase of game development.

Why Should I Learn It?

Learning to use Node3DGizmo efficiently can dramatically expedite your workflow in game development. These are just a few advantages of mastering this tool:

– Streamlining the process of editing complex 3D scenes
– Enhancing precision and control over 3D object transformations
– Understanding Node3DGizmo adds depth to your understanding of Godot’s robust editor

Gizmos are a visual bridge between designers and the digital realm, providing immediate, direct control over the elements that compose your game’s environment. In the following sections, we’ll explore Node3DGizmo with hands-on coding examples to jumpstart your ability to use this class with confidence.

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

Creating a Custom Node3DGizmo in Godot 4

Our exploration into Node3DGizmo begins with creating a custom gizmo within the Godot editor. This allows us to define unique visual and interactive properties tailored to specific objects in our 3D scene. Let’s start by setting up a simple custom gizmo.

extends Node3DGizmo

func _init():
    # Imagine this gizmo is for a custom "Planet" node
    create_handle("rotate_y", EditorSpatialGizmo::FLAG_USE_BILLBOARD)
    add_lines([Vector3(0, 0, 0), Vector3(0, 1, 0)], Color(0, 1, 0, 1), false)
    commit_handle("rotate_y", get_material("transform_xform_gizmo", false))

In the code snippet above, we extend the Node3DGizmo class and initialize our custom gizmo for a hypothetical ‘Planet’ node. This script adds a handle for rotating around the Y-axis and visualizes it with a green line pointing upwards.

Implementing Interaction Handling

After establishing the basic visual elements of our gizmo, we’ll need to manage user interactions. The Node3DGizmo class lets us define how our gizmos will react when the user interacts with them.

func is_handle_clickable(id, camera, point_2d, intersect_pos, intersect_normal, ray_length):
    # Additional logic to determine if a handle can be clicked goes here
    
func intersect_ray(camera, ray_origin, ray_direction):
    # Logic to handle ray intersection with our gizmo for picking

The `is_handle_clickable` function checks if a gizmo handle is clickable under certain conditions, while the `intersect_ray` function determines if and where a ray (for example, from a mouse click) intersects with the gizmo.

Defining Custom Gizmo Properties

To further customize our gizmo, we can define properties that determine its behavior and appearance. Here, we’ll see how to set up a property that changes the color of our gizmo based on whether it’s selected.

var selected = false setget set_selected

func set_selected(value):
    selected = value
    # Change the material color based on the selection
    var color = selected ? Color(1, 0, 0, 1) : Color(0, 1, 0, 1)
    get_material("transform_xform_gizmo", false).albedo_color = color

In the above example, we utilize a setter function `set_selected` to adjust the color of the gizmo material to red when it’s selected, reverting to green when it’s not.

Applying Transformations with Node3DGizmo

Node3DGizmo not only provides visual cues but can also be used to apply transformations to the Node3D objects it’s associated with. The following demonstrates how you could handle transformation logic when an axis handle is dragged.

func commit_handle(for_id, restore, intersects):
    var transformation
    if for_id == "rotate_y":
        transformation = Transform().rotated(Vector3(0, 1, 0), intersects.x)
    # Apply the new transformation to the gizmo's parent Node3D
    get_node().global_transform = transformation

When the Y-axis handle identified as “rotate_y” is dragged, we calculate a new rotation transform and apply this to the gizmo’s parent node, which effectively rotates the node in the 3D space.

By the end of these steps, we’ve created a functional Node3DGizmo that provides both a visual interface and the interactive mechanics necessary for manipulating 3D objects in Godot’s editor. Armed with this foundational understanding, we’re ready to dive into more complex applications and user interactions which we’ll address in the next part of our tutorial.

In this section, we’re going to look at more advanced customizations and controls for our Node3DGizmo. Building on the basic setup, let’s add more functionality and visual feedback for a comprehensive editing experience.

Let’s start by adjusting the appearance and behavior of our gizmo when it’s undergoing various transformations:

func red_material():
    var mat = SpatialMaterial.new()
    mat.albedo_color = Color(1, 0, 0, 1)
    return mat

func blue_material():
    var mat = SpatialMaterial.new()
    mat.albedo_color = Color(0, 0, 1, 1)
    return mat

func handle_transform(type, plane, point):
    if type == 'translate':
        handles[type].material_override = red_material()
    elif type == 'rotate':
        handles[type].material_override = blue_material()

This code sets up basic materials to give feedback through color change when different transformation types are performed on the gizmo.

Next, let’s implement a function to handle the user’s input when they interact with the translation handles:

func can_be_hidden():
    return false

func intersect_handles(camera, ray_origin, ray_direction):
    # Process intersection for translation handles

The `can_be_hidden` function determines if our gizmo can be hidden from view or not, which is helpful for ensuring that important gizmos stay visible. Meanwhile, the `intersect_handles` function is stubbed out here and would contain logic to handle the user’s interaction with translation handles specifically.

Further refining user interaction, we can define how the gizmo responds to mouse drag events:

func select_handle(id):
    selected_handle = id
    # Logic to highlight or show selected handle

func deselect_handle():
    selected_handle = null
    # Logic for deselecting and resetting the handle material

These functions manage the state of handle selection, allowing us to apply specific visual changes on selection and deselection.

We might also want to indicate which axis is being acted upon more overtly. To achieve this, let’s define a simple method that adjusts the corresponding handle color upon mouse hover:

func set_handle_hover(id, hover):
    if hover:
        handles[id].material_override = SpatialMaterial.new()
        handles[id].material_override.albedo_color = Color(0.8, 0.8, 0.8, 1)
    else:
        # Reset to default material
        handles[id].material_override = null

Here, a lighter material is applied when the user hovers over a handle (given by its `id`) which then resets when the hover ends.

Finally, let’s add functionality to actually move an object along a chosen axis:

func move_along_axis(axis, distance):
    var prev_transform = get_node().global_transform
    var movement_vec = axis.normalized() * distance
    get_node().global_transform.origin += movement_vec

In the `move_along_axis` function, we move the selected node globally along the given axis by the specified distance. This can be connected to drag events on translation handles.

With these functions at hand, you can now craft a Node3DGizmo that responds visually and functionally to a user’s input, providing a much more intuitive and satisfying editing experience. By building these custom handles and interactions, you have the power to mold Godot’s editor to the particular needs of your 3D objects and create a more efficient workflow for yourself or your team.

Expanding on our Node3DGizmo’s functionality, we’ll focus on enhancing the precision and versatility of the interactions. Precision is key when hand-tweaking positions, rotations, or scales in 3D scenes. Let’s explore additional code examples to enable fine control for these transformations.

A common feature in gizmos is the ability to snap transformations to a grid. The following example illustrates how we can integrate snapping functionality:

var snap_enabled = false
var snap_step = Vector3(1, 1, 1) # Snap steps for x, y, z

func apply_snap(transform):
    if snap_enabled:
        transform.origin = transform.origin.snapped(snap_step)
    return transform

With a `snap_enabled` flag and a `snap_step`, we can conditionally snap the transform’s origin to a grid defined by `snap_step`. The `apply_snap` function applies this to any given transform.

Another important gizmo enhancement is the ability to scale along a specific axis. Here’s a snippet that shows how to implement this:

func scale_along_axis(axis, factor):
    var prev_scale = get_node().scale
    var axis_scale = Vector3.ONE
    axis_scale[axis] *= factor
    get_node().scale = prev_scale * axis_scale

We define a function `scale_along_axis` that accepts an axis vector and a scaling factor. This allows us to adjust the scale of the node’s axis independently by modifying the corresponding axis component in the scale vector.

For a more tactile interaction, it might be helpful to provide feedback during rotation. We can create a visual cue that tracks the rotation angle:

func draw_rotation_disc(angle, axis):
    # Reset the previous gizmo lines before drawing new ones
    clear_lines()
    
    var disc_points = PoolVector3Array()
    var steps = 32
    var step_angle = TAU / steps
    for i in range(steps + 1):
        var rotated_vec = Vector3(0, 1, 0).rotated(axis, step_angle * i)
        disc_points.append(rotated_vec * 2) # Arbitrary size
    
    add_lines(disc_points, Color(0.7, 0.7, 0), false)
    commit_handle("rotate", get_material("gizmo_lines", false))

This function, `draw_rotation_disc`, draws a disc composed of line segments that represent the degree of rotation, providing immediate feedback on the scope of angular changes as they are made.

Implementing alternative controls for manipulating the gizmo can also be advantageous, such as key modifiers to adjust the sensitivity or lock certain axes:

func handle_input(event):
    if event is InputEventMouseButton and event.control:
        scale_sensitivity = 0.1 # Reduce sensitivity when Control key is held
    elif event is InputEventMouseMotion and event.shift:
        lock_axis = Vector3(0, 1, 0) # Lock movement/rotation along the Y-axis

In this example, we alter `scale_sensitivity` when the Control key is pressed and `lock_axis` when the Shift key is held, allowing for more granular control during transformations.

Last but not least, undo-redo functionality is crucial for any editor. Here’s a straightforward approach to integrating undo history:

func undo_redo(callback, old_value, new_value):
    get_undo_redo().create_action("Transform Node")
    get_undo_redo().add_do_method(callback, new_value)
    get_undo_redo().add_undo_method(callback, old_value)
    get_undo_redo().commit_action()

This function leverages Godot’s built-in undo-redo system, wrapping the creation and application of actions. Here, `callback` is the method responsible for applying the transform, `old_value` is the transform before the change, and `new_value` is the transform after the change.

By implementing these additional functionalities, we’ve substantially boosted the usability and sophistication of our custom Node3DGizmo, bringing it on par with the interactive needs of professional-level editing within Godot 4. With these tools at your disposal, the creative possibilities in crafting your 3D worlds expand, giving you the flexibility and control needed for precise, effective game development.

Furthering Your Godot Development Journey

Congratulations on exploring the Node3DGizmo class and enhancing your understanding of Godot’s 3D capabilities! This knowledge paves the way for the next steps in your game development journey. As you continue to develop your skills and tackle more intricate projects, remember that this is just the beginning of what you can achieve with Godot 4.

For those who are eager to dive deeper and expand their repertoire, our Godot Game Development Mini-Degree is an excellent path forward. This comprehensive collection of courses covers a vast array of topics to equip you with the skills necessary to create cross-platform games using Godot 4. From 2D and 3D assets to intricate gameplay mechanics across various game types, the Mini-Degree offers a structured learning experience with projects that build on each other in complexity. Perfect for both beginners and seasoned developers, it’s an opportunity to go from basic concepts to creating professional-grade games.

If you’re looking for an even broader scope to choose from, be sure to explore our full range of Godot courses. With Zenva, you can tailor your learning to fit your specific goals and interests, all while learning at your own pace on any modern device. Embark on your next big game development adventure with us, and join the vibrant community of creators just like you.

Conclusion

As we wrap up our tutorial on Node3DGizmo, we celebrate the strides you’ve made in mastering this robust tool within Godot 4. Your journey doesn’t stop here! Embrace the skills you’ve honed, and let enthusiasm drive you towards creating the immersive 3D experiences you envision. Remember, with each node you tweak and each gizmo you customize, you’re not only building worlds, but you’re also crafting the very skills that define you as a game developer.

Don’t let the momentum stop — continue to harness the full potential of Godot and elevate your game development expertise with our Godot Game Development Mini-Degree. This is your invitation to step into a realm of new possibilities and challenges with Zenva, your ally in learning, growing, and succeeding in the exciting universe of game development. We can’t wait to see the amazing games you’ll create!

FREE COURSES
Python Blog Image

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