- Useful Godot Links
- Misc Godot Info
- Pixel Art Rendering Notes
- Godot Camera, Isometric View
- Viewport Texture Error
- Static Typing
- Custom BBCode Scripts
Useful Godot Links
April 7, 2024
Documentation
Tutorial and Reference
Art and Design
- Character Modeling for Beginners - Blender (Youtube)
- Rigging for Beginners - Blender (Youtube)
- Rigging is Beautiful, Here's How It Works (Youtube)
- BLENDER to GODOT 4: Rigging & Animation Tutorial (Youtube)
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.
From: https://forum.godotengine.org/t/how-can-i-change-the-background-color-of-the-editor-scene-area/25086
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)
From: https://forum.godotengine.org/t/how-to-add-multiple-instanced-children-at-the-same-time/17787/2
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
- Node3D (Parent)
- SubViewport (Camera Viewport) (322x182)
- Node3D (Camera Control)
- 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
- Control Node
- 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:
- Grab a reference to the mesh
- Grab a reference to the viewport/subviewport
- 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
- There is no difference between = and := for constants.
- 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:
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.(Only return type)
void
. Indicates that the function does not return any value.Native classes (
Object
,Node
,Area2D
,Camera2D
, etc.).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.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
Version 2, released May 1, 2024.