Redot 26.3 stable release

The stable release for Redot 26.3 is here! This is Redot's biggest release, with new features, enhancements, and bugfixes. A lot of work into Redot 26.3, and it adds quite a bit, so let's get right into it.

You can download Redot 26.3 over on our releases page.

New Features

WorldScape3D

3D Redot devs can now edit terrain directly in the engine without the need for addons or extensions with WorldScape 3D!

WorldScape 3D is a built-in terrain editor for 3D games, based on the Terrain 3D extension by Cory Petkovsek and ported into Redot by OldDev78.

To use it, add a WorldScape3D node to your scene, set the data directory to a folder that holds your terrain data, and then select the WorldScape3D node to use the sculpt and paint tools.

For more in-depth guides and tutorials, you can check out the Terrain3D documentation here.

Mode7Sprite2D and Manual Scroll for Parallax2D

The Mode7Sprite2D Node recreates SNES Mode7 effects in Redot, which gives 2D games the illusion of having 3D effects.

Originally, the SNES used Mode 7 graphics to control affine transformations per scanline that enabled iconic perspective projection effects that allowed games like F-Zero to have a convincing pseudo-3D effect without the use of polygons.

In Redot, the Mode7Sprite2D node replaces the standard Sprite2D material with a custom canvas_item shader that reads per-scanline transforms from a 3 pixel-wide texture that uses transform matrix, offset/pivot, and modulate.

The Manual Scroll setting for Parallax2D allows devs to dynamically change the scroll effect while the game is running.

You can see both of these features being used in the video above to recreate the nostalgic feeling of navigating an airship through the overworld of a 2D game. The Mode7Sprite2D node is used for the ground, which gives a bit of depth to the overworld, and the sky uses a Parallax2D node with manual scrolling, where it moves left or right, or stops, depending on the player's input. All of this is done entirely in 2D.

Structs

Structs are a data type allow devs to group related variables of different data types under a single name. While this does sound like what dictionaries already do, structs differ by allowing you to use typed variables, which improves performance, and they don’t rely on strings like dictionaries do to access the data inside.

struct_name Stats:
var name: String
var level: int = 1
var max_hp: int
var hp: int
var attack: int
var defense: int

This is an example of how to create a global struct by declaring it with struct_name in its own .gd file. From here, any script can use the Stats struct.

class_name Goblin extends Node2D

var stats: Stats = Stats.new("Goblin", 2, 20, 20, 4, 1)

This creates a copy of Stats for the Goblin class, and assigns new values for each variable.

You can read more about structs here.

Traits

Traits represent a set of methods that can be used to extend the functionality of a class. Basically, this is a way for you to use code from one or more scripts, without having that class inherit from those scripts. It’s a very powerful feature focused on composition and reusable code.

# interactable.gd
trait_name Interactable
extends Node2D

@export var prompt_text: String = "Talk"

func interact(by: Node) -> void:
print(by.name, " interacts with ", name)
# damagable.gd
trait_name Damageable
extends Node2D

signal died

@export var max_hp: int = 10
var hp: int = 10

func take_damage(amount: int) -> void:
hp = max(hp - amount, 0)
if hp == 0:
died.emit()
# npc.gd
class_name NPC extends Node2D
uses Interactable, Damagable

func _ready() -> void:
hp = max_hp
died.connect(_on_died)

func interact(by: Node) -> void:
super(by)
if hp == 0:
print("(I think he's dead)")
elif hp <= 9:
print("Bloody hell!")
else:
print("Oi bruv!")

func on_died() -> void:
print("A bit rude innit?")

In this example, we have interactable and damagable traits which are used by a NPC where the dialogue changes depending on how much health they have.

You can click here to see a more practical example.

Nullable types

This is a feature that allows a typed variable to hold a null value, which represents the absence of a value. This becomes very useful when you need to declare a variable in code and dynamically assign or remove values to it as the game is running.

To allow a variable to hold a null value, simply put ? after declaring a type.

class_name AttackData extends Resource

@export var attack_name: String
@export var power: int
@export var accuracy: float?

func hits() -> bool:
var chance: float? = accuracy
return chance == null or randf() < chance

In this example there's an AttackData resource with a function that determines whether or not the attack hits depending on the accuracy. If the accuracy is null, the attack never misses, otherwise randf() picks a random number between 0.0 and 1.0 and the attack can miss if the number falls outside of the accuracy range.

For more information on nullable types, click here.

Full Changelog

GDScript Enhancements

UI Changes

Editor Enhancements

2D Enhancements

3D Enhancements

Performance Enhancements

Networking Enhancements

General Enhancements

Rendering Enhancements

Shaders

Physics

Movie Maker Mode

Bugs Squashed

Documentation

Chores & Buildsystem

Special Thanks

We'd like to thank all of the devs and contributors who worked on 26.3, everyone who tested the new features and reported bugs in the Beta and RC builds, Memmo for making the blogpost thumbnail, and everyone who uses Redot Engine to make their game. We'll continue to work on the engine to bring more features and bug fixes to give you all the best engine that we can.

Want to contribute?

If you’d like to help contribute to Redot or Draconic, or would like an internship at Redot Foundation, send an email to [email protected] If you’d like to help support development, you can go to redotfoundation.org and make a donation. Redot and Draconic are FOSS projects made by passionate devs in their free time, and every donation helps!