SwiftGodot

0.60.1

New Godot bindings for Swift
migueldeicaza/SwiftGodot

What's New

Release 0.60.1

2025-04-26T16:07:47Z

Swift bindings for Godot 0.60.1 release

We did a big jump from version 0.50 to 0.60 as this contains a large set of improvements to the binding thanks to Elijah Semyonov. Unless otherwise noted, the contributions were done by Elijah.

This is a large set of improvements that vastly improve the performance, reliability, usability and correctness of SwiftGodot andwe have tested this on a very large SwiftGodot app (Xogot). There are some changes required to your code, but the changes required are
simple, and detailed below.

Perhaps the biggest surprise is that the new TypedArray needs the T to be optional whenever you are dealing with a Godot Object, so you can now have things like: TypedArray<Int> and TypedArray<Node?> as well as the signal connection operations no longer returning an Object token that you pass to disconnect, but instead a Callable token that you must pass to disconnect.

Changes

Callable and Signals

  • @Export-able Swift closures properties proxied via Callable
  • Make Callable constructor taking an arbitrary Swift closure, where arguments and return types are bridgeable Gogot types. No manual working with Arguments anymore! :)
  • Godot -> Swift calls are now heap-allocation free. We no longer need heap-allocated Variants neither for argument extraction, nor returned value. FastVariant is nice!
  • Refine SignalWithArguments make it a struct. Make it reference wrapped object weakly (not preventing Swift from destroying it). That prevents signals from retaining the target object, which could lead to unexpected memory leaks. This behavior aligns with Godot Signal.
  • Deprecate SignalWithArguments.emitted, it's unsafe and will leak coroutine state referenced by continuation and captured context if it's never fired.
  • @callable macro now supports an autoSnakeCase parameter if you want to have this applied directly to your function to surface in a Godot-friendly way.

Collection Changes

  • Rename GArray to VariantArray. Leave deprecated typealias.
  • Introduces TypedArray uniting functionality of ObjectCollection and VariantCollection. It's almost fully source compatible but will require inserting some ? in existing code, but Swift didn't allow to avoid it. ObjectCollection and VariantCollection are deprecated type aliases to TypedArray. Migrating requires changing occurrences of ObjectCollection<Type> to TypedArray<Type?>
  • Introduce GodotBuiltinConvertible, a happy sibling of VariantConvertible that can proxy user type via specific Godot builtin instead of general purpose Variant.
  • Remove all special treatments of Swift.Array. It's simply conforming to GodotBuiltinConvertible if its elements are valid TypedArray elements ( builtin types, or Optional Object-derived types).
  • Change semantics of VariantArray to TypedArray conversion slightly. Now it follows the Godot rules: if conversion fails, it returns an empty (but typed!) array.
  • Constrain registration API to Object.Type instead of Wrapped.Type. It allows us to store Object-inherited classes metatypes of both framework types and user types for future improvements.
  • Godot proc addresses static storage is moved to a separate case-less enum for each builtin type. It is called GodotInterfaceFor{TypeName}. This is also a groundwork for per-class procs caches that should be manually flushed in projects like Xogot. Later this enum with static storage (or computed properties in case of Xogot) can be converted into a struct with instance properties stored into a global variables like var giNode: GodotInterfaceForNode!, similar to how general GodotInterface is stored in var gi: GodotInterface!.
  • Rename Dictionary to VariantDictionary. Leave deprecated typealias.
  • Implement TypedDictionary which behaves similar to TypedArray and facades Godot typed dictionary.
  • Make Swift.Dictionary a bridgeable type via GodotBuiltinConvertible.
  • Make Godot class builtins Hashable if they have hash() function.

Strings

  • String marshaling directly to and from FastVariant and Variant avoid allocation of GString class instance
  • Introduce FastStringName, a special kind of StringName that allows wrapping StaticString (Swift string with static storage), which allows avoiding allocations when StringName is constructed from non-interpolated String literal.
  • Use FastStringName in generated code during proc address retrieval.
  • Throw diagnostics error for an attempt to macro static members (was never supported).

Core - Variants

Internally, the VariantStorable and VariantRepresentable were replaced with the new VariantConvertible and a family of operations that simplified plenty of the operations on variants, removed boilerplate and ad-hoc hacks in various places.

This allowed the various macros in the SwiftGodot family to rely on the compiler and its type inference system to do the heavy-lifting when exposing types rather than rely on processing the syntax and attempting to cover various type scenarios.

