Git Product home page Git Product logo

gdscript2all's Introduction

GdScript2All

A tool for converting Godot's GdScript to other languages (currently C# and c++) with features like type inference, written in Python. It should be fairly easy to add new languages (see here).

Editor addon (windows only)

Download as a zip (not yet available from the Godot asset library), extract into your project and enable the plugin in your project settings.

From the command line

with python 3.9+ installed, call the main script using your favorite command line utility (add -t Cpp for c++) :

py main.py <file_or_folder_path> -o <output_file_or_folder_path>

Example

script input :

@tool
extends Node

# line comment

""" multiline
   comment
"""

class Nested1 extends test: pass

enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
enum NamedEnum {THING_1, THING_2, ANOTHER_THING = -1}

@export
var export

@export_group('group')

@export_flags("Self:4", "Allies:8", "Foes:16")
var export_flags : int

# basic property definitions / expressions
static var i = 0
const str = 'the fox said "get off my lawn"'
var big_str : string = """
    this is a multiline string """
var array = [0,1,2]
var dict := {0:1, 1:2, 2:3}
var string_array : Array[string] = ['0','1']

func method(param = 5.):
    for k in string_array:
        print(k)
    return val * param

# type inference on members
var j = i
var k = string_array[0]

# determine type based on godot doc
var x = self.get_parent()
var aClass = ProjectSettings.get_global_class_list()[10]
const enum = RenderingServer.SHADER_SPATIAL
var function = angle_difference(.1,.2)

# Gdscript special syntax
var get_node = $node
var get_node2 = $"../node"
var get_unique_node = %unique_node
var preload_resource = preload("res://path")
var load_resource = load("res://path")

var sprite : Sprite2D :
    set (value):
        sprite = value
        sprite.position = Vector2(1,2)
        sprite.position += Vector2(1,2) # cpp will need help here
    get:
        return sprite

func enum_return(): return THING_2

# signals
signal jump
signal movement(dir:Vector3, speed:float)

func async_function():
    await jump
    await get_tree().process_frame
    
    get_tree().process_frame.emit(.7)
    
    var myLambda = func(): print("look ma i'm jumping")
    
    # lambdas are not perfectly translated
    jump.connect( myLambda )
    
    movement.emit(Vector3.UP, .1)

# _ready generation when @onready is used
@onready var k = 42

C# output :

using System;
using Godot;
using Godot.Collections;



// line comment

/* multiline
   comment
*/

[Tool]
[GlobalClass]
public partial class test : Godot.Node
{
    [Tool]
    public partial class Nested1 : Godot.test
    {

    }

    public enum Enum0 {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
    public enum NamedEnum {THING_1, THING_2, ANOTHER_THING =  - 1}

    [Export]
    public Godot.Variant Export;

    [ExportGroup("group")]

    [Export(PropertyHint.Flags, "Self:4,Allies:8,Foes:16")]
    public int ExportFlags;

    // basic property definitions / expressions
    public static int I = 0;
    public const string str = "the fox said \"get off my lawn\"";
    public string BigStr = @"
    this is a multiline string ";
    public Array Array = new Array{0, 1, 2, };
    public Dictionary Dict = new Dictionary{{0, 1},{1, 2},{2, 3},};
    public Array<string> StringArray = new Array{"0", "1", };

    public double Method(double param = 5.0)
    {
        foreach(string k in StringArray)
        {
            GD.Print(K);
        }
        return val * param;
    }

    // type inference on members
    public int J = I;
    public string K = StringArray[0];

    // determine type based on godot doc
    public Godot.Node X = this.GetParent();
    public Dictionary AClass = Godot.ProjectSettings.GetGlobalClassList()[10];
    public const RenderingServer.ShaderMode enum = Godot.RenderingServer.ShaderMode.ShaderSpatial;
    public double Function = Mathf.AngleDifference(0.1, 0.2);

    // Gdscript special syntax
    public Godot.Node GetNode = GetNode("node");
    public Godot.Node GetNode2 = GetNode("../node");
    public Godot.Node GetUniqueNode = GetNode("%unique_node");
    public Godot.Resource PreloadResource = /* preload has no equivalent, add a 'ResourcePreloader' Node in your scene */("res://path");
    public Godot.Resource LoadResource = Load("res://path");

