The Book of Nodes: 3D

christinec_dev

christine

Posted on August 7, 2024

The Book of Nodes: 3D

Book of Nodes

Below you can find a list of 3D nodes that can be used in Godot 4. This is part of my Book of Nodes series. If you want to see similar content on 2D or UI nodes, please refer to the parent page of this post for those links. 😊

Before we begin, if you need a base project to test these code snippets, feel free to download my FREE 2D and 3D templates here. I’ll be using these templates throughout this post.

*Please note that this list is not 100% complete yet, but I will be updating this list as time goes on.


  • AnimatedSprite3D
  • AnimationPlayer
  • AnimationTree
  • Area3D
  • AudioStreamPlayer3D
  • Skeleton3D, Bone3D, and BoneAttachment3D
  • Camera3D
  • CharacterBody3D
  • CollisionShape3D
  • DirectionalLight3D
  • GridMap
  • MeshInstance3D
  • NavigationAgent3D, NavigationObstacle3D, NavigationRegion3D
  • Node3D
  • OmniLight3D
  • Path3D, PathFollow3D
  • RayCast3D
  • RigidBody3D
  • Sprite3D
  • Spotlight3D
  • StaticBody3D
  • Timer
  • VehicleBody3D
  • WorldEnvironment

Node3D

The Node3Dnode is the fundamental building block for all 3D scenes in Godot. It is the base node for all 3D-related functionalities, providing 3D spatial features like position, rotation, and scale. Almost all 3D nodes (like CharacterBody3D, Area3D, etc.) inherit from Node3D, which makes it the core of any 3D scene structure in Godot.

Book of Nodes

Mechanic:

Rotate a group of 3D nodes collectively.

Implementation:

  • Add a Node3D to your scene to serve as a parent node. In an actual project, this could be used to represent a complex object like a spacecraft or a robot.

Book of Nodes

  • Add child nodes such as twoMeshInstance3Dnodes. These children can represent visual components, collision areas, etc.

Book of Nodes

  • Give the MeshInstance3Dnodes a shape of your choosing. I’m going to choose a BoxMeshshape.

Book of Nodes

  • Now if we manipulate the Node3D parent, it will affect all its children. For example, rotating the Node3D will rotate all its children, whilst maintaining their relative positions and transformations.

Book of Nodes

  • We can also do this via code — say every second the game runs the node should rotate around the y-axis pivot point.
### Main.gd

extends Node3D

@onready var node_3d = $Node3D

func _process(delta):
    node_3d.rotate_y(1.0 * delta) 
Enter fullscreen mode Exit fullscreen mode

Book of Nodes

MeshInstance3D

The MeshInstance3D node in Godot is used to display 3D geometry. It takes a Meshresource and instances it in the current scene, which is essential for rendering 3D models.

In Godot, a mesh is used as a resource that can be applied to MeshInstance3D nodes to render 3D geometry in a scene. Meshes in Godot can be created directly in the engine or imported from external 3D modeling tools such as Blender. Godot supports several 3D model formats for importing meshes, including .fbx, .gltf and .glb (glTF), .obj, and others.

Book of Nodes

You can use this node to display characters, objects, or even simple shapes for prototyping, such as cylinders, planes, cubes, etc.

Book of Nodes

Mechanic:

Display a 3D model in a game scene.

Implementation:

  • Create a MeshInstance3D node within your 3D scene.

Book of Nodes

  • In the Inspector, assign a mesh resource to the mesh property of the MeshInstance3D.

Book of Nodes

Book of Nodes

  • This is great for prototyping, but what if you created a Mesh in Blender and want to show that instead of a shape? Well, you can drag your imported objects directly into your scene.

Book of Nodes

Book of Nodes

Book of Nodes

  • You’ll see that it is added to your scene underneath a Node3Dnode which has been imported as a scene. To access the MeshInstance3Dchild node from this parent, you’ll have to localize the scene. Do this by right-clicking on the node, and selecting “Make Local”.

Book of Nodes

Book of Nodes

  • You can now add collisions to this node, or even materials and shaders.

Book of Nodes

Book of Nodes

Book of Nodes

Book of Nodes

Sprite3D

The Sprite3D node in Godot is used to display 2D images in a 3D environment. This node can be set to always face the camera, which makes it useful for billboards, decals, NPC headlines, and other elements that need to be visible from all angles in a 3D space.

Mechanic:

Display a billboard that always faces the player.

Implementation:

  • Create a Sprite3D node in your 3D scene.

Book of Nodes

  • Assign a texture to the texture property of the Sprite3D node in the Inspector. This texture will appear as a flat image in the 3D space.

Book of Nodes

Book of Nodes

Book of Nodes

  • Currently the image is just flat. To change this, enable the billboard_mode property of the Sprite3D to ensure it always faces the camera, making it visible from any angle.

Book of Nodes

  • Run the scene and navigate around the 3D world to see the sprite always facing towards the camera, behaving like a billboard.

Book of Nodes

AnimatedSprite3D

The AnimatedSprite3D node utilizes a series of images (sprites) and displays them in a sequence to create an animation in a 3D space. Unlike a simple Sprite3D node that displays a single static image in our world, AnimatedSprite3D can cycle through multiple frames to animate characters, objects, or effects within your 3D game. To create these frames, we can use either sprites or a spritesheet.

Book of Nodes

The AnimatedSprite3D node utilized the SpriteFrames Resource to create animations. This is a special resource in Godot that holds collections of images. Each collection can be configured as an animation by specifying the images (frames) that belong to it. You can create multiple animations within a single SpriteFrames resource, each with its own set of frames and playback properties like speed and loop settings.

Book of Nodes

Book of Nodes

Mechanic:

Animate a 3D spinning coin.

Implementation:

Book of Nodes

  • Assign a SpriteFrames resource to the AnimatedSprite3D.

Book of Nodes

  • Add a new animation by clicking on the page+ icon.

Book of Nodes

  • Rename this animation by double clicking on it.

Book of Nodes

  • Either drag in sprites into the frames box, or click the spritesheet icon to add animations via our coin atlas.

Book of Nodes

  • Crop out the frames horizontally and vertically.

Book of Nodes

  • Select the frames you want. For instance, I will choose frame 0–4 in row 1.

Book of Nodes

  • Scale the coin down and move it to where you want it.

Book of Nodes

  • Then play the animation to see if you need to alter the FPS to make the coin rotate faster/slower.

Book of Nodes

  • Also enable billboard settings so that our image retains its 3D look and feel (instead of looking like a flat 2D coin).

Book of Nodes

  • Play the animation in your code so that when your player moves during runtime the animation can play:
### Main.gd

@onready var animated_sprite_3D = $AnimationSprite3D

func _ready():
    animated_sprite_3D.play("rotating_coin")
Enter fullscreen mode Exit fullscreen mode
  • Start your project and observe the coin rotating around in your world.

Book of Nodes

AnimationPlayer

Unlike the AnimatedSprite3D which is specifically designed for image animations, the AnimationPlayer can animate virtually ANY node within a Godot scene. Instead of animating a simple sprite, you can animate the node’s properties — including but not limited to positions, rotations, scales, colors, and even variables.

The AnimationPlayer can hold a set of animations on a singular timeline, each containing keyframes that define the start and end points of any property that changes over time. You can create complex sequences and control animations in a non-linear fashion.

