Godot - GDExtensions

GDExtension

Rust (Gdext)

Documentation
Challenges

Setup

  • cargo add godot

    • I think this is only necessary if you don't manually define Godot in the Dependencies tab of Cargo.toml.

  • cargo new NAME --lib

  • It must be compiled as a 'dynamic C library':

    [lib]
    crate-type = ["cdylib"]
    

Attributes

Document in Godot
  • You can document your functions, classes, members, and signals with the ///  doc comment syntax.

Derive Macros

derive(GodotClass)
  • Trait GodotClass .

  • You should use this macro; manual implementations of the GodotClass  trait are not encouraged.

  • This is typically used in combination with #[godot_api] , which can implement custom functions and constants, as well as override virtual methods.

#[derive(GodotClass)]
{
    // Attributes available to this derive:
    #[class]
    #[base]
    #[hint]
    #[var]
    #[export]
    #[init]
}
derive(GodotConverter)
  • Trait GodotConverter .

    • Indicates that a type can be passed to/from Godot, either directly or through an intermediate “via” type.

    • The associated type Via  specifies how  this type is passed across the FFI boundary to/from Godot. Generally ToGodot  needs to be implemented to pass a type to Godot, and FromGodot  to receive this type from Godot.

    • GodotType  is a stronger bound than GodotConvert , since it expresses that a type is directly  representable in Godot (without intermediate “via”). Every GodotType  also implements GodotConvert  with Via = Self .

    • Please read the godot::meta  module docs  for further information about conversions.

#[derive(GodotConvert)]
{
    // Attributes available to this derive:
    #[godot]
}
derive(Var)
  • Derive macro for Var  on enums.

  • This expects a derived GodotConvert  implementation, using a manual implementation of GodotConvert  may lead to incorrect values being displayed in Godot.

#[derive(Var)]
{
    // Attributes available to this derive:
    #[godot]
}
derive(Export)
#[derive(Export)]
{
    // Attributes available to this derive:
    #[godot]
}

Attribute Macros

var
#[derive(GodotClass)]
struct MyStruct {
    #[var]
    my_field: i64,
}
  • This makes the field accessible in GDScript using my_struct.my_field  syntax. Additionally, it generates a trivial getter and setter named get_my_field  and set_my_field , respectively. These are pub  in Rust, since they’re exposed from GDScript anyway.

  • If you want to implement your own getter and/or setter, write those as a function on your Rust type, expose it using #[func] , and annotate the field with #[var(get = ..., set = ...)] :

#[derive(GodotClass)]
#[class(init)]
struct MyStruct {
    #[var(get = get_my_field, set = set_my_field)]
    my_field: i64,
}

#[godot_api]
impl MyStruct {
    #[func]
    pub fn get_my_field(&self) -> i64 {
        self.my_field
    }

    #[func]
    pub fn set_my_field(&mut self, value: i64) {
        self.my_field = value;
    }
}
struct MyStruct {
    // Default getter, custom setter.
    #[var(get, set = set_my_field)]
    my_field: i64,
}
export
  • Trait Export .

  • For exporting properties to the editor, you can use the #[export]  attribute:

#[derive(GodotClass)]
struct MyStruct {
    #[export]
    my_field: i64,
}
  • There is no need to use #[var]  together with export.


const MAX_HEALTH: f64 = 100.0;

#[derive(GodotClass)]
struct MyStruct {
    #[export]
    float: f64,
     
    #[export(range = (0.0, 10.0, or_greater))]
    range_f64: f64,

    #[export(file)]
    file: GString,

    #[export(file = "*.gd")]
    gdscript_file: GString,

    #[export(flags_3d_physics)]
    physics: u32,

    #[export(exp_easing)]
    ease: f64,

    // @export_enum("One", "Two", "Ten:10", "Twelve:12", "Thirteen")
    #[export(enum = (One, Two, Ten = 10, Twelve = 12, Thirteen))]
    exported_enum: i64,

    // @export_flags("A:1", "B:2", "AB:3")
    #[export(flags = (A = 1, B = 2, AB = 3))]
    flags: u32,

    #[export(range = (0.0, MAX_HEALTH))]
    health: f64,

    #[export(flags = (A = 0b0001, B = 0b0010, C = 0b0100, D = 0b1000))]
    flags: u32,

    #[var(
        hint = ENUM,
        hint_string = "One,Two",
        usage_flags = [EDITOR, READ_ONLY]
    )]
    my_field: i64,
}
signal
  • The functions it decorates (the signals) can accept parameters.

  • It will be fundamentally reworked.

#[derive(GodotClass)]
struct MyClass {}

#[godot_api]
impl MyClass {
    #[signal]
    fn some_signal();