    public Godot.Sprite2D Sprite
    {
        set
        {
            _Sprite = value;
            _Sprite.Position = new Vector2(1, 2);
            _Sprite.Position += new Vector2(1, 2);// cpp will need help here
        }
        get
        {
            return _Sprite;
        }
    }
    private Godot.Sprite2D _Sprite;


    public NamedEnum EnumReturn()
    {return THING_2;
    }

    // signals
    [Signal]
    public delegate void JumpEventHandler();
    [Signal]
    public delegate void MovementEventHandler(Vector3 dir, double speed);

    public void AsyncFunction()
    {
        await ToSignal(this, "Jump");
        await ToSignal(GetTree(), "ProcessFrame");

        GetTree().EmitSignal("ProcessFrame", 0.7);

        var myLambda = () =>
        {    GD.Print("look ma i'm jumping");
        };

        // lambdas are not perfectly translated
        Jump += myLambda;

        EmitSignal("Movement", Vector3.Up, 0.1);
    }

    // _ready generation when @onready is used
    public int K;


    protected override void _Ready()
    {
        K = 42;
    }
}

c++ output (header) :

#ifndef TEST_H
#define TEST_H

#include <godot_cpp/godot.hpp>
#include <godot_cpp/variant/array.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/classes/sprite2d.hpp>
#include <godot_cpp/classes/test.hpp>

using namespace godot;

// line comment

/* multiline
   comment
*/

class Nested1 : public test {
    GDCLASS(Nested1, test);
public:

};

class test : public Node {
    GDCLASS(test, Node);
public:
    enum  {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY};
    enum NamedEnum {THING_1, THING_2, ANOTHER_THING =  - 1};

protected:
    Variant export;

    int export_flags;

// basic property definitions / expressions
    static int i;
    const String str = "the fox said \"get off my lawn\"";
    String big_str = "\
    this is a multiline string ";
    Array array =  /* no array initializer in c++ ! */ {0, 1, 2, };
    Dictionary dict =  /* no dictionary initializer in c++ ! */ {{0, 1},{1, 2},{2, 3},};
    Array string_array =  /* no array initializer in c++ ! */ {"0", "1", };

// type inference on members

public:
    double method(double param = 5.0);

protected:
    int j = i;
    String k = string_array[0];

// determine type based on godot doc
    Ref<Node> x = this->get_parent();
    Dictionary aClass = ProjectSettings::get_singleton()->get_global_class_list()[10];
    const RenderingServer::ShaderMode enum = RenderingServer::ShaderMode::SHADER_SPATIAL;
    double function = Math::angle_difference(0.1, 0.2);

// Gdscript special syntax
    Ref<Node> get_node = get_node("node");
    Ref<Node> get_node2 = get_node("../node");
    Ref<Node> get_unique_node = get_node("%unique_node");
    Ref<Resource> preload_resource = /* preload has no equivalent, add a 'ResourcePreloader' Node in your scene */("res://path");
    Ref<Resource> load_resource = load("res://path");

    Ref<Sprite2D> sprite;

public:
    void set_sprite(Ref<Sprite2D> value);

    Ref<Sprite2D> get_sprite();

// signals
    NamedEnum enum_return();
    /* signal jump() */
    /* signal movement(Vector3 dir, double speed) */

// _ready generation when @onready is used
    void async_function();

protected:
    int k;

public:
    void _ready() override;
    void set_export(Variant value);
    Variant get_export();
    void set_export_flags(int value);
    int get_export_flags();

