Misc Godot Info

April 7, 2024


Change the Background Color in the Editor
Navigate to Project > Project Settings > General Tab > Rendering > Environment > Default Clear Color.
Changing this color will change the environment color of the editor.


Capture Resize Callback
@onready var root = get_node("/root/")

#later in function 
root.size_changed.connect(your_function) 

#----------------------- OR --------------------------------#

get_tree().get_root().size_changed.connect(your_function) 

Event Bus
Create script SignalBus.gd and make it an autoload.
#signalbus.gd
signal _hello_world(val)
Create event that emits the signal for example buttons being pressed
#button.gd
var worldName := "Earth"
func helloWorld()-> void:
    SignalBus.emit_signal("_hello_world", worldName)
# OR SignalBus._hello_world.emit(worldName)
Connect the node that should trigger event.
#player.gd
func _ready()-> void:
    SignalBus.connect("_hello_world", helloWorld) 
func helloWorld(val):
    print("Hello ", val)


Cloning Scenes
onready var amount = 20
onready var coin = preload("res://coin.tscn")
func _drop():
for i in range(amount):
            var new_coin = coin.instance()
            get_parent().add_child(new_coin)
            print("coin: ", new_coin.name)


Get Nodes in Group
var enemies = get_tree().get_nodes_in_group("enemies")

Change Sprite Texture with Code
func update_texture(texture: Texture):
    var reference_frames: SpriteFrames = $AnimatedSprite.frames
    var updated_frames = SpriteFrames.new()
    
    for animation in reference_frames.get_animation_names():
        if animation != "default":
            updated_frames.add_animation(animation)
            updated_frames.set_animation_speed(animation, reference_frames.get_animation_speed(animation))
            updated_frames.set_animation_loop(animation, reference_frames.get_animation_loop(animation))
            
            for i in reference_frames.get_frame_count(animation):
                var updated_texture: AtlasTexture = reference_frames.get_frame(animation, i).duplicate()
                updated_texture.atlas = texture
                updated_frames.add_frame(animation, updated_texture)
updated_frames.remove_animation("default")
$AnimatedSprite.frames = updated_frames

Pixel Art Rendering Notes

June 25, 2024


Godot Project Settings

  • Go to Project > Project Settings > Display > Window > Stretch > Mode and set it to "viewport".
  • Go to Project > Project Settings > Display > Window > Stretch > Aspect and set it to "keep" *
  • Go to Project > Project Settings > Rendering > Quality > 2D > Use Pixel Snap and turn this On.
  • Make sure Pixel Snap is enabled for 2D View
  • Whenever you add a Sprite or AnimatedSprite node, make sure to disable the "Centered" property. For the AnimatedSprite node you will find that property under "AnimatedSprite" and for the Sprite node you find it under "Offset". 
  • Change the default Rendering Texture in Project Settings rendering/textures/canvas_textures/default_texture_filter (you need to activate Advanced Settings), to Nearest instead of Linear.


Viewport Settings
Snap2D and Filter do not inherit, make sure they are set in the viewport.

3D Pixel Art Rendering
Based on the work of Denovodavid, there is a way to have 3D look like real pixel art.


3D Pixel Art Rendering - Camera
  1. Node3D (Parent)
  2. SubViewport (Camera Viewport) (322x182)
  3. Node3D (Camera Control)
  4. Camera3D
#Camera Control
extends Node3D


@export var circular_radius: float = 0.0
@export var circular_speed: float = 0.2

@export var cam: Camera3D 
@export var follow: Node3D 
@export var followlerp: bool

var selfx := 0.0
var selfz := 0.0

func _ready() -> void:
	selfx = position.x
	selfz = position.z


func _process(_delta: float) -> void:
	if follow:
		position.x = selfx + follow.position.x
		position.z = selfz + follow.position.z
	if followlerp:
		position.x = selfx + lerp(follow.position.x, position.x, 0.02)
		position.z = selfz + lerp(follow.position.z, position.z, 0.02)
		
	if Input.is_action_pressed("ui_copy"):
		cam.size = lerp(cam.size, 20.0, 0.2)
	if Input.is_action_pressed("ui_cut"):
		cam.size = lerp(cam.size, 10.0, 0.2)