    #[signal]
    fn some_signal_with_parameters(my_parameter: Gd<Node>);
}
rpc
  • You can use the #[rpc]  attribute to let your functions act as remote procedure calls (RPCs) in Godot. This is the Rust equivalent of GDScript’s @rpc  annotation. #[rpc]  is only supported for classes inheriting Node , and they need to declare a Base<T>  field.

  • The syntax follows GDScript’s @rpc .

use godot::classes::multiplayer_api::RpcMode;
use godot::classes::multiplayer_peer::TransferMode;
use godot::prelude::*;
use godot::register::RpcConfig;

#[godot_api]
impl MyStruct {
    #[rpc(unreliable_ordered, channel = 2)]
    fn with_defaults(&mut self) {}

    #[rpc(authority, unreliable_ordered, call_remote, channel = 2)]
    fn explicit(&mut self) {}

    #[rpc(config = MY_RPC_CONFIG)]
    fn external_config_const(&mut self) {}

    #[rpc(config = my_rpc_provider())]
    fn external_config_fn(&mut self) {}
}

const MY_RPC_CONFIG: RpcConfig = RpcConfig {
    rpc_mode: RpcMode::AUTHORITY,
    transfer_mode: TransferMode::UNRELIABLE_ORDERED,
    call_local: false,
    channel: 2,
};

fn my_rpc_provider() -> RpcConfig {
    RpcConfig {
        transfer_mode: TransferMode::UNRELIABLE_ORDERED,
        channel: 2,
        ..Default::default() // only possible in fn, not in const.
    }
}
func
  • You can use the #[func]  attribute to declare your own functions. These are exposed to Godot and callable from GDScript.

  • More information .

Special

gd_extension
#[gdextension]
godot_dyn
#[godot_dyn]
  • Generates a Class  -> dyn Trait  upcasting relation.

  • This attribute macro can be applied to impl MyTrait for MyClass  blocks, where MyClass  is a GodotClass .

  • It will automatically implement MyClass: AsDyn<dyn MyTrait>  for you.

  • Establishing this relation allows godot-rust to upcast MyGodotClass  to dyn Trait  inside the library’s DynGd  smart pointer.

Attribute Macros: Class

class(base=algo)
  • Unlike C++, Rust doesn’t really have inheritance, but the GDExtension API lets us “inherit” from a Godot-provided engine class.

  • By default, classes created with this library inherit from RefCounted , like GDScript.

  • To specify a different class to inherit from, add #[class(base = Base)]  as an annotation on your struct :

#[derive(GodotClass)]
#[class(init, base=Node2D)]
struct MyStruct {
    // ...
}
  • If you need a reference to the base class, you can add a field of type Base<T> . The derive macro will pick this up and wire your object accordingly. You can access it through self.base()  and self.base_mut()  methods.