    static void _bind_methods();
};

VARIANT_ENUM_CAST(test::NamedEnum)

#endif // TEST_H

c++ output (implementation) :

#include "test.hpp"

#include <godot_cpp/core/object.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

double test::method(double param)
{
    for(String k : string_array)
    {
        UtilityFunctions::print(k);
    }
    return val * param;
}

void test::set_sprite(Ref<Sprite2D> value)
{
    sprite = value;
    sprite->set_position(Vector2(1, 2));
    sprite->set_position( /* get_position() */ + Vector2(1, 2));// cpp will need help here
}

Ref<Sprite2D> test::get_sprite()
{
    return sprite;
}

NamedEnum test::enum_return()
{return THING_2;
}

void test::async_function()
{
    /* await this->jump; */ // no equivalent to await in c++ !
    /* await this->get_tree()->process_frame; */ // no equivalent to await in c++ !

    get_tree()->emit_signal("process_frame", 0.7);

    Callable myLambda = []() 
    {    UtilityFunctions::print("look ma i'm jumping");
    };

    // lambdas are not perfectly translated
    connect("jump", myLambda);

    emit_signal("movement", Vector3::UP, 0.1);
}

void test::_ready()
{
    k = 42;
}

void test::set_export(Variant value) {
    export = value;
}

Variant test::get_export() {
    return export;
}

void test::set_export_flags(int value) {
    export_flags = value;
}

int test::get_export_flags() {
    return export_flags;
}

void test::_bind_methods() {
    ClassDB::bind_method(D_METHOD("method", "param"), &test::method);
    ClassDB::bind_method(D_METHOD("enum_return"), &test::enum_return);
    ClassDB::bind_method(D_METHOD("async_function"), &test::async_function);
    ClassDB::bind_method(D_METHOD("set_sprite", "value"), &test::set_sprite);
    ClassDB::bind_method(D_METHOD("get_sprite"), &test::get_sprite);
    ClassDB::bind_method(D_METHOD("set_export", "value"), &test::set_export);
    ClassDB::bind_method(D_METHOD("get_export"), &test::get_export);
    ClassDB::bind_method(D_METHOD("set_export_flags", "value"), &test::set_export_flags);
    ClassDB::bind_method(D_METHOD("get_export_flags"), &test::get_export_flags);
    ClassDB::bind_integer_constant(get_class_static(), _gde_constant_get_enum_name(UNIT_NEUTRAL, "UNIT_NEUTRAL"), "UNIT_NEUTRAL", UNIT_NEUTRAL);
    ClassDB::bind_integer_constant(get_class_static(), _gde_constant_get_enum_name(UNIT_ENEMY, "UNIT_ENEMY"), "UNIT_ENEMY", UNIT_ENEMY);
    ClassDB::bind_integer_constant(get_class_static(), _gde_constant_get_enum_name(UNIT_ALLY, "UNIT_ALLY"), "UNIT_ALLY", UNIT_ALLY);
    ClassDB::bind_integer_constant(get_class_static(), _gde_constant_get_enum_name(THING_1, "THING_1"), "THING_1", THING_1);
    ClassDB::bind_integer_constant(get_class_static(), _gde_constant_get_enum_name(THING_2, "THING_2"), "THING_2", THING_2);
    ClassDB::bind_integer_constant(get_class_static(), _gde_constant_get_enum_name(ANOTHER_THING, "ANOTHER_THING"), "ANOTHER_THING", ANOTHER_THING);
    ClassDB::add_property(get_class_static(), PropertyInfo(Variant::OBJECT, "export"), "set_export", "get_export");
    ClassDB::add_property_group(get_class_static(), "group","");
    ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "export_flags", PROPERTY_HINT_FLAGS, "Self:4,Allies:8,Foes:16"), "set_export_flags", "get_export_flags");
    ClassDB::add_signal(get_class_static(), MethodInfo("jump"));
    ClassDB::add_signal(get_class_static(), MethodInfo("movement", PropertyInfo(Variant::VECTOR3, "dir"), PropertyInfo(Variant::FLOAT, "speed")));
}

Adding new languages

If you want to transpile to an unsupported language, rename a copy of the C# transpiler backend, modify it as needed, then to use it you just have to pass its name with the -t flag (example below with c++ transpiler):

py main.py -t Cpp <file_or_folder_path>

Limitations

  • generated code might need corrections ! (indentation might need a second pass too)
  • this tool parses and emits code ; if it encounters something unexpected it will drop the current line and try to resume at the next (panic mode)
  • generated C++ does a best guess on what should be a pointer/reference
  • in c++ accessing/modifying parent class properties does not use getters/setters (this is a conscious choice)
  • read TODO.md for current/missing features
  • pattern matching ex:
match [34, 6]:
  [0, var y]:
     print(y)
  [var x, 6] when x > 10 :
     print(x)

will probably not be supported (too complicated to generate an equivalent)

Updating the API definition

  • clone the offical godot repo
  • copy it's doc/classes folder and paste it into our classData folder
  • install untangle (xml parsing library) if you don't have it (pip install untangle)
  • run py src/godot_types.py to generate the pickle class db
  • profit.