This node can be used to animate 2D, 3D, and even UI nodes!

Book of Nodes

Book of Nodes

Mechanic:

Create a platform that moves up and down.

Implementation:

  • Add a MeshInstance3Dnode and an AnimationPlayernode to your scene. The MeshInstance3Dnode is the node we want to animate, and the property we want to animate of this node is its position.

Book of Nodes

  • Assign a shape to the MeshInstance3Dnode. I’ll assign a simple box shape.

Book of Nodes

  • Change its scale to be more similar to a platform — Vector3(2, 0.2, 2).

Book of Nodes

  • Select the AnimationPlayer node.
  • In the animation panel, click “New Animation” and name it something descriptive like “move_platform”.

Book of Nodes

Book of Nodes

  • Set the animation length to the duration you want for one pulse cycle (e.g., 1 second).
  • Enable the “Loop” option to make the animation repeat continuously.

Book of Nodes

  • Go to the beginning of the animation timeline (0 seconds).
  • Select the MeshInstance3D node, and in the Inspector, set the position property to its initial value (e.g., Vector3(0, 0, 0)).
  • Right-click the position property in the Inspector and select "Key" to add a keyframe.
  • Move to the middle of the timeline (e.g., 0.5 seconds), change the position to a larger value (e.g., Vector3(0, 1, 0)), and add another keyframe.
  • At the end of the timeline (1 second), set the position back to the initial value (Vector3(0, 0, 0)) and add a final keyframe.

Book of Nodes

Book of Nodes

Book of Nodes

Book of Nodes

  • You can control when the animation starts or stops via script, or let it run continuously since it’s set to loop.
### Main.gd

@onready var animation_player = $AnimationPlayer
func _ready():
    animation_player.play("move_platform")
Enter fullscreen mode Exit fullscreen mode
  • Start your project and observe the platform move up and down.

Book of Nodes

AnimationTree

The AnimationTree node enhances the capabilities of the AnimationPlayer by providing advanced features for animations, such as blending, transitions, and states. This makes it extremely easy to make detailed character animations and interactive scene elements in 2D and 3D environments.

We usually use blending to create smooth transitions between animations, for example, smoothly transitioning between walking and running depending on the player’s speed.

We use state machines to switch our animations dynamically depending on the conditions, for example, switching between idle and attack animations if the player presses a key.

Mechanic:

Animate a 3D character with multiple actions (e.g., running, jumping, idle).

Implementation:

  • Add an AnimationPlayernode to your scene which has your 3D model, and create animations for it like "run", "attack", and "idle".

Book of Nodes

  • You can also download animations from Mixamo if you don’t want to manually create these. If you want to download an animation from Mixamo, you will have to upload your 3D character (MAKE SURE IT’S THE SAME CHARACTER AS THE ONE IN YOUR GAME) to the dashboard, and then download the skin along with your desired animation.
  • Then you’ll have to import the character with the animation into your project, add it to your scene, localize the scene, extract the animation (by saving it as a .res), and then you’ll have to load it into your original AnimationPlayer.
  • Now, add an AnimationTree node.

Book of Nodes

  • Configure the tree_root as an AnimationRootNode for managing states.

Book of Nodes

  • Also assign the AnimationPlayeras the Anim Player property because this is where our AnimationTree will call the animations from, and the Advanced Expression as the root node because this is where our animation coding can be found (our script).

Book of Nodes

  • If you open your AnimationTree, you will see if you right-click you can add animations, blend trees, and state machines. Add a new animation for ‘idle’, ‘walk’, ‘run’, and ‘jump’.

Book of Nodes

Book of Nodes

  • Add a transition between start -> idle. The transition type should be immediatebecause we want the animation to play immediately.

Book of Nodes

  • Add transitions between idle -> walk and vice versa. The transition type for both should be syncbecause we want to blend the animation. Also set the mode to enabledbecause we will activate this animation via the code, and not automatically.

Book of Nodes

Book of Nodes

  • Select your transitions from walk <-> idle, and in the Inspector panel, change the x-fade time to a value such as 0.3. This is the blending time of the transitions, which smoothens out our transition from one animation to the next.

Book of Nodes

Book of Nodes

  • Do the exact same for the transitions between your walk <-> sprint.

Book of Nodes

Book of Nodes

  • Do the same for your jump animation, but it should transition back to all of your other animations (jump -> idle, jump -> walk, jump -> sprint). The transition from your Jump to other animations should be of type At End, because we want to transition back to running, walking, or jumping when the player has finished jumping.

Book of Nodes

  • Then, we only want to JUMP if our player is jumping by pressing the ui_jump key. For this we can make use if an expression. We can enter boolean values in the expressionpanel, which will then check our code for the conditions to play the animation based on this boolean. So, if in our code we say is_jumping = true when we press our jump button, this part of the AnimationTree will execute.
  • Add an expression is_jumping to all of the transitions that go towards your jump animation (idle -> jump, walk -> jump, sprint -> jump).

Book of Nodes

  • Now in our code, we can play our animations.
### Player.gd

extends CharacterBody3D

# Vars
const run_speed = 3.0
const sprint_speed = 5.0
const jump_speed = 3.0
const gravity = 10

@onready var animation_tree = $AnimationTree
@onready var animation_state = animation_tree.get("parameters/playback")
@onready var camera = $ThirdPersonCamera/Camera

var is_jumping = false

func _ready():
 Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _process(delta):
 handle_animation()

func _physics_process(delta):
 handle_movement(delta)

func handle_animation():
 var input_dir = Input.get_vector("ui_right", "ui_left", "ui_down", "ui_up")
 if is_jumping:
  if animation_state.get_current_node() != "jump":
   animation_state.travel("jump")
 elif input_dir.length() > 0:
  if Input.is_action_pressed("ui_sprint"):
   if animation_state.get_current_node() != "sprint":
    animation_state.travel("sprint")
  else:
   if animation_state.get_current_node() != "walk":
    animation_state.travel("walk")
 else:
  if animation_state.get_current_node() != "idle":
   animation_state.travel("idle")

func handle_movement(delta):
 # Check if the player has landed
 if is_on_floor() and velocity.y == 0:
  is_jumping = false
 # Apply gravity
 velocity.y += -gravity * delta

 # Get input direction
 var input_dir = Input.get_vector("ui_left", "ui_right", "ui_down", "ui_up")
 if input_dir.length() > 0:
  # Calculate movement direction based on camera orientation
  var forward = -camera.global_transform.basis.z
  var right = camera.global_transform.basis.x
  forward.y = 0  
  right.y = 0   
  forward = forward.normalized()
  right = right.normalized()
  var movement_dir = (forward * input_dir.y + right * input_dir.x).normalized()

  # Rotate player to face movement direction
  var target_rotation = atan2(movement_dir.x, movement_dir.z)
  rotation.y = lerp_angle(rotation.y, target_rotation, 0.1)

  # Apply movement
  if Input.is_action_pressed("ui_sprint"):
   velocity.x = movement_dir.x * sprint_speed
   velocity.z = movement_dir.z * sprint_speed
  else:
   velocity.x = movement_dir.x * run_speed
   velocity.z = movement_dir.z * run_speed
 else:
  velocity.x = move_toward(velocity.x, 0, run_speed)
  velocity.z = move_toward(velocity.z, 0, run_speed)

 # Handle jumping
 if Input.is_action_just_pressed("ui_jump") and is_on_floor():
  velocity.y = jump_speed
  is_jumping = true

 move_and_slide()