This means that we now have a more reliable, more Swift-y way of working and will reduce surprised in the future for corner cases, as it is now the compiler doing the work, not relying on the macro to perform good guesses (Elijah)

  • All macro logic related to @Export, @Callable and @Godot relies on static dispatch into correctly selected overloaded functions for inferring PropInfo. That effectively removes a need for our handicapped attempt of type analysis based on SwiftSyntax.

  • .enum hints are filled automatically for @Export when eligible.

  • No explicit type information is needed for Node, @Export-ed properties anymore.

  • All places that need to distinguish nullability of marshalled types now do it according to Godot rules. Builtin types are never
    null. Objects subclasses and Variant are nullable and can be
    constrained to non-nullable on Swift side.

  • Code-generated SignalWithArguments now properly infers nullability
    according to these rules.

  • Names of arguments of @Callable are now correctly processed if they use MemberTypeSyntax (aka qualified names).

  • VariantStorable and VariantRepresentable are removed. Source-compatibility still stays, if they were not used directly.

  • Nice APIs to tackle Variant conversions. variant.to(Int.self), 50.toVariant(), Variant("Hello"), String(variant), everything is possible, except Object(variant) that won't crash runtime anymore, it simply doesn't exist.

  • Introduce lightweight FastVariant that doesn't require a heap allocation.

  • Integrate FastVariant for extraction specific types from Arguments. No heap allocations for each extracted argument, FastVariant temporarily borrows VariantContent provided by Godot to extract types we are interested in.

  • Make a lot of Variant APIs @inlinable so that compilation of user projects can optimise access to those APIs.

  • All VariantConvertible types are compatible with macros. They will surface as Variant in Godot.

Interoperability

  • The @godot Macro generates fully qualified typenames to avoid clash with user
    typenames.

Initialization

  • #initSwiftExtension macro will topologically sort the classes to register and deregister them in the right order. This is especially important for proper functioning of EntryPointGeneratorPlugin, which emits meta type elements in the order of files and syntax traversal.

Internals

  • Cleaned up macro library. Dead code for type analysis was removed. SwiftSyntax queries specific to Godot were moved to corresponding *Syntax extensions. Now code is much easier to follow. A little brother of Generator.Printer was brought to macro library to avoid indentation mess during code generation.
  • Macro-generated code is completely detached from ClassInfo. It's a prerequisite for a future improvement, where registration and reregistration happens proactively during extension initialisation. classInitializer will be replaced with a static var classDescriptor allowing topological sorting and and other lazy inference where information about all generated classes is available.

4.4 Support

  • This bring support for the 4.4 API update (Gabor Koncz, Miguel de Icaza)

  • Godot 4.4 Typed Dictionaries are now supported (Elijah)

Note

Release 0.60 was tagged, but there were a handful of small issues that prevented from this being a glorious release, so we added a couple of fixes.

SwiftGodot


SwiftPM compatible Platforms Swift Package Index License

SwiftGodot provides Swift language bindings for the Godot 4.3 game engine using the new GDExtension system (for 4.1 compatibility, use the 4.1 branch, for 4.2 compatibility use the 4.2 branch).

SwiftGodot can be used to either build an extension that can be added to an existing Godot project, where your code is providing services to the game engine, or it can be used as an API with SwiftGodotKit which embeds Godot as an application that is driven directly from Swift.

Tutorials and Documentation:

Of interest to the community:

Driving Godot from Swift has the advantage that on MacOS you can debug your code from Xcode as well as the Godot code.

Screen.Recording.2023-04-02.at.11.59.18.AM.mov

Why SwiftGodot?

Quickly Getting Started

The SwiftGodotKick project can create a skeleton template GDExtension with Swift, as well as a standalone SwiftGodotKit project that can be used to quickly iterate on your game on MacOS.

Supported Platforms

Currently, SwiftGodot can be used in projects targeting the iOS, Linux, macOS, or Windows platforms. It may be possible to target additional platforms, but testing for other platforms has not been completed and stability cannot be verified at this time.

Development Status

SwiftGodot is built on the GDExtension framework, which is still in an experimental state, and consequently SwiftGodot is still in an experimental state. Compatibility may break in order to fix major bugs or include critical features. That being said, much of the Godot API surface has been implemented and SwiftGodot is suitable for use in small to mid size projects.

Consuming SwiftGodot

There are two ways of consuming SwiftGodot, you can either reference this module in SwiftPM by using this address - and it will trigger a complete source code build for you, or to quickly iterate on MacOS, you can use a convenient binary in the peer https://github.com/migueldeicaza/SwiftGodotBinary

Currently this requires Swift 5.9 or Xcode 15.

Working with this Repository

You should be all set by referencing this as a package from SwiftPM but if you want to just work on the binding generator, you may want to open the Generator project and edit the okList variable to trim the build times.