Explaining the GPL-3.0 license

The code this tool generates from your GDScipt is yours. However, any improvment made to this tool's source has to be contributed back. I think that's fair.

Buy Me A Coffee

gdscript2all's People

Contributors

lcbx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

gdscript2all's Issues

Array typedefs panic on nested types

Arrays parameterized by nested types fail to parse:

Expected Behavior

Class with a nested type:

class_name Foo

enum Bar = {FOO, BAR, BAZ}

Caller

var foobar: Array[Foo.Bar]

Example cs output:

public Array<Foo.Bar> Foobar;

Current Behavior

public Array<Foo> Foobar;
//PANIC! <. Bar ]> unexpected at Token(type='.', value='.', lineno=1, index=21, end=22)

Is test cause panics

change the test.gd "method" function in line 32 to below:

func method(param = 5.):
	for k in string_array:
		if not k is String:
			pass
	return val * param

get panics:

consumed: method
consumed: param
consumed: 5.
UpScope 0
emit: {
emit:
consumed: k
consumed: string_array
emit: string_array
emit: for(String k : string_array)
UpScope 1
emit: {
emit:
consumed: not
consumed: k
emit: if(
emit: !
emit: k
emit: )
UpScope 2
emit: {
consumed: is
emit: is
emit: ;
consumed: String
emit: String
emit: ;
emit: ;
emit: ;
emit: ;
emit: void test::method(double param)
consumed: :
emit:
emit: //PANIC! <:> unexpected at Token(type=':', value=':', lineno=34, index=640, end=641)
PANIC! <:> unexpected at Token(type=':', value=':', lineno=34, index=640, end=641)
DownScope 3
emit: }
DownScope 2
emit: }
emit:
consumed: return
consumed: val
consumed: *
consumed: param
DownScope 1
emit: }
emit:
consumed: type inference on members
emit:
PANIC! <return val * param> unexpected at Token(type='TEXT', value='return', lineno=36, index=651, end=657)

Custom types are not parsed consistently

Types added by the user are sometimes output with a Godot. prefix, despite not being strictly defined in that namespace.

Expected Behavior

Custom Class

class_name Foo

Caller

var foobars: Array[Foo]
var foobar: Foo = Foo.new()

func fooBarBaz(foos: Array[Foo], foo: Foo) -> Foo:
	for bar in foos:
		# do something
	return foo

Example cs output:

	public Array<Foo> Foobars;
	public Foo Foobar = Foo.New();

	public Foo FooBarBaz(Array<Foo> foos, Foo foo)
	{
		foreach(Foo bar in foos)
		{
			// do something
		}
		return foo;
	}

Current Behavior

	public Array<Foo> Foobars;
	public Godot.Foo Foobar = Foo.New();

	public Godot.Foo FooBarBaz(Array<Foo> foos, Godot.Foo foo)
	{
		foreach(Foo bar in foos)
		{
			// do something
		}
		return foo;
	}

Specify a type for the loop variable in a for loop cause error

for k : String in string_array:
	print(k)

get error:

AttributeError 'NoneType' object has no attribute 'replace'
at main.py:107 main
at Parser.py:95 transpile
at Parser.py:117 class_body
at Parser.py:208 method
at Parser.py:226 Block
at Parser.py:249 statement
at Parser.py:296 forStmt

Godot support specify a type for the loop variable since 4.2 link
image

Class definitions will not parse correctly when classname appears before extends

Class definitions will not parse correctly when classname appears before extends. The majority of the gdscript examples in Godot's documentation define these attributes in this order, though the grammar supports top level declarations in any order.

Expected Behavior

Source script file:

class_name Foo
extends Bar

Example cs output:

public partial class Foo: Bar

Current Behavior

public partial class Foo : Godot.Object

//PANIC! <extends Bar> unexpected at Token(type='TEXT', value='extends', lineno=2, index=21, end=28)

Culprit lines in parser.py:

# script start specific statements
		self.is_tool = self.expect('@', 'tool'); self.endline()
		base_class = self.consume() if self.expect('extends') else 'Object'; self.endline()
		class_name = self.consume() if self.expect('class_name') else self.script_name

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.