Enter fullscreen mode Exit fullscreen mode
  • Run the scene and control the character to observe the transitions and movement based on our state.

Book of Nodes

CollisionShape3D

The CollisionShape3D node allows you to specify the boundaries of an object for collision detection, which is essential for handling interactions between objects in your game.

Book of Nodes

Mechanic:

Add a collision area to block the character from passing.

Implementation:

  • Create a CollisionShape3D node as a child of a CharacterBody3D, RigidBody3D, orStaticBody3D. These nodes will block other collisions. To have a node pass through collisions, use an Area3D.

Book of Nodes

  • In the Inspector, assign a Shape3D resource to the shape property of the CollisionShape3D. The shape you choose will depend on the shape of your entity. For example, a player might have a capsule shape, a pickup a sphere, an area a box.

Book of Nodes

  • Let’s enable debugging to see our collisions in action. You can also change the color of your debug lines to make it more visible.

Book of Nodes

Book of Nodes

  • Run your scene to see how your player interacts with the collision shape. Since we used a StaticBody3D node, they should be blocked and unallowed to go through the collision.

Book of Nodes

CharacterBody3D

The CharacterBody3D node is a specialized class for physics bodies that are meant to be controlled or moved around by the user. Unlike other physics bodies such as the RigidBody3D or StaticBody3D node, CharacterBody3D is not affected by the engine’s physics properties like gravity or friction by default. Instead, you have to write code to control its behavior, giving you precise control over how it moves and reacts to collisions.

Book of Nodes

Mechanic:

Move a character with arrow keys, including handling gravity and jumping.

Implementation:

  • Create a CharacterBody3D node in your scene.

Book of Nodes

  • You’ll see it has a warning icon next to it. This is because it needs a collision shape to be able to interact with the world. Add a CollisionShape3D as a child of the CharacterBody3D and set its shape to match your character.

Book of Nodes

  • Add a MeshInstance3D node to this scene so that we can see our character.

Book of Nodes

  • You’ll also need to attach a camera to your character so we can see them.

Book of Nodes

  • Attach a script to the CharacterBody3D to handle movement and jumping.
# Character.gd

extends CharacterBody3D

# Variables
@export var speed = 5.0
@export var jump_force = 10.0
@export var gravity = 10.0

# Input for movement
func get_input():
 velocity.x = 0
 velocity.z = 0
 if Input.is_action_pressed("ui_up"):
  velocity.z -= speed
 if Input.is_action_pressed("ui_down"):
  velocity.z += speed
 if Input.is_action_pressed("ui_left"):
  velocity.x -= speed
 if Input.is_action_pressed("ui_right"):
  velocity.x += speed
 if is_on_floor() and Input.is_action_just_pressed("ui_accept"):
  velocity.y = jump_force

# Movement & Gravity
func _physics_process(delta):
 get_input()
 velocity.y -= gravity * delta
 move_and_slide()
Enter fullscreen mode Exit fullscreen mode
  • Run the scene and use the arrow keys to move the character and make it jump.

Book of Nodes

StaticBody3D

The StaticBody3D node is used to represent objects that do not move. This node is ideal for creating static elements in your game, such as walls, floors, and other immovable objects such as chests.

Book of Nodes

Mechanic:

Create an obstacle.

Implementation:

  • Create a StaticBody3D node in your scene. Add a CollisionShape3Das a child of the StaticBody and set its shape to match the obstacle.

Book of Nodes

  • Give it a MeshInstance3Dof your choice so that we can see the item.

Book of Nodes

  • Run your scene to see how your player interacts with the collision shape. They should be blocked and unallowed to go through the obstacle.

Book of Nodes

RigidBody3D

The RigidBody3D node is used for objects that are affected by the engine’s physics. These bodies can move, rotate, and respond to forces and collisions. They are ideal for creating dynamic objects that need realistic physics interactions, such as balls, bullets, moveable obstacles, etc.

Book of Nodes

Mechanic:

Create a moveable obstacle.

Implementation:

  • Create a RigidBody3D node in your scene. Add a CollisionShape3Das a child of the RigidBody and set its shape to match the obstacle.

Book of Nodes

  • Give it a MeshInstance3Dof your choice so that we can see the item.

Book of Nodes

  • Since we don’t want the object to fly into space when we collide with it, we will need to increase its mass value.

Book of Nodes

  • Use GDScript to apply forces or impulses to the rigid body if the player pushes it.
### Player.gd

extends CharacterBody3D

# Vars
const run_speed = 3.0
const sprint_speed = 5.0
const jump_speed = 3.0
const gravity = 10

@export var push_force = 80.0

@onready var animation_tree = $AnimationTree
@onready var animation_state = animation_tree.get("parameters/playback")
@onready var camera = $ThirdPersonCamera/Camera

var is_jumping = false

func _ready():
 Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _process(delta):
 handle_animation()

func _physics_process(delta):
 handle_movement(delta)
 handle_collisions()

func handle_animation():
 var input_dir = Input.get_vector("ui_right", "ui_left", "ui_down", "ui_up")
 if is_jumping:
  if animation_state.get_current_node() != "jump":
   animation_state.travel("jump")
 elif input_dir.length() > 0:
  if Input.is_action_pressed("ui_sprint"):
   if animation_state.get_current_node() != "sprint":
    animation_state.travel("sprint")
  else:
   if animation_state.get_current_node() != "walk":
    animation_state.travel("walk")
 else:
  if animation_state.get_current_node() != "idle":
   animation_state.travel("idle")

func handle_movement(delta):
 # Check if the player has landed
 if is_on_floor() and velocity.y == 0:
  is_jumping = false
 # Apply gravity
 velocity.y += -gravity * delta

 # Get input direction
 var input_dir = Input.get_vector("ui_left", "ui_right", "ui_down", "ui_up")
 if input_dir.length() > 0:
  # Calculate movement direction based on camera orientation
  var forward = -camera.global_transform.basis.z
  var right = camera.global_transform.basis.x
  forward.y = 0  
  right.y = 0   
  forward = forward.normalized()
  right = right.normalized()
  var movement_dir = (forward * input_dir.y + right * input_dir.x).normalized()

  # Rotate player to face movement direction
  var target_rotation = atan2(movement_dir.x, movement_dir.z)
  rotation.y = lerp_angle(rotation.y, target_rotation, 0.1)

  # Apply movement
  if Input.is_action_pressed("ui_sprint"):
   velocity.x = movement_dir.x * sprint_speed
   velocity.z = movement_dir.z * sprint_speed
  else:
   velocity.x = movement_dir.x * run_speed
   velocity.z = movement_dir.z * run_speed
 else:
  velocity.x = move_toward(velocity.x, 0, run_speed)
  velocity.z = move_toward(velocity.z, 0, run_speed)

 # Handle jumping
 if Input.is_action_just_pressed("ui_jump") and is_on_floor():
  velocity.y = jump_speed
  is_jumping = true

 move_and_slide()

# Handle Collisions
func handle_collisions():
 for i in range(get_slide_collision_count()):
  var collision = get_slide_collision(i)
  if collision.get_collider() is RigidBody3D:
   var collider = collision.get_collider() as RigidBody3D
   var impulse = -collision.get_normal() * push_force
   collider.apply_central_impulse(impulse)