#Camera3D
class_name Camera3DTexelSnapped3
extends Camera3D

@export var snap := true
@export var snap_objects := true

var texel_error := Vector2.ZERO

@onready var _prev_rotation := global_rotation
@onready var _snap_space := global_transform
var _texel_size: float = 0.0

var _snap_nodes: Array[Node]
var _pre_snapped_positions: Array[Vector3]


func _ready() -> void:
	RenderingServer.frame_post_draw.connect(_snap_objects_revert)
	self.rotation.y = 0.8853982


func _process(_delta: float) -> void:
	self.rotation.y = 0.7853982
	# rotation changes the snap space
	if global_rotation != _prev_rotation:
		_prev_rotation = global_rotation
		_snap_space = global_transform
	_texel_size = size / float((get_viewport() as SubViewport).size.y)
	# camera position in snap space
	var snap_space_position := global_position * _snap_space
	# snap!
	var snapped_snap_space_position := snap_space_position.snapped(Vector3.ONE * _texel_size)
	# how much we snapped (in snap space)
	var snap_error := snapped_snap_space_position - snap_space_position
	if snap:
		# apply camera offset as to not affect the actual transform
		h_offset = snap_error.x
		v_offset = snap_error.y
		# error in screen texels (will be used later)
		texel_error = Vector2(snap_error.x, -snap_error.y) / _texel_size
		if snap_objects:
			_snap_objects.call_deferred()
	else:
		texel_error = Vector2.ZERO


func _snap_objects() -> void:
	_snap_nodes = get_tree().get_nodes_in_group("snap")
	_pre_snapped_positions.resize(_snap_nodes.size())
	for i in _snap_nodes.size():
		var node := _snap_nodes[i] as Node3D
		var pos := node.global_position
		_pre_snapped_positions[i] = pos
		var snap_space_pos := pos * _snap_space
		var snapped_snap_space_pos := snap_space_pos.snapped(Vector3(_texel_size, _texel_size, 0.0))
		node.global_position = _snap_space * snapped_snap_space_pos


func _snap_objects_revert() -> void:
	for i in _snap_nodes.size():
		(_snap_nodes[i] as Node3D).global_position = _pre_snapped_positions[i]
	_snap_nodes.clear()

3D Pixel Art Rendering - Rendering To Screen
  1. Control Node
  2. Sprite2D
#Control Node
extends Control

@export var viewport: SubViewport
@export var pixel_movement := true
@export var sub_pixel_movement_at_integer_scale := true
@export var _sprite: Sprite2D


func _process(_delta: float) -> void:
	var screen_size := Vector2(get_window().size)
	# viewport size minus padding
	var game_size := Vector2(viewport.size - Vector2i(2, 2))
	var display_scale := screen_size / game_size
	# maintain aspect ratio
	var display_scale_min: float = minf(display_scale.x, display_scale.y)
	_sprite.scale = Vector2(display_scale_min, display_scale_min)
	# scale and center control node
	size = (_sprite.scale * game_size).round()
	position = ((screen_size - size) / 2).round()
	# smooth!
	if pixel_movement:
		var cam := viewport.get_camera_3d() as Camera3DTexelSnapped3
		var pixel_error: Vector2 = cam.texel_error * _sprite.scale
		_sprite.position = -_sprite.scale + pixel_error
		var is_integer_scale := display_scale == display_scale.floor()
		if is_integer_scale and not sub_pixel_movement_at_integer_scale:
			_sprite.position = _sprite.position.round()

// Sprite2D Shader Material
// based on code by t3ssel8r: https://youtu.be/d6tp43wZqps
// adapted to Godot by denovodavid

shader_type canvas_item;
render_mode unshaded;