#[derive(GodotClass)]
#[class(init, base=Node2D)]
struct MyStruct {
    base: Base<Node2D>,
}
class(init)
  • If you don’t override init()  manually (within a #[godot_api]  block), gdext can generate a default constructor for you. This constructor is made available to Godot and lets you call MyStruct.new()  from GDScript.

  • The generated init  function will initialize each struct field (except the field of type Base<T> , if any) using Default::default() .

#[derive(GodotClass)]
#[class(init)]
struct MyStruct {
    // ...
}
  • To assign some other value, annotate the field with #[init(val = ...)] :

#[derive(GodotClass)]
#[class(init)]
struct MyStruct {
    #[init(val = 42)]
    my_field: i64
}
class(no_init)
  • You can also disable  construction from GDScript. This needs to be explicit via #[class(no_init)] . Simply omitting the init / no_init  keys and not overriding your own constructor will cause a compile error.

#[derive(GodotClass)]
#[class(no_init)]
struct MyStruct {
   // ...
}
class(tool)
#[class(tool)]
  • Its lifecycle methods ( ready() , process()  etc.) will be invoked in the editor.

  • Very similar to @tool .

class(editor_plugin)
  • It will be turned into an editor plugin. The class must then inherit from EditorPlugin , and an instance of that class will be automatically added to the editor when launched.

  • Usually combined with tool .

class(rename)
  • You may want to have structs with the same name. With Rust, this is allowed using mod . However, in GDScript there are no modules, namespaces, or any such disambiguation. Therefore, you need to change the names before they can get to Godot. You can use the rename  key while defining your GodotClass  for this.

mod animal {
    #[derive(GodotClass)]
    #[class(init, rename=AnimalToad)]
    pub struct Toad {}
}

mod npc {
    #[derive(GodotClass)]
    #[class(init, rename=NpcToad)]
    pub struct Toad {}
}
  • These classes will appear in the Godot editor and GDScript as “AnimalToad” or “NpcToad”.

class(internal)
  • Registers a class with Godot, but does not have it show up in the editor.

#[derive(GodotClass)]
#[class(base=Node, init, internal)]
pub struct Foo {}

Objects

Gd<T>

What it stores
  • Can only hold instances of Godot classes ( Node , RefCounted , etc.) or user-declared structs (declared with #[derive(GodotClass)] ).

    • It does not hold built-in types ( Vector3 , Color , i32 ).

  • Gd<T>  never holds null objects.

    • If you need nullability, use Option<Gd<T>> . To pass null objects to engine APIs, you can additionally use Gd::null_arg()  as a shorthand.

Memory
  • The memory management strategy is fully dependent on T :

    • Objects that inherit from RefCounted :

      • Every time a smart pointer is shared using Clone::clone() , the reference counter is incremented, and every time one is dropped, it is decremented.

      • It ensures that the last reference (either in Rust or Godot) will deallocate the object and call T ’s destructor.

    • Manual :

      • Objects inheriting from Object  which are not RefCounted  (or inherited) are manually-managed ; most notably, this includes all Node  classes.

      • Their destructor is not automatically called (unless they are part of the scene tree).

      • Creating a Gd<T>  means that you are responsible for explicitly deallocating such objects using free() .

    • Dynamic :

      • Due to polymorphism, a Gd<Object>  ( T=Object ) can point to either reference-counted or manually-managed types at runtime.

      • Note that if the dynamic type is also Object , the memory is manually-managed.

Construction
  • To construct default instances of various Gd<T>  types, there are extension methods on the type T  itself:

    • Manually managed :

      • NewAlloc::new_alloc() .

    • Reference-counted :

      • NewGd::new_gd() .

        • Gd::<T>::default()  is equivalent to the shorter T::new_gd()  and primarily useful for derives or generics.

      • Gd::default() .

        • For reference-counted types that are constructible. For user types, this means they must expose an init  function or have a generated one.

    • Singletons :

      • T::singleton()  (inherent).

  • Gd::from_init_fn(function) .

    • For Rust objects with Base<T>  field, which are constructed inside the smart pointer. This is a very handy function if you want to pass extra parameters to your object upon construction.

  • Gd::from_object(rust_obj) .

    • For existing Rust objects without a Base<T>  field that are moved into  the smart pointer.

Bind Guards (I think this is Ownership)
  • The bind()  and bind_mut()  methods allow you to obtain a shared or exclusive guard to the user instance.

  • These provide interior mutability similar to RefCell , with the addition that Gd  simultaneously handles reference counting (for some types T ).

  • Dropping :

    • By closing a { }  block or calling std::mem::drop() .

    • Holding a bind guard will prevent other code paths from obtaining their own shared/mutable bind. As such, you should drop the guard as soon as you don’t need it anymore.

  • When you declare a #[func]  method on your own class, and it accepts &self  or &mut self , an implicit bind()  or bind_mut()  call on the owning Gd<T>  is performed.

    • This is important to keep in mind, as you can get into situations that violate dynamic borrow rules; for example if you are inside a &mut self  method, make a call to GDScript and indirectly call another method on the same object (re-entrancy).

Obtaining References
  • Gd::from_instance_id(id)  and Gd::try_from_instance_id(id)  to obtain a pointer to an object which is already alive in the engine.

Gd Mut
  • Mutably/exclusively bound reference guard for a Gd  smart pointer.

  • See Gd::bind_mut  for usage.

Gd Ref
  • Immutably/shared bound reference guard for a Gd  smart pointer.

  • See Gd::bind  for usage.

Gd<T>: Base

Base
  • Restricted version of Gd , to hold the base instance inside a user’s GodotClass .

  • Behaves similarly to Gd , but is more constrained. Cannot be constructed by the user.

Base Mut
  • Mutable/exclusive reference guard for a Base  pointer

  • This can be used to call methods on the base object of a Rust object, which takes &self  or &mut self  as the receiver.

  • See WithBaseField::base_mut()  for usage.

Base Ref
  • Shared reference guard for a Base  pointer.

  • This can be used to call methods on the base object of a Rust object that takes &self  as the receiver.

  • See WithBaseField::base()  for usage.

Gd<T>: DynGd

Bounds

  • Declarer  tells you whether the class is provided by the engine or user-defined.

    • DeclEngine  is used for all classes provided by the engine (e.g. Node3D ).

    • DeclUser  is used for all classes defined by the user, typically through #[derive(GodotClass)] .

  • Memory  is used to check the memory strategy of the static  type.
    This is useful when you operate on associated functions of Gd<T>  or T , e.g. for construction.

    • MemRefCounted  is used for RefCounted  classes and derived.

    • MemManual  is used for Object  and all inherited classes, which are not RefCounted  (e.g. Node ).

Examples
  • Declare a custom smart pointer which wraps Gd<T>  pointers, but only accepts T  objects that are manually managed:

    use godot::prelude::*;
    use godot::obj::{bounds, Bounds};
    
    struct MyGd<T>
    where T: GodotClass + Bounds<Memory = bounds::MemManual>
    {
       inner: Gd<T>,
    }
    

OnReady

use godot::prelude::*;

#[derive(GodotClass)]
#[class(base = Node)]
struct MyClass {
   base: Base<Node>,
   auto: OnReady<i32>,
   manual: OnReady<i32>,
}

#[godot_api]
impl INode for MyClass {
    fn init(base: Base<Node>) -> Self {
       Self {
           base,
           auto: OnReady::new(|| 11),
           manual: OnReady::manual(),
       }
    }

    fn ready(&mut self) {
       // self.auto is now ready with value 11.
       assert_eq!(*self.auto, 11);

       // self.manual needs to be initialized manually.
       self.manual.init(22);
       assert_eq!(*self.manual, 22);
    }
}
use godot::prelude::*;

#[derive(GodotClass)]
#[class(init, base = Node)]
struct MyClass {
   base: Base<Node>,
   #[init(node = "ChildPath")]
   auto: OnReady<Gd<Node2D>>,
   #[init(val = OnReady::manual())]
   manual: OnReady<i32>,
}

#[godot_api]
impl INode for MyClass {
    fn ready(&mut self) {
       // self.node is now ready with the node found at path `ChildPath`.
       assert_eq!(self.auto.get_name(), "ChildPath".into());

       // self.manual needs to be initialized manually.
       self.manual.init(22);
       assert_eq!(*self.manual, 22);
    }
}

Notes

use godot::{
    prelude::*,
    classes::Sprite2D,
};

#[derive(GodotClass)]
#[class(base=Node)]
struct Visuals {
    slime: Option<Gd<Sprite2D>>,
    base: Base<Node>
    }

  
#[godot_api]
impl INode for Visuals {
    fn init(base: Base<Node>) -> Self {        
        Self {
            slime: None,
            base
            }
    }

    fn ready(&mut self,) {
        self.slime = Some(self.base().get_node_as::<Sprite2D>("Slime"));
    }

    fn physics_process(&mut self, delta: f64) {      
        let new_position = self.slime().get_position() + Vector2::new(10.0 * delta as f32, 0.0);
        self.slime().set_position(new_position);

        let angular_speed = 1.0;
        let new_rotation = self.slime().get_rotation() + (angular_speed * delta as f32);
        self.slime().set_rotation(new_rotation);
    }

}

  
  

impl Visuals {
    fn slime(&mut self) -> &mut Gd<Sprite2D> {
        self.slime.as_mut().unwrap()
        // as_mut()
            // Creates a new "mutable reference".
                // "Converts self (an Option) into a mutable reference to the contained value, if it exists. Returns Option<&mut T>."
            //-> &mut Gd<Sprite2D>
        // as_deref_mut
            // Same as `as_mut`, but then ownership is immediately taken.
                // "After `as_mut`, the deref_mut() method returns the mutable reference to the 'target value' of T."
                // "`as_mut` is enough because you only need a mutable reference to the `Gd<Sprite2D>` itself, not the internal 'target value' (`Sprite2D`)."
            //-> &mut Sprite2D

    }

}

Threads

  • As a rule of thumb, if you must use threading, prefer to use Rust threads  over Godot threads.

  • The Cargo feature experimental-threads  provides experimental support for multithreading. The underlying safety rules are still being worked out, as such you may encounter unsoundness and an unstable API.

  • Godot’s own thread safety rules  apply. Types in this crate implement (or don’t implement) Send  and Sync  wherever appropriate, but the Rust compiler cannot check what happens to an object through C++ or GDScript.

Swift (Godot Swift)

Problems that made the experience terrible

  • Related to the time I tried in [2025-01].

SwiftGodot
  • All SwiftGodot documentation is wrong, incomplete, or insufficient.

  • Nonsensical things, where things stop working without any warning, or things that don’t work with no logic.

  • 3000 different .dll dependencies that must be in the main .dll folder.

    • You need to copy all .dll files from a folder in AppData/Local/Programs/Swift/etc,etc,etc. It’s an inconvenient mess.

  • 3000 different dependencies that must be mentioned in the GDExtension for ~exporting (I think, nobody knows for sure).

  • The devs have NO priority or even equipment to test things on Windows.

  • Could not make the LSP recognize the SwiftGodot import in VSCode.

  • The SwiftGodot repository is very disorganized.

Swift
  • TERRIBLE compilation time.

    • Compiling a "hello world" sometimes takes about 10 minutes.

    • There were some problems happening in Windows, but even after "solving" it, the comp time is still huge.

  • No binaries for Windows and Linux.

  • Tons of annoying dependencies to download on Windows, a very long installation process, where 3000 things can go wrong during it.

    • It can be ~simple, but at the same time it can cause problems and be exhausting.

  • Found Swift Package Manager to be terrible.

    • Every time I had to reset tons of caches, do package clean, delete Package.resolve to get a different result.

    • Several times the build did not run, with no indication of an error.

    • Sometimes I need to move the project folder.

  • The LSP is not good.

    • It did not work on Windows for Swift 6.0+, and months went by with no response on Github, so when they finally "fixed" the problem, the solution was for version 6.1.0 (pre-release), which is bizarre.

  • The project folder path cannot contain accents, which is annoying.

  • There is no "real" Windows support, as .platform  in Package.swift does not have this option, which is very strange. Swift can be used on Windows and exported as .dll or .exe, but this is not intuitive from that option.

  • Apple; that should already be a no go.

  • Terrible ecosystem.

  • You need to enable Windows Developer Mode, which is a hassle.

  • Swift 6.0+ Concurrency system is strange and uncomfortable, breaking the convenience of using Swift for simpler tasks.

    • This was my initial impression, but maybe studying more about it will make it less inconvenient.

    • Anyway, having to study concurrency just to make a simple project compile is frustrating.

  • No tutorials.

Current problems
  • https://github.com/migueldeicaza/SwiftGodot/issues/160

  • https://github.com/migueldeicaza/SwiftGodot/issues/589

  • 6.0+ :

    • https://github.com/migueldeicaza/SwiftGodot/issues/462

    • https://github.com/migueldeicaza/SwiftGodot/pull/612#issuecomment-2555523506

About

GodotKit
  • Swift Godot Kit .

    • Needs libgodot , something like that, still experimental.

    • "This API can be used to implement new capabilities in both Godot extensions, or to drive Godot entirely from Swift."

    • Tutorial .

Xogot (Swift Godot on iPad)

Setup and Compilation

Initialize Swift in the current folder as a Library
  • swift package init --type library

Build
  • swift build

  • Using swift build -debug-info-format codeview  will generate the pdbs, which need to be placed alongside the dlls to stop Godot complaining.

GDExtension

[configuration]
entry_symbol = "swift_entry_point"
compatibility_minimum = 4.3

[libraries]
windows.debug.x86_64 = "res://bin/SimpleRunnerDriver.dll"

[dependencies]
windows.debug.x86_64 = {"res://bin/SwiftGodot.dll" : ""}
Dependencies
  • "This means copying all *.dll  files from C:\Program Files\Swift\runtime-development\usr\bin\ ".

  • C:\Users\caior\AppData\Local\Programs\Swift\Runtimes\6.1.0\usr\bin .

    • Copied all files from this folder to the folder where floresta.dll  was placed.

  • "Also add this section to your Godot .gdextension  after the libraries  section. This allows the dlls to copy automatically when exporting your project."

[dependencies]

windows.debug = {
    "res://bin/BlocksRuntime.dll" : "",
    "res://bin/dispatch.dll" : "",
    "res://bin/Foundation.dll" : "",
    "res://bin/FoundationNetworking.dll" : "",
    "res://bin/FoundationXML.dll" : "",
    "res://bin/swiftCore.dll" : "",
    "res://bin/swiftCRT.dll" : "",
    "res://bin/swiftDispatch.dll" : "",
    "res://bin/swiftDistributed.dll" : "",
    "res://bin/swiftObservation.dll" : "",
    "res://bin/swiftRegexBuilder.dll" : "",
    "res://bin/swiftRemoteMirror.dll" : "",
    "res://bin/swiftSwiftOnoneSupport.dll" : "",
    "res://bin/swiftWinSDK.dll" : "",
    "res://bin/swift_Concurrency.dll" : "",
    "res://bin/swift_Differentiation.dll" : "",
    "res://bin/swift_RegexParser.dll" : "",
    "res://bin/swift_StringProcessing.dll" : "",
}
windows.release = {
    "res://bin/BlocksRuntime.dll" : "",
    "res://bin/dispatch.dll" : "",
    "res://bin/Foundation.dll" : "",
    "res://bin/FoundationNetworking.dll" : "",
    "res://bin/FoundationXML.dll" : "",
    "res://bin/swiftCore.dll" : "",
    "res://bin/swiftCRT.dll" : "",
    "res://bin/swiftDispatch.dll" : "",
    "res://bin/swiftDistributed.dll" : "",
    "res://bin/swiftObservation.dll" : "",
    "res://bin/swiftRegexBuilder.dll" : "",
    "res://bin/swiftRemoteMirror.dll" : "",
    "res://bin/swiftSwiftOnoneSupport.dll" : "",
    "res://bin/swiftWinSDK.dll" : "",
    "res://bin/swift_Concurrency.dll" : "",
    "res://bin/swift_Differentiation.dll" : "",
    "res://bin/swift_RegexParser.dll" : "",
    "res://bin/swift_StringProcessing.dll" : "",
}
  • SwiftGodot.dll

    • "I'm not getting any SwiftGodot.dll being generated, I have no idea where to get this dll, but it worked even without it". "Your package is shared but by default SwiftGodot builds as static on Windows, which means that its contents will be linked in to your .dll - so that’s all you need."

    • To build SwiftGodot as a DLL you need to change the Package.swift for it, which requires a bit of a hack of the SwiftGodot package itself. I wouldn’t bother if you’re up & running - I don’t think it relates to the pdb problem (which I think you can ignore anyway for now), and it might break something else.

  • .a

    • A ".a" is not going to help, it is not looked up.

Package.swift

// swift-tools-version: 5.10.1

import PackageDescription

let package = Package(
    name: "floresta",
    products: [
        .library(
            name: "floresta",
            type: .dynamic,
            targets: ["floresta"]
        ),
    ],
    dependencies: [
        .package(
            name: "SwiftGodot",
            path: ".build/checkouts/SwiftGodot"
        )
    ],
    targets: [
        .target(
            name: "floresta",
            dependencies: [
                "SwiftGodot"
            ],
            swiftSettings: [.unsafeFlags(["-suppress-warnings"])]
        ),
        .testTarget(
            name: "floresta_test",
            dependencies: ["floresta"]
        )
    ]
)
Type of Library
  • A library needs  to be .dynamic  on Windows, since Godot GDExtension  primarily works with .dll  files  as dynamic libraries, not .lib  files.

Dependencies
  • (2025-01-23)

    • Used 5.10.1

    • Forked the SwiftGodot repository and referenced my fork within the .build folder.

      dependencies: [
          .package(
              name: "SwiftGodot",
              path: ".build/checkouts/SwiftGodot"
          )
      ],
      
    • Used a branch from my fork pointing to the commit before the Swift 6.0+ build system update.

  • Binary dependencies :

    • XCFramework is specific to Apple platform versions of SwiftGodot and likely won't work on Windows.

Targets
  • Maybe necessary  to use swiftSettings: [.unsafeFlags(["-suppress-warnings"])] .

How I made it work

  • Swift 6.1.0.

Package.swift
  • .dynamic library

// swift-tools-version: 5.10

import PackageDescription

let package = Package(
    name: "floresta",
    products: [
        .library(
            name: "floresta",
            type: .dynamic,
            targets: ["floresta"]
        ),
    ],
    dependencies: [
        .package(
            url: "https://github.com/migueldeicaza/SwiftGodot",
            branch: "main"
        )
    ],
    targets: [
        .target(
            name: "floresta",
            dependencies: [
                "SwiftGodot"
            ],
            swiftSettings: [.unsafeFlags(["-suppress-warnings"])]
        ),
        .testTarget(
            name: "floresta_test",
            dependencies: ["floresta"]
        )
    ]
)
GDExtension
[configuration]
entry_symbol = "swift_entry_point"
compatibility_minimum = 4.3

[libraries]
windows.debug.x86_64 =   "res://swift/floresta.dll"
; windows.release.x86_64 = "res://swift/floresta.dll"

[dependencies]
windows.debug.x86_64 = {"res://bin/SwiftGodot.dll" : ""}
  • C:\Users\caior\AppData\Local\Programs\Swift\Runtimes\6.1.0\usr\bin .

    • Copied all files from this folder to the folder where floresta.dll  was placed.

Class registration

  • @Godot

    • The @Godot  macro does a few things, it creates a default constructor that follows the convention to call the parent init()  method and performs any registrations that you might have done in your class for variables or methods.

    • When you use the @Godot  macro, a number of additional macros can be used inside your class, like #signal   to define signals , @Callable  to surface a method to Godot, and @Export  to surface properties .

    • Behind the scenes these macros use the lower-level ClassDB  API to define functions, properties and their values.

  • Automatic way:

    
    targets: [
            .target(
                // this plugin will generate a source file visible to compiler with '#initSwiftExtension(cdecl: "swift_entry_point", types: [SpinningCube.self])'
                plugins: [
                    .plugin(name: "EntryPointGeneratorPlugin", package: "SwiftGodot")
                ]
            )
    ]
    
  • Simplified way:

    import SwiftGodot
    
    #initSwiftExtension(cdecl: "swift_entry_point", types: [])
    
    import SwiftGodot
    
    #initSwiftExtension(cdecl: "swift_entry_point", types: [SpinningCube.self])
    
  • Extended way:

    /// We register our new type when we are told that the scene is being loaded
    func setupScene (level: GDExtension.InitializationLevel) {
        if level == .scene {
            register(type: SpinningCube.self)
        }
    }
    
    // Export our entry point to Godot:
    @_cdecl("swift_entry_point")
    public func swift_entry_point(
        interfacePtr: OpaquePointer?,
        libraryPtr: OpaquePointer?,
        extensionPtr: OpaquePointer?) -> UInt8
    {
        print ("SwiftGodot Extension loaded")
        guard let interfacePtr, let libraryPtr, let extensionPtr else {
            print ("Error: some parameters were not provided")
            return 0
        }
        initializeSwiftModule(interfacePtr, libraryPtr, extensionPtr, initHook: setupScene, deInitHook: { x in })
        return 1
    }
    

Debugging in VSCode

  • “create a launch.json file” and select “LLDB”.

  • Things to change :

    • Add a line for program  with the path to your Godot executable

    • Add a line for args  with any necessary arguments to launch your game, such as the path to your Godot project.

    • specify the cwd  (current working directory)

  • Example:

    {
       "type": "lldb",
       "request": "launch",
       "name": "Launch Godot Game",
       "program": "path/to/your/Godot/executable",
       "args": ["--path", "path/to/your/game/project"],
       "cwd": "${workspaceFolder}"
    }
    
  • If you want to attach to PID :

    {
       "version": "0.2.0",
       "configurations": [
          {
             "type": "lldb",
             "request": "launch",
             "name": "Launch Godot Game",
             "program": "path/to/your/Godot/executable",
             "args": ["--path", "path/to/your/game/project"],
             "cwd": "${workspaceFolder}"
          },
          {
             "type": "lldb",
             "request": "attach",
             "name": "Attach to PID",
             "pid": "${command:pickProcess}"
          }
       ]
    }
    
    • "alternatively, if you do not need access to the source, you can use the .binaryTarget  feature of SwiftPM and reference an .xcframework  that I have conveniently published on GitHub at SwiftGodotBinary ".

Explanations

GDScript Reference

Variables
  • Variables are not exposed to Godot

  • You can export properties though

  • @Export

async / await
  • Unlike GDScript, await can only be invoked from async  methods.

func demo() async {
    await someSignal.emitted
}
  • If you are inside of a function that is not async, you need to wrap your code in Task, like this:

func demo() {
    // We are not an async function, but we can start a Task
    Task {
        await someSignal.emitted
    }
}
Singletons
let pressed = Input.isActionPressed(ui_down)
Signals
Callable
  • In SwiftGodot, you can create Callable  instances by directly passing a Swift function that takes an array of Variant  arguments, and returns an optional Variant  result, like this:

func myCallback(args: borrowing Arguments)-> Variant? {
    print ("MyCallback invoked with \(args.count) arguments")
    return nil
}


let myCallable = Callable(myCallback)
  • Alternatively, you can use a StringName that binds a method that you have exported (via, the @Callable  macro), like this:

@Callable func myCallback(message: String) {
    GD.print(message)
}
  • You can call the callable in GDScript by invoking call() method of the exported Swift type.

MySwiftNode.myCallback.call("Hello from Swift!")
Resources

GDScript Reference: Types

Variant
  • You can create Variants from types that conform to the VariantStorable protocol.

  • This includes the following types:

    • Godot’s native types: GString, Vector, Rect, Transform, Plane, Quaternion, AABB, Basis, Projection, Int64, NodePaths, RIDs, Callable, GDictionary, Array and PackedArrays.

    • Swift types that SwiftGodot adds convenience conformances for: Bool, Int, String and Float

    • Godot’s objects: e.g. Node, Area2D

    • Your own subclasses of SwiftGodot.Object type.

    • Other types that you can manually conform to VariantStorable.

  • You wrap your data type by calling one of the Variant  constructors, and then you can pass this variant to Godot functions that expect a Variant .

  • For example, to pass the value true :

let trueVaraint = Variant (true)
print (trueVariant.description)
  • If you have a Variant  and want to extract its value, you typically use this pattern:

/// This method will return nil if the Variant provided does not contain
/// a boolean value.   Otherwise it will contain the boolean stored in the
/// variant.
func getBoolValue (variant: Variant) -> Bool? {
    guard let boolValue = Bool (variant) else {
        return nil
    }
    return boolValue
}
Arrays
@Export
var myResources: VariantCollection<Resource>
@Export
var myNodes: ObjectCollection<MySpinnerCube>

GlobalScope Reference

GlobalScope
  • Global functions and some constants had to be moved to classes to avoid polluting the global name space, and you can find them in the GD  class.

Math Functions
  • The various GDScript Math functions like abs , acos , atan  as well as their helper functions like clamp  are defined as static functions in the GD  class.

  • Generally, you can use the Swift versions instead of the GDScript versions, which are more complete and do not incur a marshaling overhead.

Random
  • The random functions like randi , randf  are under the GD  class.

  • Swift provides a set of random functions that are just as good, like Int.random(in:)  or Double.random(in:)  that operate on Swift ranges.

Nodes

onready
  • There is no support for onready .

@Godot
class Demo: Node {
    var myLabel: Node3D?

    override func _ready () {
        myLabel = getNode (path: "myLabel") as? Node3D
    }
}
  • If you do not need to load the node right away, and you merely need to be able to access it, you can use this instead:

@Godot
class Main: Node {
    @SceneTree(path: "CharacterBody2D") var player: PlayerController?
    @SceneTree(path: "locations/spawnpoint") var spawnpoint: Node2D?
    @SceneTree(path: "Telepoint") var teleportArea: Area2D?
}
class Demo: Node {
    @BindNode var myLabel: Node3D
}
init
  • Not understood.

@Godot
class NetworkedNode: Node {
    required init() {
        super.init()
        onInit()
    }


    required init(nativeHandle: UnsafeRawPointer) {
        super.init(nativeHandle: nativeHandle)
        onInit()
    }


    func onInit() {
        print("Was init!")
    }
}
export
  • Fields and properties:

    @Export
    var number: Int
    
    
    @Export
    var AnotherNumber { get { ... } set { ... }
    
  • Default values:

    @Export
    var number: Int = 0
    
    
    @Export
    var text: String? = nil        // Allows for nil
    
    
    @Export
    var greeting = "Hello World"   // Exported field specifies a default value
    
  • Nodes:

    @Export
    public var node: Node { 
        get { 
            return myInternalNode
        } 
        set { 
            print ("Setting the node")
        } 
    }
    
    @Export
    public Node Node { get; set; }
    
    • If you find yourself that you do not want to provide manual get/set properties in your export, and want to have an optional for one of the Object types, you can use something like this:

    @Export(.nodeType, "Camera3D")
    var camera: Camera3D? = nil
    
    • The parameter to .nodeType  needs to match the type of the object.

  • Resources:

    @Export
    var resource: Resource { get {} set {} }
    
    @Export
    var resource: AnimationNode
    
    @Export
    public var resource: Resource { 
        get { 
            return myInternalResource
        } 
        set { 
            print ("Setting my resource")
        } 
    }
    
  • Enums:

    enum MyEnum: Int, CaseIterable {
        case first
        case second
    }
    
    
    @Godot
    class Sample: Node {
        @Export(.enum)
        var myValue: MyEnum
    }
    
  • Customizing:

    @Export(.range, "0,20,")
    var number: Int = 0
    
    @Export(.range, "-10,20,0.2")
    var number = 0
    
    @Export(.range, "0,100,1,or_greater,or_less")
    var number: Int = 0
    
    @Export(.file)
    var GameFile: String? 
    
    @Export(.dir)
    var gameDirectory: String?
    
    @Export (.file, "*.txt")
    var GameFile: String?
    
    @Export (.globalFile, "*.png")
    var toolImage: String?
    
    @Export (.globalDir)
    var toolDir: String?
    
    @Export (.multilineText)
    var text: String?
    
    @Export(.expEasing)
    public transitionSpeed: Float = 0
    
    @Export(.colorNoAlpha)
    var color: Color { get {} set {} }
    
export_group
  • It is possible to group your exported properties inside the Godot Inspector with the #exportGroup  macro. Every exported property after this annotation will be added to the group. Start a new group or use #export_group  (””) to break out.

    #exportGroup("My Properties")
    @Export var number = 3
    
  • You can also specify that only properties with a given prefix be grouped, like this:

    #exportGroup("My Properties", prefix: "health")
    @Export var health_reload_speed = 3
    
  • Groups cannot be nested, use #exportSubgroup  to create subgroups within a group.

    #exportSubgroup("Extra Properties")
    #export var string = ""
    #export var flag = false
    
tool
@Godot(.tool)

Useful Manipulations

Null Check
  • You can check in one go if a given variant contains a valid object and is of a given type, with the asObject(_:)  method, combined with Swift’s “let”:

func demo(input: Variant) {
    if let node = input.asObject(Node.self) {
        // We have a happy node inside 'input'
    } else {
        print ("The variant did not wrap an object, if it did, it was either nil, or was not of type Node")
    }
}