Enter fullscreen mode Exit fullscreen mode
  • Run the scene and observe how the obstacle moves when the player pushes against it.

Book of Nodes

VehicleBody3D

TheVehicleBody3D node is used to simulate a 3D vehicle with realistic physics. It requires VehicleWheel3D nodes for each wheel to function correctly.

On our VehicleBody3D node, the most important properties are the engine_force, steer_angle, and brake_force properties. The engine_force allows our vehicle to move forward/backward. The steer_angle/steering properties allow our vehicle to move left/right. The brake_force property allows our car to stop.

Mechanic:

Create a controllable vehicle with wheels.

Implementation:

  • For this part, you will need to download a car and import it into your project. If you’re using my base template, I recommend downloading the Car Kit from Kenney and importing it into your project.

Book of Nodes

Book of Nodes

  • Create a VehicleBody3D node in your scene. You will need to give it a CollisionShape3D, and a mesh (use the one you imported).

Book of Nodes

  • Add VehicleWheel3D nodes as children of the VehicleBody3D node for each wheel. Name them FrontLWheel, BackLWheel, FrontRWheel, BackRWheel. Also move them into the positions of your wheels.

Book of Nodes

  • You’ll need to set your front wheels as the steering wheels, and your back wheels as your traction wheels.

Book of Nodes

Book of Nodes

  • Also attach a camera to your vehicle so you can follow it around.

Book of Nodes

  • Attach a script to your vehicle to handle the vehicle’s movement and control. We’ll move our car with our arrow keys, and brake with our spacebar.
# VehicleBody.gd

extends VehicleBody3D

@onready var front_l_wheel = $FrontLWheel
@onready var front_r_wheel = $FrontRWheel
@onready var back_r_wheel = $BackRWheel
@onready var back_l_wheel = $BackLWheel

# Variables
@export var new_engine_force = 1000.0
@export var brake_force = 500.0
@export var steer_angle = 0.5

func _process(delta):
 handle_input()

func handle_input():
 var engine = 0.0
 var brake = 0.0
 var steer = 0.0

 if Input.is_action_pressed("ui_up"):
  engine = new_engine_force
 if Input.is_action_pressed("ui_down"):
  engine = -new_engine_force
 if Input.is_action_pressed("ui_left"):
  steer = steer_angle
 if Input.is_action_pressed("ui_right"):
  steer = -steer_angle
 if Input.is_action_pressed("ui_accept"):
  brake = brake_force

 front_l_wheel.engine_force = engine
 front_r_wheel.engine_force = engine
 front_l_wheel.brake = brake
 front_r_wheel.brake = brake
 front_l_wheel.steering = steer
 front_r_wheel.steering = steer
Enter fullscreen mode Exit fullscreen mode
  • Run your scene and try to control your vehicle. It should move around your map when controlled!

Book of Nodes

Area3D

The Area3D node is used to detect when objects enter or exit a defined area. They do not represent physical bodies but are useful for triggering events such as cutscenes or map transitions, detecting overlaps, and creating zones for things such as enemy or loot spawning.

Book of Nodes

We can use the Area3D node’s on_body_entered() and on_body_exited() signals to determine whether or not a PhysicsBody has entered this zone.

Book of Nodes

Mechanic:

Create a trigger zone that detects when the player enters a specific area.

Implementation:

  • Create an Area3D node in your scene. You also need to add a CollisionShape3D as a child of the Area and set its shape to define the trigger zone. Adjust the collision shape's properties to fit the dimensions of your trigger zone.

Book of Nodes

Book of Nodes

  • Attach the Area3D node’s on_body_entered() and on_body_exited() signals to your script.
  • Use GDScript to notify us when the Player enters or exits the area.
### Main.gd

extends Node3D

func _on_area_3d_body_entered(body):
 if body.name == "Player":
  print("The player has entered the area!")

func _on_area_3d_body_exited(body):
 if body.name == "Player":
  print("The player has exited the area!")
Enter fullscreen mode Exit fullscreen mode
  • Enable debugging so we can see when our Player enters/exits our area.

Book of Nodes

  • Run the scene and observe how the area detects when the Player enters or exits the defined zone. Each time the player enters/exits the zone, the game should be notified.

Book of Nodes

Book of Nodes

RayCast3D

The RayCast3D node is used to cast a ray in a 3D space to detect objects along its path. This is useful for various purposes such as line-of-sight checks, shooting and attacking mechanics, and collision detection.

