# Godot 4.7 — Project Conventions & API Reference A reference document for the EnvoyOS project targeting Godot 4.7. Derived from the official [Godot 4.7 documentation](https://github.com/godotengine/godot-docs/tree/4.7) structure and the project's `AIFIX.md` corrections. --- ## Table of Contents 1. [GDScript Syntax & Type Hints](#1-gdscript-syntax--type-hints) 2. [Node Lifecycle](#2-node-lifecycle) 3. [Signals](#3-signals) 4. [Input Handling](#4-input-handling) 5. [Controls & UI](#5-controls--ui) 6. [Window Management](#6-window-management) 7. [Audio](#7-audio) 8. [Networking (Multiplayer)](#8-networking-multiplayer) 9. [File I/O](#9-file-io) 10. [Resources & Loading](#10-resources--loading) 11. [Performance](#11-performance) 12. [Style Conventions](#12-style-conventions) 13. [Common Pitfalls & Corrections](#13-common-pitfalls--corrections) --- ## 1. GDScript Syntax & Type Hints ### Typed Arrays ```gdscript # WRONG (Godot 3 style, inferred empty array) var rocks := [] # CORRECT (Godot 4.7 explicit type) var rocks: Array[Label] = [] var items: Array[Dictionary] = [] var names: Array[String] = [] var scales: Array[float] = [] ``` ### Variable Declarations ```gdscript # Prefer explicit types over := inference when the type is unambiguous var file: FileAccess = FileAccess.open(path, FileAccess.READ) var parsed: Variant = JSON.parse_string(json_str) # Use := only when the right-hand side type is clear var btn := Button.new() var style := StyleBoxFlat.new() ``` ### Color Construction ```gdscript # WRONG col.color = Color.rand() # CORRECT col.color = Color(randf(), randf(), randf()) ``` ### Global Functions (Not Methods) ```gdscript # WRONG (clampf is a global function, not a method on float) _transparency_multiplier = value.clampf(0.0, 1.0) # CORRECT _transparency_multiplier = clampf(value, 0.0, 1.0) # OR _transparency_multiplier = clamp(value, 0.0, 1.0) ``` ### Vector Operations ```gdscript # WRONG (ceiled() does not exist on Vector2) var target := vec.ceiled() # CORRECT var target := Vector2i(vec.ceil()) ``` ### Async/Await ```gdscript # WRONG (yield was removed in Godot 4) yield(get_tree().process_frame, "process_frame") # CORRECT await get_tree().process_frame ``` ### Process Delta Time ```gdscript # WRONG (throws error) var speed = Node.get_process_delta_time() # CORRECT var speed = get_process_delta_time() ``` ### Constants & Enums ```gdscript const MAX_ITEMS := 12 const CONFIG_PATH := "user://inventory_config.cfg" enum PowerState { OFF = 0, LOW = 1, NETWORK = 2, HIGH = 3, } ``` --- ## 2. Node Lifecycle ### Key Notifications (Godot 4.7) ```gdscript func _notification(what: int) -> void: match what: NOTIFICATION_WM_CLOSE_REQUEST: # Window close (replaces NOTIFICATION_WM_QUIT_REQUEST) pass NOTIFICATION_APPLICATION_PAUSED, NOTIFICATION_APPLICATION_FOCUS_OUT: # App backgrounded / lost focus pass NOTIFICATION_APPLICATION_RESUMED, NOTIFICATION_APPLICATION_FOCUS_IN: # App foregrounded / regained focus pass ``` ### Process Mode (Replaces Old Pause Mode) ```gdscript # WRONG timer.pause_mode = Timer.PAUSE_MODE_PROCESS # CORRECT timer.process_mode = Node.PROCESS_MODE_ALWAYS ``` ### Dialogs and Timers ```gdscript # When a ConfirmationDialog is shown via popup_centered(), # the SceneTree pauses. Timers inside will stop unless: dialog.process_mode = Node.PROCESS_MODE_ALWAYS ``` ### Node Creation and Freeing ```gdscript # To clear a container's children (VBoxContainer has no .clear() in Godot 4) # WRONG kitchen_sink_box.clear() # CORRECT for child in container.get_children(): child.queue_free() ``` ### Safe Instance Checks ```gdscript # After any await or deferred call, verify the object still exists if is_instance_valid(node) and node.is_inside_tree(): node.do_something() ``` --- ## 3. Signals ### Declaration ```gdscript signal item_updated(index: int) signal stats_updated(latency_ms: float, jitter: float, packet_loss: float) ``` ### Connecting (with type-safe lambdas) ```gdscript btn.pressed.connect(_on_button_pressed.bind(index)) slider.value_changed.connect(func(val: float): _on_slider(val)) ``` ### Disconnecting ```gdscript if signal_name in node.get_signal_connection_list(sig): node.disconnect(sig, callable) ``` ### RPC (Multiplayer) ```gdscript @rpc("any_peer", "unreliable_ordered") func receive_secure_clipboard(encrypted_data: PackedByteArray) -> void: pass # Call from any peer: rpc("receive_secure_clipboard", data) ``` --- ## 4. Input Handling ### Key Codes (Godot 4.7) ```gdscript if event is InputEventKey and event.keycode == KEY_TAB and Input.is_key_pressed(KEY_CTRL): pass ``` ### Touchscreen ```gdscript # WRONG Input.is_touchscreen_available() # CORRECT DisplayServer.is_touchscreen_available() ``` ### WebRTC Feature Check ```gdscript # WRONG Engine.has_feature("web_view") # CORRECT OS.has_feature("web") or ClassDB.class_exists("WebRTCPeerConnection") ``` ### Mouse Position (Global vs Local) ```gdscript # For window-level calculations (resize, drag), use GLOBAL screen coords: var global_mouse: Vector2 = DisplayServer.mouse_get_position() # For UI-local calculations: var local_mouse: Vector2 = get_viewport().get_mouse_position() ``` --- ## 5. Controls & UI ### StyleBoxFlat (Per-Corner, Per-Side) ```gdscript # WRONG (Godot 3 properties do not exist in 4.7) style.border_width_all = 2 style.corner_radius_all = 16 # CORRECT (individual properties) style.border_width_left = 2 style.border_width_right = 2 style.border_width_top = 2 style.border_width_bottom = 2 style.corner_radius_top_left = 16 style.corner_radius_top_right = 16 style.corner_radius_bottom_left = 16 style.corner_radius_bottom_right = 16 ``` ### StyleBox Copying ```gdscript # WRONG var hover_style = StyleBoxFlat.new(style) # CORRECT var hover_style = style.duplicate() ``` ### TextEdit Wrapping ```gdscript # WRONG system_input.fit_content_width = true # CORRECT system_input.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY ``` ### Tree Items ```gdscript # WRONG (type inference fails) var child := control.create_item(root) # CORRECT var child: TreeItem = control.create_item(root) ``` ### Abstract Classes ```gdscript # WRONG (Slider is abstract, cannot be constructed) Slider.new() # CORRECT VSlider.new() HSlider.new() ``` ### TabBar Close Buttons ```gdscript # WRONG (property does not exist) tab_bar.tabs_closeable = true # CORRECT tab_bar.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_ALWAYS tab_bar.tab_close_pressed.connect(_on_tab_close_pressed) ``` ### Hiding TabContainer's Built-in Tab Bar ```gdscript tab_container.tabs_visible = false ``` ### ConfirmationDialog ```gdscript # Godot 4.7: use get_cancel_button() (not get_cancel()) dialog.get_cancel_button().text = "Cancel" ``` --- ## 6. Window Management ### V-Sync ```gdscript # WRONG DisplayServer.vsync = DisplayServer.VSYNC_ENABLED # CORRECT DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED) ``` ### DPI / Content Scaling ```gdscript # WRONG (size_scale does not exist) get_window().size_scale = 2.0 # CORRECT get_window().content_scale_factor = 2.0 # OR for base resolution: get_window().content_scale_size = Vector2i(1920, 1080) ``` ### Window Size (Integer Required) ```gdscript # Adjust physical window size inversely proportional to scale var target_size := Vector2i((LOGICAL_BASE_SIZE / scale).ceil()) window.size = target_size ``` ### Deferred Window Size (Avoid Startup Race) ```gdscript window.content_scale_factor = scale var target_size := Vector2i((LOGICAL_BASE_SIZE / scale).ceil()) window.set_deferred("size", target_size) ``` ### Borderless Window ```gdscript get_window().borderless = true DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true) ``` ### Resize Flag (Windows DWM) ```gdscript # WRONG (RESIZE flag does not exist as a set-to-true operation) DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE, true) # CORRECT (pass false to RESIZE_DISABLED to ENABLE resizing) DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE_DISABLED, false) ``` ### Borderless Resize (Global Mouse) ```gdscript # Must use global screen coordinates for borderless resize var global_mouse := DisplayServer.mouse_get_position() var window_top_left := get_window().position var target_width: int = int(max(global_mouse.x - window_top_left.x, 800)) var target_height: int = int(max(global_mouse.y - window_top_left.y, 500)) get_window().size = Vector2i(target_width, target_height) ``` ### Cursor Shapes ```gdscript DisplayServer.cursor_set_shape(DisplayServer.CURSOR_DRAG) DisplayServer.cursor_set_shape(DisplayServer.CURSOR_HSIZE) DisplayServer.cursor_set_shape(DisplayServer.CURSOR_ARROW) ``` ### Always on Top ```gdscript get_window().always_on_top = true ``` ### Window Position ```gdscript DisplayServer.window_set_position(Vector2i(x, y)) # OR get_window().position = Vector2i(x, y) ``` --- ## 7. Audio ### Bus Management ```gdscript AudioServer.add_bus() AudioServer.set_bus_volume_db(index, db) AudioServer.set_bus_mute(index, true) AudioServer.get_bus_count() AudioServer.get_bus_name(index) ``` ### Audio Effects ```gdscript var capture := AudioEffectCapture.new() AudioServer.add_bus_effect(bus_index, capture) AudioServer.set_bus_effect_enabled(bus_index, effect_index, true) ``` ### Microphone Input ```gdscript var mic_stream := AudioStreamMicrophone.new() player.stream = mic_stream player.bus = AudioServer.get_bus_name(bus_index) player.autoplay = true ``` ### PCM Playback ```gdscript var wav := AudioStreamWAV.new() wav.format = AudioStreamWAV.FORMAT_16_BITS wav.mix_rate = 48000 wav.stereo = true wav.set_data(PackedFloat32Array) player.stream = wav player.play() ``` ### Input Device List ```gdscript if AudioServer.has_method("get_input_device_list"): var devices: Array[String] = AudioServer.call("get_input_device_list") ``` --- ## 8. Networking (Multiplayer) ### ENet Multiplayer ```gdscript var peer := ENetMultiplayerPeer.new() var err: int = peer.create_server(port) # OR var err: int = peer.create_client("192.168.1.10", 8443) multiplayer.multiplayer_peer = peer ``` ### Signals ```gdscript multiplayer.connected_to_server.connect(_on_connected) multiplayer.connection_failed.connect(_on_failed) multiplayer.server_disconnected.connect(_on_disconnected) multiplayer.peer_connected.connect(_on_peer_joined) multiplayer.peer_disconnected.connect(_on_peer_left) multiplayer.get_peers() # Array[int] multiplayer.get_unique_id() # int ``` ### UDP (VoIP) ```gdscript var pkt_peer := PacketPeerUDP.new() pkt_peer.connect_to_host(ip, port) pkt_peer.put_data(PackedByteArray) pkt_peer.get_available_packet_count() pkt_peer.get_packet() # PackedByteArray pkt_peer.close() ``` ### Local Addresses ```gdscript # WRONG IP.get_network_interfaces() # as static call # CORRECT (on the singleton instance) var addresses: Array = IP.get_local_addresses() var interfaces: Array = IP.get_network_interfaces() ``` ### Crypto (AES-256) ```gdscript var aes := AESContext.new() var hash_ctx := HashingContext.new() hash_ctx.start(HashingContext.HASH_SHA256) hash_ctx.update(key_bytes) var key: PackedByteArray = hash_ctx.finish() aes.start(AESContext.MODE_ECB_ENCRYPT, key) var encrypted: PackedByteArray = aes.update(data) aes.finish() ``` --- ## 9. File I/O ### ConfigFile ```gdscript var config := ConfigFile.new() config.load("user://settings.cfg") var val = config.get_value("section", "key", default) config.set_value("section", "key", value) config.save("user://settings.cfg") ``` ### FileAccess ```gdscript # Plain var file := FileAccess.open(path, FileAccess.READ) var text := file.get_as_text() file.close() # Compressed (FastLZ) var file := FileAccess.open_compressed(path, FileAccess.WRITE, FileAccess.COMPRESSION_FASTLZ) file.store_string(json_str) file.close() # Check existence FileAccess.file_exists("user://data.json") ``` ### CSV ```gdscript file.store_csv_line(["col1", "col2", "col3"]) var row: PackedStringArray = file.get_csv_line() ``` ### JSON ```gdscript var parsed: Variant = JSON.parse_string(json_text) var json_str: String = JSON.stringify(data, "\t") ``` --- ## 10. Resources & Loading ### Loading Scripts ```gdscript # Static (compile-time) const MyScript = preload("res://scripts/my_script.gd") # Dynamic (runtime) var script: GDScript = load("res://scripts/dynamic.gd") var instance: Node = script.new() ``` ### Loading Fonts ```gdscript var custom_font: Font = load("res://font/text_with_emojis.tres") ``` ### Class Registration ```gdscript class_name MyEngine extends Node ``` > **Note:** If two scripts reference each other (circular dependency), > relax the type hint to the base class to break the loop: > ```gdscript > # Instead of: > var tab_bar: MainTabBar > # Use: > var tab_bar: TabBar > ``` --- ## 11. Performance ### FPS ```gdscript Engine.get_frames_per_second() Engine.max_fps = 60 ``` ### Draw Calls ```gdscript Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME) ``` ### Low Processor Usage ```gdscript OS.low_processor_usage_mode = true ``` ### Object Pooling ```gdscript # Pre-allocate nodes, toggle visibility instead of create/free var pool: Array[Label] = [] for i in POOL_SIZE: var node := Label.new() node.visible = false add_child(node) pool.append(node) ``` ### Avoid Per-Frame Allocations ```gdscript # Reuse cache arrays var _cache: Array[Node] = [] func update(): _cache.clear() for item in items: if item.valid: _cache.append(item) # Swap with working array var temp = items items = _cache _cache = temp ``` ### Debounce Timers ```gdscript var _save_timer := Timer.new() _save_timer.wait_time = 1.0 _save_timer.one_shot = true add_child(_save_timer) # In event handler: _save_timer.stop() _save_timer.start() ``` --- ## 12. Style Conventions ### File Naming | Pattern | Purpose | |---------|---------| | `*_engine.gd` | Logic / data / network layer | | `*_ui.gd` | Presentation / node construction | | `*_helper.gd` | Static utility functions | ### Engine Pattern ```gdscript extends Node class_name MyEngine signal data_changed(value: Variant) const CONFIG_PATH := "user://my_settings.cfg" var _state: Dictionary = {} func _ready() -> void: _load() func _exit_tree() -> void: _save() func _process(delta: float) -> void: # Per-frame logic # Public API func get_value(key: String) -> Variant: ... func set_value(key: String, val: Variant) -> void: ... # Private helpers func _load() -> void: ... func _save() -> void: ... ``` ### UI Pattern ```gdscript extends RefCounted class_name MyUI static func setup_ui(page_container: Node, engine: Node) -> void: # Guard against double-setup if page_container.has_node("MyWrapper"): return # Locate content area var layout_vbox = page_container.get_child(0) var main_content = layout_vbox.get_child(0) # Clear previous for child in main_content.get_children(): child.queue_free() # Build UI... ``` ### Print Statements ```gdscript print("[EngineName] Message: ", detail) push_warning("[EngineName] Non-fatal issue") push_error("[EngineName] Fatal issue: ", err) ``` --- ## 13. Common Pitfalls & Corrections | # | Wrong | Correct | Reason | |---|-------|---------|--------| | 1 | `var items := []` | `var items: Array[Label] = []` | Typed arrays required | | 2 | `DisplayServer.vsync = true` | `DisplayServer.window_set_vsync_mode(...)` | API changed in 4.x | | 3 | `Input.is_touchscreen_available()` | `DisplayServer.is_touchscreen_available()` | Moved to DisplayServer | | 4 | `Color.rand()` | `Color(randf(), randf(), randf())` | No static rand() | | 5 | `yield(...)` | `await ...` | yield removed in Godot 4 | | 6 | `container.clear()` | `for c in container.get_children(): c.queue_free()` | No clear() on Container | | 7 | `style.border_width_all = 2` | Per-side `border_width_left/right/top/bottom` | No _all shortcut | | 8 | `style.corner_radius_all = 16` | Per-corner `corner_radius_top_left/...` | No _all shortcut | | 9 | `StyleBoxFlat.new(style)` | `style.duplicate()` | No copy constructor | | 10 | `fit_content_width = true` | `wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY` | Property renamed | | 11 | `Slider.new()` | `VSlider.new()` / `HSlider.new()` | Slider is abstract | | 12 | `tab_bar.tabs_closeable = true` | `tab_bar.tab_close_display_policy = ...` | Property renamed | | 13 | `get_window().size_scale = 2.0` | `get_window().content_scale_factor = 2.0` | Property renamed | | 14 | `DisplayServer.window_set_flag(RESIZE, true)` | `DisplayServer.window_set_flag(RESIZE_DISABLED, false)` | Flag semantics inverted | | 15 | `dialog.get_cancel()` | `dialog.get_cancel_button()` | Method renamed in 4.6+ | | 16 | `Node.get_process_delta_time()` | `get_process_delta_time()` | Instance method, not static | | 17 | `vec.ceiled()` | `Vector2i(vec.ceil())` | Wrong method name | | 18 | `IP.get_network_interfaces()` (static) | `IP.get_network_interfaces()` (on singleton) | Must be instance call | | 19 | `NOTIFICATION_WM_QUIT_REQUEST` | `NOTIFICATION_WM_CLOSE_REQUEST` | Renamed in Godot 4 | | 20 | `timer.pause_mode = PAUSE_MODE_PROCESS` | `timer.process_mode = PROCESS_MODE_ALWAYS` | Renamed in Godot 4 | | 21 | `Engine.has_feature("web_view")` | `OS.has_feature("web")` | Correct singleton + feature | | 22 | `var child := control.create_item(root)` | `var child: TreeItem = control.create_item(root)` | Type inference fails | | 23 | `Engine.get_process_time()` | Does not exist | Use `Time.get_ticks_msec()` | | 24 | `NOTIFICATION_FOCUS_OUT` | `NOTIFICATION_APPLICATION_FOCUS_OUT` | Correct constant name | --- ## Quick Reference: Godot 4.7 Class Hierarchy (UI) ``` CanvasItem ├── Control │ ├── Container │ │ ├── VBoxContainer │ │ ├── HBoxContainer │ │ ├── GridContainer │ │ ├── MarginContainer │ │ ├── PanelContainer │ │ ├── ScrollContainer │ │ ├── TabContainer │ │ ├── HSplitContainer / VSplitContainer │ │ └── CenterContainer │ ├── Label │ ├── Button │ ├── CheckBox │ ├── OptionButton │ ├── LineEdit │ ├── TextEdit │ ├── RichTextLabel │ ├── HSlider / VSlider │ ├── ProgressBar │ ├── SpinBox │ ├── ColorPickerButton │ ├── Tree │ ├── TabBar │ ├── PopupMenu │ └── ConfirmationDialog (extends Window) ├── Node2D ├── AudioStreamPlayer └── Node (base) ``` --- ## References - [Godot 4.7 Documentation](https://docs.godotengine.org/en/4.7/) - [Godot 4.7 Class Reference](https://docs.godotengine.org/en/4.7/classes/index.html) - [Getting Started](https://docs.godotengine.org/en/4.7/getting_started/index.html) - [Tutorials](https://docs.godotengine.org/en/4.7/tutorials/index.html) - [Engine Details](https://docs.godotengine.org/en/4.7/engine_details/index.html) - [GDScript Syntax](https://docs.godotengine.org/en/4.7/tutorials/scripting/gdscript/index.html) - [Signals](https://docs.godotengine.org/en/4.7/tutorials/scripting/signals.html) - [Multiplayer](https://docs.godotengine.org/en/4.7/tutorials/networking/multiplayer_introduction.html) - [Window Management](https://docs.godotengine.org/en/4.7/classes/class_displayserver.html) - [Audio](https://docs.godotengine.org/en/4.7/tutorials/audio/index.html) --- *Last updated for Godot 4.7. Cross-reference with `AIFIX.md` for project-specific corrections.*