godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.16k stars 97 forks source link

Adopt Swift as the scripting language #1900

Closed hyouuu closed 3 years ago

hyouuu commented 3 years ago

Describe the project you are working on

In the planning phase with friends to work on a game. For context I'm currently an iOS developer but has past experience making indie games, and interned at EA to make FIFA 11 back in 2010.

Describe the problem or limitation you are having in your project

Tried out GDScript and did some research, and feel that it has certain limitations:

  1. Lack of types in function parameters, which makes code hard to read and follow.
  2. Then when I try to jump to some var or class definitions, there is a lack of jumping support to definition for super classes etc.
  3. Lack of Optional support (https://github.com/godotengine/godot-proposals/issues/162).
  4. Being its own language, it's not exactly Python or C++ or something, so one has to constantly check references until fully accustomed to the language.
  5. Numerous sources claim GDScript is very slow in larger calculations - e.g. this says it's 9x slower than Java, 100x slower than C# (https://www.reddit.com/r/godot/comments/aisrey/i_have_created_a_minimal_benchmark_for_java_and/).
  6. Looking at its direction and progress for Godot 4.0, adding annotation etc makes the language more complicated, to achieve a similar effect Swift already implemented.
  7. Being an iOS developer, I have to learn a new script language without finding an actual advantage over my familiar Swift.
  8. Plus, there has been interests expressed to support Swift in Godot: https://godotengine.org/qa/45009/add-swift-support?show=50503#c50503, https://www.reddit.com/r/swift/comments/ghtai7/requesting_support_for_swift_development_for/

Describe the feature / enhancement and how it helps to overcome the problem or limitation

I propose Godot to switch to Swift as its core scripting language to connect with its underlying C++, and add GDScript on top of Swift for people who want to use GDScript. I feel this will address the followings:

  1. To begin with, Swift is fully open source as well :)
  2. Swift has better type clarity, Optional support with null safeguard, and annotations built into the language, which addresses 1, 3 (https://github.com/godotengine/godot-proposals/issues/162) & 6 above.
  3. Not sure about IDE jumping to definitions functionality, but guess we could leverage some existing implementations to address 2.
  4. It is a relatively new and already mature language, which is in its early prime time and one of the best candidates to choose for today, and it avoids the need to re-invent the wheels for e.g. annotations. Additionally it will attract lots of iOS developers to Godot which I am pretty confident about. This addresses 4 & 7.
  5. Swift is a performant language, at least beating Java - addresses 5. Reference to https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/swift.html please note the binary tree part was not done right: https://forums.swift.org/t/swift-benchmarks/32113.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Here I want to make a simple code snippet comparison, taking the Player.gd example from Platformer 2D:

class_name Player
extends Actor

const FLOOR_DETECT_DISTANCE = 20.0

export(String) var action_suffix = ""  // This is already changed in Godot 4.0 to annotation

onready var platform_detector = $PlatformDetector

func _ready():
    var camera: Camera2D = $Camera
    if action_suffix == "_p1":
        camera.custom_viewport = $"../.."
    elif action_suffix == "_p2":
        var viewport: Viewport = $"../../../../ViewportContainer2/Viewport"
        viewport.world_2d = ($"../.." as Viewport).world_2d
        camera.custom_viewport = viewport

func _physics_process(_delta):
    var direction = get_direction()

    var is_jump_interrupted = Input.is_action_just_released("jump" + action_suffix) and _velocity.y < 0.0
    _velocity = calculate_move_velocity(_velocity, direction, speed, is_jump_interrupted)

    # When the character’s direction changes, we want to to scale the Sprite accordingly to flip it.
    # This will make Robi face left or right depending on the direction you move.
    if direction.x != 0:
        sprite.scale.x = 1 if direction.x > 0 else -1

    var is_shooting = false
    if Input.is_action_just_pressed("shoot" + action_suffix):
        is_shooting = gun.shoot(sprite.scale.x)

    var animation = get_new_animation(is_shooting)
    if animation != animation_player.current_animation and shoot_timer.is_stopped():
        if is_shooting:
            shoot_timer.start()
        animation_player.play(animation)

func get_direction():
    return Vector2(
        Input.get_action_strength("move_right" + action_suffix) - Input.get_action_strength("move_left" + action_suffix),
        -1.5 if is_on_floor() and Input.is_action_just_pressed("jump" + action_suffix) else 0
    )

func calculate_move_velocity(
        linear_velocity,
        direction,
        speed,
        is_jump_interrupted
    ):

    var velocity = linear_velocity

    if direction.y != 0.0:
        velocity.y = speed.y * direction.y
    if is_jump_interrupted:
        velocity.y *= 0.6
    return velocity

In Swift it would be more or less look like:

class Player: Actor {
    let floorDetectDistance = 20.0

    @export // or some other name like implemented in Godot 4.0's GDScript
    private var actionSuffix = ""

    lazy var platformDetector = PlatformDetector()

    // override makes it clear this is overriding a super function
    override func ready() {
        var camera: Camera2D = .init() // or just `var camera = Camera2D() since it's equally clear`
        if actionSuffix == "_p1" {
            camera.customViewport = SomeViewport()
        } else if actionSuffix == "_p2" {
            var viewport = Viewport()
            viewport.world2d = (SomeViewport as Viewport).world2d
            camera.customViewport = viewport
        }
    }

    override func physicsProcess(_ delta: Double) {
        var direction = getDirection()

        var isJumpInterrupted = Input.isActionJustReleased("jump" + actionSuffix) && velocity.y < 0.0
        velocity = calculateMoveVelocity(velocity, direction, speed, isJumpInterrupted)

        // When the character’s direction changes, we want to to scale the Sprite accordingly to flip it.
        // This will make Robi face left or right depending on the direction you move.
        if direction.x != 0 {
            sprite.scale.x = direction.x > 0 ? 1 : -1
        }

        var isShooting = false
        if Input.isActionJustPressed("shoot" + actionSuffix) {
            isShooting = gun.shoot(sprite.scale.x)
        }

        var animation = getNewAnimation(isShooting)
        if animation != animationPlayer.currentAnimation && shootTimer.isStopped() {
            if isShooting {
                shootTimer.start()
            }
            animationPlayer.play(animation)
        }
    }

    func getDirection() {
        return Vector2(
            Input.getActionStrength("move_right" + actionSuffix) - Input.getActionStrength("move_left" + actionSuffix),
            isOnFloor() && Input.isActionJustPressed("jump" + actionSuffix) ? -1.5 : 0
        )
    }

    // More clear param types, and can either require name on callsites or use _ to avoid named params
    func calculateMoveVelocity(_ linearVelocity: Vector2,
                               _ direction: Vector2,
                               _ speed: Vector2,
                               _ isJumpInterrupted: Bool)
    {
        var velocity = linearVelocity

        if direction.y != 0.0 {
            velocity.y = speed.y * direction.y
        }
        if isJumpInterrupted {
            velocity.y *= 0.6
        }
        return velocity
}

If this enhancement will not be used often, can it be worked around with a few lines of script?

Not applicable here.

Is there a reason why this should be core and not an add-on in the asset library?

This touches the engine core so I don't think it suits as an add-on.

Calinou commented 3 years ago

Adding support for more scripting languages can be done with a third-party GDNative binding, but this won't be accepted in core. We're happy with GDScript as it's precisely designed to fit Godot's needs, and would prefer not splitting the maintenance effort too much. See this section of What is GDNative?:

There are no plans to support additional languages with GDNative officially. That said, the community offers several bindings for other languages (see below).

For context, Godot already officially supports no less than four "scripting" options:


  1. Lack of types in function parameters, which makes code hard to read and follow.

Are you sure about this? GDScript supports type hints for variables (member and local), function parameters and return types.

  1. Numerous sources claim GDScript is very slow in larger calculations - e.g. this says it's 9x slower than Java, 100x slower than C# (reddit.com/r/godot/comments/aisrey/i_have_created_a_minimal_benchmark_for_java_and).

Typed instructions are being implemented in Godot 4.0 to improve GDScript performance. Also, most of the time, you'll be calling methods that are implemented in C++ anyway.

hyouuu commented 3 years ago

Thank you @Calinou for the explanation! I think it makes sense to add things on top of GDNative as you pointed out, and it's great to hear the performance boost will be very significant.

As for my misunderstanding about types in function parameters, I guess we could improve the example projects with best practices for the upcoming 4.0 so newcomers won't be misguided.

I'll close this one and watch the Kotlin implementation for now as it's very close to Swift in many aspects, and hopefully someone more experienced with bindings could use the information here to create a Swift binding in the near future :)

Calinou commented 3 years ago

As for my misunderstanding about types in function parameters, I guess we could improve the example projects with best practices for the upcoming 4.0 so newcomers won't be misguided.

We intentionally chose not to use type hints in demo projects and documentation to avoid confusing beginners. At the end of the day, type hinting in GDScript is optional and will remain so.

ShalokShalom commented 3 years ago

You can find a list of all the languages supported, including that one by the community, here: https://github.com/Vivraan/godot-lang-support/blob/master/README.md

Nim and Rust are two languages who are very similar to Swift, by being modern implementations who feature imperative and functional features with a strong type system.

Nim has actuallya pretty nifty documentation and amazing macro skills, plus you can compile it to C/C++ and ObjectiveC, which could help you to utilize it somewhere else.

https://pragmagic.github.io/godot-nim/v0.7.8/index.html

Much fun, what ever you choose 😚

hyouuu commented 3 years ago

Thanks for elaborating on the supported languages @ShalokShalom ! @Calinou I'm not sure if avoiding the type hints would help avoid confusing beginners. Was there a survey done on the subject? Maybe should have a discussion with other core members there? Just a suggestion from a newcomer's perspective :)