It can collide with bodies such as StaticBody3D (useful for detecting loot and quest items), CharacterBody3D (useful for detecting interactions with enemies and NPCs), and RigidBody3D (useful for detecting interactions with moveable objects. It can also collide with areas, such as Area3D (useful for interactions with trigger zones).

Book of Nodes

Mechanic:

Cast a ray from the player and detect what it hits.

Implementation:

  • Add a RayCast3D node to the Player in your scene.

Book of Nodes

  • Set the target_position property face the direction of your player (z: 1). You can also enable its collision with areas.

Book of Nodes

Book of Nodes

  • Now we can print what we are colliding with in our code.
### Player.gd

extends CharacterBody3D

# Vars
const run_speed = 3.0
const sprint_speed = 5.0
const jump_speed = 3.0
const gravity = 10

@onready var animation_tree = $AnimationTree
@onready var animation_state = animation_tree.get("parameters/playback")
@onready var camera = $ThirdPersonCamera/Camera
@onready var ray_cast_3d = $RayCast3D

var is_jumping = false

func _ready():
 Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _process(delta):
 handle_animation()

    if ray_cast_3d.is_colliding():
        var collider = ray_cast_3d.get_collider()
        print("Raycast hit: ", collider.name)
Enter fullscreen mode Exit fullscreen mode
  • Run your scene and interact with objects that have colliders. The raycast should detect the objects and notify the game.

Book of Nodes

Book of Nodes

Camera3D

The Camera3D node is used to control the view of a 3D scene. It allows the screen to follow the player or other objects. Only one Camera can be active per viewport, and it registers itself in the nearest Viewport node.

In third-person games , we usually attach the Camera3D node to a SpringArm3D node. The SpringArm node reacts to collisions in the environment. It ensures that the camera moves closer to the player when there are obstacles, preventing the camera from clipping through objects.

In first-person games , we usually attach the Camera3D directly to our Pivot — such as our Player head. This way we “see” from the player’s perspective.

In “God-Mode” , we attach the Camera to our World scene so that we can get a birds-eye view of the environment. This usually requires a bit more configuration and coding, as you have to make the camera able to move, rotate, and zoom.

Book of Nodes

Mechanic:

Create a “God Mode” 3D camera that can move, zoom, and rotate based on user input.

Implementation:

  • Add a Camera3D node to your Main (World) scene. Make sure this camera is set to ‘current’, and all other cameras are disabled.
  • Also move the camera up on the y-axis (10), and rotate it slightly on the x-axis (-45) to point down at your world.

Book of Nodes

  • Add the inputs to zoom, move, and rotate your camera.

Book of Nodes

Book of Nodes

  • Use GDScript to handle the camera’s zoom, movement, and rotation. You can do this in a custom Camera.gd script (preferred), or directly in your root script.
### Main.gd

extends Node3D

@onready var camera_3d = $Camera3D

@export var zoom_speed = 50.0
@export var move_speed = 5.0 # Adjusted to a more reasonable value
@export var rotate_speed = 0.5 # Adjusted to a more reasonable value
@export var min_zoom = 10.0
@export var max_zoom = 100.0

var target_fov = 70.0
var camera_drag = Vector2.ZERO
var camera_movement = Vector3.ZERO
var sensibility = 0.001

func _process(delta):
 handle_zoom(delta)
 handle_movement(delta)
 handle_rotation(delta)

func handle_zoom(delta):
 if Input.is_action_pressed("zoom_in"):
  camera_3d.fov -= zoom_speed * delta
 if Input.is_action_pressed("zoom_out"):
  camera_3d.fov += zoom_speed * delta
 camera_3d.fov = clamp(camera_3d.fov, min_zoom, max_zoom)

func handle_movement(delta):
 if Input.is_action_just_pressed("camera_drag"):
  camera_drag = get_viewport().get_mouse_position()
 if Input.is_action_pressed("camera_drag"):
  camera_movement.x += (get_viewport().get_mouse_position().x - camera_drag.x) * sensibility
  camera_movement.z += (get_viewport().get_mouse_position().y - camera_drag.y) * sensibility
 camera_3d.position += camera_movement * move_speed * delta
 camera_movement = lerp(camera_movement, Vector3.ZERO, 0.2)

func handle_rotation(delta):
 if Input.is_action_pressed("rotate_left"):
  camera_3d.rotate_y(rotate_speed * delta)
 if Input.is_action_pressed("rotate_right"):
  camera_3d.rotate_y(-rotate_speed * delta)
Enter fullscreen mode Exit fullscreen mode
  • Run the scene and use the defined input actions to move, zoom, and rotate the camera.

Book of Nodes

DirectionalLight3D

The DirectionalLight3D node is used to simulate sunlight or moonlight in our 3D environment. It emits light in a specific direction, affecting all objects in the scene equally, regardless of their distance from the light source. This type of light is useful for outdoor scenes where you need consistent lighting across the entire scene.

This node’s two main properties are the energy and color properties. The energy property determines how bright/dim the light is, and the color is the shading of the light. You can also change the Mode of the shadows, which will change the way your shadows are rendered. By default, PSSM 2 is a good option to choose.

Book of Nodes

In 3D environments, your light will also be influenced by your WorldEnvironment node, which contains the sky of your world.

Mechanic:

Illuminate a scene with sunlight.

Implementation:

  1. Create a DirectionalLight3D node in your scene.

Book of Nodes

Book of Nodes

  • By default, the light and energy looks pretty good. We can change these values if you want. For instance, we can make the energy brighter and the color darker to simulate night time.

Book of Nodes

  • Now let’s do something fun. I recommend using shaders with this node, but just for testing sake, let’s have it randomize its color each second.
  • Add a Timer node to your scene. In the Inspector Panel, enable autostart, and connect its timeout signal to your script

Book of Nodes

Book of Nodes

Book of Nodes

  • In your code, let’s randomize our light’s color every time the timer times out (every second).
### Main.gd

extends Node3D

@onready var directional_light_3d = $World/DirectionalLight3D

func _on_timer_timeout():
 directional_light_3d.color = Color(randf(), randf(), randf()
Enter fullscreen mode Exit fullscreen mode
  • Run your scene and enjoy the overstimulation.

Book of Nodes

SpotLight3D

The SpotLight3D node emits light in a cone shape, illuminating objects within the cone’s range. This type of light is useful for creating focused lighting effects, such as flashlights or stage lights.

This node’s two main properties are the energy , color, specular, and range, and projectorproperties. The energy property determines how bright/dim the light is. The color is the shading of the light. The specular property affects how reflective surfaces appear under the spotlight. The range property defines the maximum distance the light can reach. The projector property allows us to give our light a texture, such as stained glass or a vignette effect.

Book of Nodes

Mechanic:

Create a flashlight attached to your player.

Implementation:

  • To your player, attach a Spotlight3D node. Move this node into one of their hands, and rotate it on the y-axis (180) so that it faces the direction that the player is facing.

Book of Nodes

  • Play around with the energy, range, angle, color, and specular values.

Book of Nodes

Book of Nodes

  • This light looks too bright. It needs to look more like a flashlight. Download this texture, and drag it into your projector property.

Book of Nodes

Book of Nodes

  • Now, let’s move this flashlight when we move our camera.
### Player.gd

extends CharacterBody3D

# Vars
const run_speed = 3.0
const sprint_speed = 5.0
const jump_speed = 3.0
const gravity = 10

@onready var animation_tree = $AnimationTree
@onready var animation_state = animation_tree.get("parameters/playback")
@onready var camera = $ThirdPersonCamera/Camera
@onready var flashlight = $SpotLight3D

var is_jumping = false

func _ready():
 Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _process(delta):
 handle_animation()

func _physics_process(delta):
 handle_movement(delta)

func handle_animation():
 var input_dir = Input.get_vector("ui_right", "ui_left", "ui_down", "ui_up")
 if is_jumping:
  if animation_state.get_current_node() != "jump":
   animation_state.travel("jump")
 elif input_dir.length() > 0:
  if Input.is_action_pressed("ui_sprint"):
   if animation_state.get_current_node() != "sprint":
    animation_state.travel("sprint")
  else:
   if animation_state.get_current_node() != "walk":
    animation_state.travel("walk")
 else:
  if animation_state.get_current_node() != "idle":
   animation_state.travel("idle")

func handle_movement(delta):
 # Check if the player has landed
 if is_on_floor() and velocity.y == 0:
  is_jumping = false
 # Apply gravity
 velocity.y += -gravity * delta

 # Get input direction
 var input_dir = Input.get_vector("ui_left", "ui_right", "ui_down", "ui_up")
 if input_dir.length() > 0:
  # Calculate movement direction based on camera orientation
  var forward = -camera.global_transform.basis.z
  var right = camera.global_transform.basis.x
  forward.y = 0  
  right.y = 0   
  forward = forward.normalized()
  right = right.normalized()
  var movement_dir = (forward * input_dir.y + right * input_dir.x).normalized()

  # Rotate player to face movement direction
  var target_rotation = atan2(movement_dir.x, movement_dir.z)
  rotation.y = lerp_angle(rotation.y, target_rotation, 0.1)

  # Apply movement
  if Input.is_action_pressed("ui_sprint"):
   velocity.x = movement_dir.x * sprint_speed
   velocity.z = movement_dir.z * sprint_speed
  else:
   velocity.x = movement_dir.x * run_speed
   velocity.z = movement_dir.z * run_speed
 else:
  velocity.x = move_toward(velocity.x, 0, run_speed)
  velocity.z = move_toward(velocity.z, 0, run_speed)

 # Handle jumping
 if Input.is_action_just_pressed("ui_jump") and is_on_floor():
  velocity.y = jump_speed
  is_jumping = true

 move_and_slide()

 # Rotate flashlight to match camera orientation
 flashlight.global_rotation = camera.global_rotation
Enter fullscreen mode Exit fullscreen mode
  • Run your scene and move your camera. Your flashlight should shine in that direction.

Book of Nodes

OmniLight3D

The OmniLight3D node emits light in all directions from a single point, similar to a light bulb. This type of light is useful for creating ambient lighting or point light sources.

This node’s two main properties are the energy , color, specular, and range, and projectorproperties. The energy property determines how bright/dim the light is. The color is the shading of the light. The specular property affects how reflective surfaces appear under the spotlight. The range property defines the maximum distance the light can reach.

Book of Nodes

Mechanic:

Create a flickering torch.

Implementation:

  • In your scene, add a OmniLight3D node. Move it to where you want your light to shine from.

Book of Nodes

  • Play around with the energy, range,color, and specular values.

Book of Nodes

  • Just for fun, let’s give it a flicker effect. We’ll do this via an AnimationPlayer node.
  • Create a new animation in the AnimationPlayer.
  • Add a track for the energy property of the OmniLight3D.
  • Add keyframes to the energy track to simulate flickering.
  • Enable looping.

Book of Nodes

Book of Nodes

Book of Nodes

Book of Nodes

  • Then play this animation via the code when the game loads.
### Main.gd

extends Node3D

@onready var animation_player = $AnimationPlayer

func _ready():
 animation_player.play("flicker")
Enter fullscreen mode Exit fullscreen mode
  • Run the scene and observe the player’s color changes when they go into the flickering lights.

Book of Nodes

BoneAttachment3D

When importing a 3D model with a skeleton from 3D software such as Blender, the Skeleton3Dnode should automatically be populated with bones if the model is correctly rigged, exported, and imported. Bone3Dare the parts of the body — such as the hands, arms, legs, etc.

The BoneAttachment3Dnode is used to attach nodes to specific bones in a Skeleton3D. This allows you to dynamically attach objects to bones, which will follow the bone’s transformations. For example, we can attach a flashlight to our player’s hand, or a bow on our player’s back.

Book of Nodes

If we look at the Player character in the base template, we can see that their Skeleton3D node from is already populated with bones. This came from the model (.glb) itself — we didn’t have to manually create this in Godot.

Book of Nodes

Mechanic:

Attach a weapon to the right hand of our player.

Implementation Steps

  • In your Player scene, add aBoneAttachment3D node as a child of the Skeleton3D node.

Book of Nodes

  • Set the bone_name property of theBoneAttachment3D to the name of the bone you want to attach to. In our case, it should be the arm-right.

Book of Nodes

Book of Nodes

  • Add the objects you want to attach as children of the BoneAttachment3D nodes. In our case, we want to add a weapon. Since we don’t have weapons in the base template, we will use the cane instead.

Book of Nodes

  • Move the cane into the players hand.

Book of Nodes

  • In our script, let’s handle the attachment and detachment of this “weapon”. We also need to add an input to handle this action.

Book of Nodes

### Player.gd

extends CharacterBody3D

# Vars
const run_speed = 3.0
const sprint_speed = 5.0
const jump_speed = 3.0
const gravity = 10

@onready var animation_tree = $AnimationTree
@onready var animation_state = animation_tree.get("parameters/playback")
@onready var camera = $ThirdPersonCamera/Camera

@onready var bone_attachment_3d = $"character-female-b2/character-female-b/Skeleton3D/BoneAttachment3D"

## Older code

func _input(event):
 if event.is_action_pressed("ui_equip"):
  equip_object()

func equip_object():
 bone_attachment_3d.visible = !bone_attachment_3d.visible
Enter fullscreen mode Exit fullscreen mode
  • Run your scene. Your weapon should equip/unequip if you press TAB. When you move, the weapon should also move naturally with your bone.

Book of Nodes

AudioStreamPlayer3D and AudioStreamPlayer

These nodes are used to play audio in our games. We use the AudioStreamPlayer to play audio equally across our scene (such as background music or ambient sounds), and the AudioStreamPlayer3D to play audio positionally (such as from our players or NPCs).

Mechanic:

Play ambient music in the background, and sounds from the player when they move.

Implementation:

  • Download your sound effects. You can find free ones on Pixabay. Look for ones that work well in the background (they loop), and ones that are short effects, such as jumping sounds.

Book of Nodes

Book of Nodes

  • Add an AudioStreamPlayer node to play background audio. Add an AudioStreamPlayer3D node to play positional audio.

Book of Nodes

  • You will need to reimport your audio that is supposed to loop. Double click it, and enable looping.

Book of Nodes

  • Set the stream property to the desired audio file.

Book of Nodes

Book of Nodes

  • Adjust properties like volume_db, and pitch_scale if needed. We’ll enable autoplay on our AudioStreamPlayer node since that is our background music.

Book of Nodes

  • We also want to enable the emission property on our AudiostreamPlayer3D so that the audio can sound more distanced.

Book of Nodes

  • We will play our sound effect audio (AudioStreamPlayer3D) when our player enters a certain area. To do this, add an Area3D node to your scene with a collision body, and attach its on_body_entered() signal to your script.

Book of Nodes

Book of Nodes

  • Now play the audio when the player enters the area.
### Main.gd

extends Node3D

@onready var audio_stream_player_3d = $AudioStreamPlayer3D

func _on_area_3d_body_entered(body):
 if body.name == "Player":
  audio_stream_player_3d.play()
Enter fullscreen mode Exit fullscreen mode
  • Run your scene. The background music should play, and the sound effect should play when your player enters the area.

WorldEnvironment

The WorldEnvironment node configures the default environment settings for a scene, including lighting, post-processing effects, and background settings. This node allows you to add a bunch of environmental effects, such as a skybox, fog, shadows, etc.

Everything you see in the screenshot below (the sky, light, fog) was done with the help of the WorldEnvironment node.

Book of Nodes

The most common properties you will change on this node will be:

Fog

  • Purpose: Fog makes distant objects fade into a uniform color, simulating atmospheric effects.
  • Key Properties:
  • Light Color: The color of the fog.
  • Density: The ‘thickness’ of the fog until it is fully opaque.
  • Sky Affect: Adjusts the fog color based on the sun’s energy.

Volumetric Fog

  • Purpose: Volumetric fog provides a more realistic fog effect by interacting with the lights in the scene.
  • Key Properties:
  • Density: The base exponential density of the volumetric fog.
  • Albedo: The color of the fog when interacting with lights.
  • Emission: The emitted light from the fog, useful for establishing ambient color.
  • Emission Energy: The brightness of the emitted light.
  • Anisotropy: The direction of scattered light through the fog.
  • Length: The distance over which the fog is computed.
  • Detail Spread: The distribution of detail in the fog.
  • Ambient Inject: Scales the strength of ambient light used in the fog.
  • Sky Affect: Controls how much the fog affects the background sky.

Glow

  • Purpose: Glow adds bloom effects to bright areas, enhancing the visual appeal.
  • Key Properties:
  • Intensity: The strength of the glow effect.
  • Strength: The brightness for the glow effect.
  • Blend Mode: The blending mode for the glow effect.

Tonemapping

  • Purpose: Tonemapping adjusts the color balance and contrast of the scene.
  • Key Properties:
  • Mode: The tone-mapping algorithm (e.g., Linear, Reinhard, Filmic).
  • Exposure: Adjusts the overall exposure of the scene.

Ambient Light

  • Purpose: Ambient light provides a base level of illumination for the scene, ensuring that no part of the scene is completely dark.
  • Key Properties:
  • Color: The color of the ambient light.
  • Energy: The intensity of the ambient light.
  • Sky Contribution: The amount of light contributed by the sky.

Sky

  • Purpose: The sky provides the background for the scene, which can be a solid color, a skybox, or a custom shader.
  • Key Properties:
  • Sky: The type of sky (e.g., PanoramaSky, ProceduralSky).
  • Custom FOV: The intensity of the sky’s contribution to the scene lighting.

Background

  • Purpose: The background sets the visual backdrop for the scene, which can be a solid color, a sky, or a custom shader.
  • Key Properties:
  • Mode: The background mode (e.g., Clear Color, Sky, Custom Color).
  • Energy Multiplier: The intensity of the background’s contribution to the scene lighting.

Mechanic:

Create a spooky atmosphere.

Implementation:

  • Add a WorldEnvironment node to your scene. To this node, add an Environment resource and configure it.

Book of Nodes

Book of Nodes

  • Set the background to be a color. Set the color to be black.

Book of Nodes

  • Change the ambient light color and energy.

Book of Nodes

Book of Nodes

  • Everything is too dark. Let’s enable our fog and change our properties.

Book of Nodes

Book of Nodes

  • Let’s lighten the mood. Enable the glow property, and change its properties.

Book of Nodes

Book of Nodes

  • Run your scene. Your game should look a little bit more spooky now!

Book of Nodes

NavigationAgent3D, NavigationObstacle3D, NavigationRegion3D

The NavigationAgent, NavigationObstacle, and NavigationRegion nodes are used to manage navigation and pathfinding in both 2D and 3D environments. These nodes help create dynamic and realistic movement for characters and objects, allowing them to navigate around obstacles and follow paths.

  • The NavigationAgent3Dnode is used to move characters along a path while avoiding obstacles.
  • The NavigationObstacle3D node is used to create obstacles that navigation agents will avoid.
  • The NavigationRegion3D node defines areas where navigation is allowed or restricted.

Book of Nodes

These three nodes combined allow us to create a more immersive world through mechanics such as NPC and Enemy roaming, particle movements, and controlled entity spawning.

Mechanic:

Create an NPC that roams around a certain area on the map.

Implementation:

  • In your Main scene, add a NavigationRegion3D to your scene to define the roaming area.

Book of Nodes

  • Create a new NavigationMesh resource for this node so that we can define our region. Also move it to where you want your region to be.

Book of Nodes

Book of Nodes

  • Now to actually add our region, we’ll need to add our “floor” as a child of this NavigationRegion node. Since I’m not sure if you are using Terrains or GridMaps, we’ll use a MeshInstance3D for this.
  • Add a MeshInstance3D node as a child to this node. Change its size to something like 10 x 10 m.

Book of Nodes

  • Move your region so the floor is below the grass.

Book of Nodes

  • Now all we need to do is select our NavigationRegion node and select “Bake Navigation”. You’ll see a blue-colored polygon get drawn over our floor, that is our navigation region!

Book of Nodes

  • In a new scene, create your NPC using a CharacterBody3Dnode as the root node. Add the collisions and animations for this entity just as you did for your player.

Book of Nodes

  • To your NPC scene, add a NavigationAgent3D node. The NPC will be assigned to this agent so that they can roam in the region. Enable avoidance for this NPC so that they can avoid obstacles.

Book of Nodes

Book of Nodes

  • Attach a script to your NPC. We will then need to connect our signals from our NavigationAgent3D node to 1) compute the avoidance velocity of our NPC, and 2) redirect our NPC when that target is reached. For moving the NPC whilst avoiding obstacles, attach the velocity_computed signal to your script. For redirecting the NPC, attach the navigation_finished signal to your script.