void fragment() {
	// box filter size in texel units
	vec2 box_size = clamp(fwidth(UV) / TEXTURE_PIXEL_SIZE, 1e-5, 1);
	// scale uv by texture size to get texel coordinate
	vec2 tx = UV / TEXTURE_PIXEL_SIZE - 0.5 * box_size;
	// compute offset for pixel-sized box filter
	vec2 tx_offset = smoothstep(vec2(1) - box_size, vec2(1), fract(tx));
	// compute bilinear sample uv coordinates
	vec2 uv = (floor(tx) + 0.5 + tx_offset) * TEXTURE_PIXEL_SIZE;
	// sample the texture
	COLOR = textureGrad(TEXTURE, uv, dFdx(UV), dFdy(UV));
}

Godot Camera, Isometric View

August 3, 2024


Camera Notes
  • Camera3D (Isometric)
    • Projection: Orthogonal
    • Rotation: -30, 45, 0 (XYZ)
    • Size: 8M
    • Far: 200M (Fixes issues with Orthogonal Shadows) [Link]
Moving Player w/ Camera Angle
var direction := (Vector3(input_dir.x, 0, input_dir.y)).rotated(Vector3.UP, main_camera.rotation.y).normalized()


Viewport Texture Error

September 7, 2024


From Reddit: MamaDespik
In Godot, if you set a ViewPort Texture directly on a Material Albedo you will receive an error when running your game. This error appears to be harmless in game but it is annoying.



To work around it, you can set it in code:
  1. Grab a reference to the mesh
  2. Grab a reference to the viewport/subviewport
  3. In the _ready() callback, set the texture.

@onready var mesh_instance_3d: MeshInstance3D = $MeshInstance3D
@onready var sub_viewport: SubViewport = $SubViewport

func _ready() -> void:
	mesh_instance_3d.get_surface_override_material(0).albedo_texture = sub_viewport.get_texture()

This is not likely to be fixed anytime soon, it's been open since 2022...

Static Typing

October 14, 2024


How to use static typing

To define the type of a variable, parameter, or constant, write a colon after the name, followed by its type. E.g. var health: int. This forces the variable's type to always stay the same:

var damage: float = 10.5
const MOVE_SPEED: float = 50.0
func sum(a: float = 0.0, b: float = 0.0) -> float:
return a + b

Godot will try to infer types if you write a colon, but you omit the type:

var damage := 10.5
const MOVE_SPEED := 50.0
func sum(a := 0.0, b := 0.0) -> float:
return a + b

  1. There is no difference between = and := for constants.
  2. You don't need to write type hints for constants, as Godot sets it automatically from the assigned value. But you can still do so to make the intent of your code clearer. Also, this is useful for typed arrays (like const A: Array[int] = [1, 2, 3]), since untyped arrays are used by default.

What can be a type hint

Here is a complete list of what can be used as a type hint:

  1. Variant. Any type. In most cases this is not much different from an untyped declaration, but increases readability. As a return type, forces the function to explicitly return some value.

  2. (Only return type) void. Indicates that the function does not return any value.

  3. Built-in types.

  4. Native classes (Object, Node, Area2D, Camera2D, etc.).

  5. Global classes.

  6. Inner classes.

  7. Global, native and custom named enums. Note that an enum type is just an int, there is no guarantee that the value belongs to the set of enum values.

  8. Constants (including local ones) if they contain a preloaded class or enum.


Custom BBCode Scripts

November 2, 2024


BetterWave
@tool
extends RichTextEffect
class_name RichTextBetterWav

# Syntax: [betterwav speed=7.0 power=2.0 yadj=1][/betterwav]

# Define the tag name.
var bbcode = "betterwav"

func _process_custom_fx(char_fx):
    var speed = char_fx.env.get("freq", 7.0)
    var power = char_fx.env.get("span", 2.0)
    var yadj = char_fx.env.get("span", 1.0)
    
    # Get parameters, or use the provided default value if missing.
    char_fx.offset.y += sin(char_fx.elapsed_time * speed + char_fx.range.x) * power
    char_fx.offset.y += yadj
    return true

Credits

Document

SysL
Version 2, released May 1, 2024.