Calinou commented 3 years ago

I'm not sure if avoiding the type hints would help avoid confusing beginners. Was there a survey done on the subject? Maybe should have a discussion with other core members there? Just a suggestion from a newcomer's perspective :)

Not really, but this is the consensus we settled on with @NathanLovato who's currently reworking the documentation.

hyouuu commented 3 years ago

I see @NathanLovato is a teacher from his GitHub profile - probably worth checking with a few students about their feelings about type hints.

NathanLovato commented 3 years ago

There wasn't a consensus necessarily with or through me, but we just settled on dynamic types after multiple discussions and based on multiple persons' feedback. Godot's dev mostly works like that, we always make an effort to reach a good consensus and keep the project moving forward.

@hyouuu Here's what I can tell you after years teaching and working full-time around Godot.

I can confirm, as we teach exclusively with type hints in our courses at GDQuest, that it needs to be explained first, that I always need to do the pedagogy of why you'd want to use them, and the fact it's optional confuses beginners.

Also, people really like the "Python-like" syntax and the fact it looks and feels so simple. This includes professional developers. It's one of the aspects of GDScript that attracts so many users and that they love. I know that not only from many discussions but also market studies we've done. Even in my team, with professional developers, it's been hard to convince some teammates to use types over dynamic code.

Some more notes.

GDScript's been designed as a dynamic and duck-typed language for fast scripting. The type checker was added more recently and still has caveats until the GDScript "v2" in Godot 4.0. For many users, the fact it doesn't improve performance right now is a turn-off.

The Getting started series' in the official docs is getting a rewrite and with it, the intro explicitly tells the user that GDScript is gradually typed and has optional type hints. Hopefully, it'll help raise awareness about the feature with new users.

hyouuu commented 3 years ago

Thanks for the thorough explanation @NathanLovato - very interesting insights & contexts, and great to hear that type hints are encouraged!

ShalokShalom commented 3 years ago

I would add that it is a huge difference - for me at least - between type hints and type inference as I see this basically as two different forms of programming. Something in between dynamic typing and type annotations is programming with using types inherently and use only inference. GDScript can do that only for bindings, so its not really an option with it, but both Haskell and FSharp support that amongst the languages that are supported. Sorry for the slightly offtopic talk.

tayloraswift commented 3 years ago

Just an update for anyone finding this thread through Google: Swift now has Godot NativeScript support!

hyouuu commented 3 years ago

Great news and thanks for doing the work @kelvin13 !