Book of Nodes

Book of Nodes

  • We also want our NPC to pause before redirecting. To do this, we will add a Timer node to our scene. Enable its one_shot property, and change its wait_time to however long you want the NPC to wait before roaming again.

Book of Nodes

  • Also attach its timeout() signal to your script.

Book of Nodes

  • Now add your roaming functionality.
### NPC.gd

extends CharacterBody3D

@onready var navigation_agent_3d = $NavigationAgent3D
@onready var navigation_region = $"../NavigationRegion3D/MeshInstance3D"
@onready var animation_tree = $AnimationTree
@onready var animation_state = animation_tree.get("parameters/playback")
@onready var timer = $Timer

# Variables
@export var movement_speed: float = 1.0
var roaming_area: AABB
var target_position: Vector3

func _ready():
 # Add a delay to ensure the navigation map is loaded
 await get_tree().create_timer(1).timeout
 set_roaming_area()
 set_random_target()

func _physics_process(delta):
 # Move NPC towards the target
 var next_path_position: Vector3 = navigation_agent_3d.get_next_path_position()
 var new_velocity: Vector3 = global_position.direction_to(next_path_position) * movement_speed
 if navigation_agent_3d.avoidance_enabled:
  navigation_agent_3d.velocity = new_velocity
 else:
  _on_navigation_agent_3d_velocity_computed(new_velocity)

 # Rotate NPC to face the direction of movement
 if velocity.length() > 0:
  var target_rotation = atan2(velocity.x, velocity.z)
  rotation.y = lerp_angle(rotation.y, target_rotation, 0.1)

 # Play walking animation
 if velocity != Vector3.ZERO:
  animation_state.travel("walk")
 else:
  animation_state.travel("idle")

 move_and_slide()