Driving Godot From Swift

To drive Godot from Swift, use the companion SwiftGodotKit module which embeds Godot directly into your application, which allows you to to launch the Godot runtime from your code.

Creating an Extension

Creating an extension that can be used in Godot requires a few components:

  • Your Swift code: this is where you bring the magic
  • A .gdextension file that describes where to find the requires Swift library assets
  • Some Swift registration code and bootstrap code
  • Importing your extension into your project

Your Swift Code

Your Swift code will be compiled into a shared library that Godot will call. To get started, the simplest thing to do is to create a Swift Library Package that references the Swift Godot package, like this:

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyFirstGame",
    products: [
        .library(name: "MyFirstGame", type: .dynamic, targets: ["MyFirstGame"]),
    ],
    dependencies: [
        .package(url: "https://github.com/migueldeicaza/SwiftGodot", branch: "main")
    ],
    targets: [
        .target(
            name: "MyFirstGame",
            dependencies: ["SwiftGodot"])]
)

The above will compile all of SwiftGodot for you - 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 https://github.com/migueldeicaza/SwiftGodotBinary

The next step is to create your source file with the magic on it, here we declare a spinning cube:

import SwiftGodot

@Godot(.tool)
class SpinningCube: Node3D {
    public override func _ready () {
        let meshRender = MeshInstance3D()
        meshRender.mesh = BoxMesh()
        addChild(node: meshRender)
    }

    public override func _process(delta: Double) {
        rotateY(angle: delta)
    }
}

Additionally, you need to write some glue code for your project to be loadable by Godot, you can do it like this:

/// 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
}

Alternatively, you can use the #initSwiftExtension macro:

import SwiftGodot

#initSwiftExtension(cdecl: "swift_entry_point", types: [SpinningCube.self])

Also, you can use EntryPointGeneratorPlugin that will scan the target source files and generate entry point called swift_entry_point with types array mentioning all classes with @Godot macro attached. All you need is to add plugins entry in your Package.swift as below:

let package = Package(
    name: "MyFirstGame",
    products: [
        .library(name: "MyFirstGame", type: .dynamic, targets: ["MyFirstGame"]),
    ],
    dependencies: [
        .package(url: "https://github.com/migueldeicaza/SwiftGodot", branch: "main")
    ],
    targets: [
        .target(
            name: "MyFirstGame",
            dependencies: ["SwiftGodot"],
            // 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")
            ]
        )
    ],
)

Bundling Your Extension

To make your extension available to Godot, you will need to build the binaries for all of your target platforms, as well as creating a .gdextension file that lists this payload, along with the entry point you declared above.

You would create something like this in a file called MyFirstGame.gdextension:

[configuration]
entry_symbol = "swift_entry_point"
compatibility_minimum = 4.2

[libraries]
macos.debug = "res://bin/MyFirstGame"
macos.release = "res://bin/MyFirstGame"
windows.debug.x86_32 = "res://bin/MyFirstGame"
windows.release.x86_32 = "res://bin/MyFirstGame"
windows.debug.x86_64 = "res://bin/MyFirstGame"
windows.release.x86_64 = "res://bin/MyFirstGame"
linux.debug.x86_64 = "res://bin/MyFirstGame"
linux.release.x86_64 = "res://bin/MyFirstGame"
linux.debug.arm64 = "res://bin/MyFirstGame"
linux.release.arm64 = "res://bin/MyFirstGame"
linux.debug.rv64 = "res://bin/MyFirstGame"
linux.release.rv64 = "res://bin/MyFirstGame"
android.debug.x86_64 = "res://bin/MyFirstGame"
android.release.x86_64 = "res://bin/MyFirstGame"
android.debug.arm64 = "res://bin/MyFirstGame"
android.release.arm64 = "res://bin/MyFirstGame"

In the example above, the extension always expects the platform specific payload to be called "MyFirstGame", regarless of the platform. If you want to distribute your extension to other users and have a single payload, you will need to manually set different names for those.

Installing your Extension

You need to copy both the new .gdextension file into an existing project, along with the resources it references.

Once it is there, Godot will load it for you.

Using your Extension

Once you create your extension and have loaded it into Godot, you can reference it from your code by using the "Add Child Node" command in Godot (Command-A on MacOS) and then finding it in the hierarchy.

In our example above, it would appear under Node3D, as it is a Node3D subclass.

Community

Join the community on Slack

Contributing

Have a bug fix or feature request you'd like to see added? Consider contributing! Join our community to get started.

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Sat May 31 2025 16:39:56 GMT-0900 (Hawaii-Aleutian Daylight Time)