func set_roaming_area():
 # Set the roaming area based on the MeshInstance3D's bounding box
 roaming_area = navigation_region.get_aabb()
 print("Roaming area: ", roaming_area)

func set_random_target():
 # Set next roaming position
 target_position = Vector3(
  randf_range(-roaming_area.position.x, roaming_area.position.x),
  randf_range(-roaming_area.position.y, roaming_area.position.y),
  randf_range(-roaming_area.position.z, roaming_area.position.z)
 )
 navigation_agent_3d.set_target_position(target_position)

func _on_navigation_agent_3d_velocity_computed(safe_velocity):
 # Move NPC
 velocity = safe_velocity

func _on_timer_timeout():
 # Move NPC again
 set_random_target()

func _on_navigation_agent_3d_navigation_finished():
 # When path reached, redirect NPC
 velocity = Vector3.ZERO
 animation_state.travel("idle")
 timer.start()
Enter fullscreen mode Exit fullscreen mode
  • Instance your NPC in your Main scene. Move them into your region.

Book of Nodes

  • Optionally, add NavigationObstacle3D nodes to create obstacles. Add this node to a mesh with a collision shape.

Book of Nodes

Book of Nodes

  • Run your scene and see your NPC randomly roam. They should avoid your obstacles.

Book of Nodes

Path3D and PathFollow3D

The Path3D and PathFollow3D nodes work together to create and follow paths in a 3D space. The Path3D node is used to define a path using a sequence of points. I like this more than using a NavMesh because it allows you to create and visualize a path in the Godot editor, instead of randomizing it. The PathFollow3D node is used to make an object follow a path defined by a Path3D node.

Book of Nodes

Mechanic:

Create an NPC that roams on a defined path on the map.

Implementation:

  • Create a Path3D node in your scene.

Book of Nodes

  • Add a PathFollow3D node as a child of the Path3D.

Book of Nodes

  • Enable its use_model_front property so that our NPC will always move whilst facing the front.

Book of Nodes

  • In a new scene, create your NPC using a CharacterBody3Dnode as the root node. Add the collisions and animations for this entity just as you did for your player.

Book of Nodes

  • Attach your NPC to your PathFollow3D node. This will tell the game that this is the object that should follow this Path.

Book of Nodes

Book of Nodes

  • Now we can draw our path. In the Godot editor, select the Path3D node. Use the “Add Point” button in the toolbar to add points to draw the path shape that your NPC has to follow. Select the point to move it on your map.

Book of Nodes

Book of Nodes

Book of Nodes

  • Add more points to complete your path.

Book of Nodes

Book of Nodes

  • Make sure your path is on the ground, otherwise your NPC will fly!

Book of Nodes

Book of Nodes

  • With your path created, attach a script to your NPC. Then, add the logic for them to move along the path.
### NPC.gd

extends CharacterBody3D

@onready var animation_tree = $AnimationTree
@onready var animation_state = animation_tree.get("parameters/playback")
@onready var path_follow = get_parent()

# Vars
@export var movement_speed: float = 2.0
var current_offset: float = 0.0
var path_length: float = 0.0
var direction: int = 1
var previous_position: Vector3 = Vector3.ZERO

func _ready():
 # Get the total length of the path
 path_length = path_follow.get_parent().curve.get_baked_length()

func _physics_process(delta):
 # Update the progress along the path
 update_path_progress(delta)

 # Calculate the velocity based on the change in position
 var current_position = path_follow.global_transform.origin
 var velocity = (current_position - previous_position) / delta
 previous_position = current_position

 # Update the animation based on the velocity
 update_animation(velocity)

 # Update the NPC's position to follow the PathFollow3D node
 global_transform.origin = current_position

 # Rotate NPC to face the direction of movement
 if velocity.length() > 0:
  var target_rotation = atan2(velocity.x, velocity.z)
  rotation.y = lerp_angle(rotation.y, target_rotation, 0.1)

 move_and_slide()

func update_path_progress(delta):
 current_offset += movement_speed * delta * direction

 # Reverse direction if the end or start of the path is reached
 if current_offset >= path_length or current_offset <= 0:
  direction *= -1 # Reverse the direction
 current_offset = clamp(current_offset, 0, path_length)

 # Update the progress of the PathFollow3D node
 path_follow.progress = current_offset

func update_animation(velocity: Vector3):
 if velocity.length() == 0:
  animation_state.travel("idle")
 else:
  animation_state.travel("walk")
  animation_tree.set("parameters/walk/blend_position", velocity.normalized())
Enter fullscreen mode Exit fullscreen mode
  • Run your scene and see your NPC roam. They should follow your path in a zig-zag (back-and-forth) motion.

Book of Nodes

GridMap

The GridMap node allows us to create 3D grid-based levels. With it, we can place our 3D models (tiles) on a grid interactively, similar to how TileMap works in 2D. If you have suitable models, you can use them to create, design, and manage large, repetitive environments like dungeons, cities, or landscapes.

The Gridmap is composed of cells. Each cell has the same dimensions.

Book of Nodes

These cells are formed using floors (horizontal grid), and planes (vertical grid). Each floor represents a different height level, whereas each plane represents a different depth level.

Book of Nodes

The GridMap uses a MeshLibrary Resource that contains an array of 3D tiles that can be placed on cells on the grid. Each tile is a mesh with materials, and it can also include optional collision and navigation shapes.

Book of Nodes

In my 3D base template project, you will see that my entire world was made using several GridMap nodes.

Book of Nodes

Book of Nodes

All of these tiles that you see on the screen were originally .glb models.

Book of Nodes

We add all of these models to a new scene, add whatever collisions we want, and then we convert this entire scene into MeshLibrary Resources so that they can be used by our GridMap as tiles.

Book of Nodes

Book of Nodes

Book of Nodes

Book of Nodes

Mechanic:

Create a grid-based map.

Implementation:

  • To begin, we need a MeshLibrary, which is a collection of individual meshes that can be used in the GridMap.
  • Create a new scene with a Node3D node as the root node. Rename this node to “GridTiles”.

Book of Nodes

  • Import any .glb asset that you want to use, and then drag it into this new scene.

Book of Nodes

  • If you added meshes that need to block the player or the player should be able to walk on it (such as the ground), you’ll have to localize these scenes and add a collision shape to them.

Book of Nodes

Book of Nodes

Book of Nodes

  • After this is done, you can navigate to Scene > Export As and export this scene as a MeshLibrary.

Book of Nodes

Book of Nodes

Book of Nodes

  • Now we can add these to a GridMap. In your Main scene, add a new GridMap node.

Book of Nodes

  • Assign the library that you just created as its MeshLibrary resource. The tiles should now be available to be placed down.

Book of Nodes

  • If your meshes look odd, you can change the cell size to fit your mesh, as well as disable centering on a certain axis so it “snaps” better.

Book of Nodes

Book of Nodes

  • You can alter between floors and planes by pressing the C, X, and Z keys on your keyboard.

Book of Nodes

Book of Nodes

Book of Nodes

  • You can delete tiles by hovering over them and holding down your right mouse button.

Book of Nodes

  • You can rotate meshes via the D and S keys on your keyboard.

Book of Nodes

Book of Nodes

Book of Nodes

Book of Nodes

  • You will have to use multiple GridMaps with different settings to achieve a good result. Unless you’re using tiles that are all the same size, you won’t be able to do everything using only one GridMap.

Book of Nodes

Book of Nodes

Book of Nodes

  • Go ahead and create your mini world.

Book of Nodes

  • Run your scene and test your creation!

Book of Nodes

Timer

The Timer node is used to create countdown timers that can trigger events after a specified period. The Timer node provides several properties to control its behavior, including wait_time, autostart, and one_shot.

  • wait_time : The duration in seconds that the timer will count down before emitting the timeout signal.
  • autostart : If set to true, the timer will start automatically when the scene is loaded.
  • one_shot : If set to true, the timer will stop after emitting the timeout signal once. If false, the timer will restart automatically after each timeout.

It comes with a timeout signal, which is emitted when the timer reaches zero. This signal can be connected to a function to perform specific actions when the timer completes its countdown. The timeout signal is a crucial part of the Timer node's functionality, allowing you to trigger events at precise intervals.

Mechanic:

Spawn an enemy every 5 seconds.

Implementation:

  • Add a Timer node to your scene. Set its wait_time to 5 seconds. Since we want the enemy to “spawn” as soon as the game loads, we should enable its autostart property.

Book of Nodes

Book of Nodes

  • Connect the timer node’s timeout signal to your script. This will execute our logic to spawn our enemy every 5 seconds.

Book of Nodes

  • In your code, let’s “spawn” an enemy. Since we don’t have an actual enemy scene, we will just print the amount of enemies we have spawned.
### Main.gd

extends Node3D

@onready var timer = $Timer
var enemy_count = 0

func _ready():
 if not timer.is_stopped():
  timer.start()

func _on_timer_timeout():
 spawn_enemy()

func spawn_enemy():
 enemy_count += 1
 print("An enemy has spawned!")
 print("Current enemy count: ", enemy_count) 
Enter fullscreen mode Exit fullscreen mode
  • Run your game and your enemy should spawn each time the timer reaches 0!

Book of Nodes

💖 💪 🙅 🚩
christinec_dev
christine

Posted on August 7, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

The Book of Nodes: 3D
guide The Book of Nodes: 3D

August 7, 2024