diff --git a/.github/workflows/gh_pages.yml b/.github/workflows/gh_pages.yml new file mode 100644 index 00000000..7d54029f --- /dev/null +++ b/.github/workflows/gh_pages.yml @@ -0,0 +1,28 @@ +name: 📑 GitHub Pages +on: + push: + branches: + - "master" + - "3.4" + - "4.1" + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v3 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mkdocs-material + - run: mkdocs gh-deploy --force diff --git a/README.md b/README.md index 37cc0df6..c4d5f52d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JavaScript language binding for Godot game engine +# GodotJS - JavaScript language binding for Godot game engine This module implements JavaScript/TypeScript language support for the Godot game engine using [QuickJS](https://bellard.org/quickjs/) as the JavaScript engine. @@ -13,275 +13,32 @@ This module implements JavaScript/TypeScript language support for the Godot game - Full code completion support for all Godot APIs including signals and enumerations - Debug in Visual Studio Code with the [plugin](https://marketplace.visualstudio.com/items?itemName=geequlim.godot-javascript-debug) - currently not available for 4.x -## Installation +### Getting started -No installation or setup necessary. The binaries for download are the complete, usable Godot editor and engine with JavaScript/TypeScript language support. - -## Download - -You can try the pre-compiled binaries from the [release page](https://github.com/GodotExplorer/ECMAScript/releases) or get the binaries with the latest commits from the [GitHub build action result](https://github.com/GodotExplorer/ECMAScript/actions). - -## Compilation - -1. Clone the source code of [godot](https://github.com/godotengine/godot) -2. Clone this module and put it into `godot/modules/` and make sure the folder name of this module is `javascript` -3. [Recompile the godot engine](https://docs.godotengine.org/en/4.1/development/compiling/index.html) - Use ``scons`` with those additional options ``warnings=extra werror=yes module_text_server_fb_enabled=yes`` to show all potential errors - -![Build Godot with ECMAScript](https://github.com/GodotExplorer/ECMAScript/workflows/Build%20Godot%20with%20ECMAScript/badge.svg) - -## Usage - -### How to export script class to Godot - -1. Define your JavaScript class and inherit from a Godot class, then export it as the **default** entry: - -```js -// The default export entry is treated as an exported class to Godot -export default class MySprite extends godot.Sprite { - // this is _init() in GDScript - constructor() { - super(); - } - - _ready() {} - - _process(delta) {} -} -``` - -2. Save the script with extension `.mjs` -3. Attach the script file to the node or resource object like you do with GDScript - -### How to export signals - -```js -export default class MySprite extends godot.Sprite {} -// register game_over signal to MySprite class -godot.register_signal(MySprite, "game_over"); -``` - -### How to export properties - -```js -export default class MySprite extends godot.Sprite { - _process(delta) { - // Yes! We can use operators in JavaScript like GDScript - this.position += this.direction * delta; - } -} -// export 'direction' properties to MySprite Godot inspector -godot.register_property(MySprite, "direction", new godot.Vector2(1, 0)); -``` - -There are 2 ways of using the `godot.register_property`. The third parameter can either be a default value for the property you're trying to export or an object giving a more detailed description of how the editor should show it. - -```js -function register_property(target: GodotClass | godot.Object, name: string, value: any); -function register_property(target: GodotClass | godot.Object, name: string, info: PropertyInfo); -``` - -So calling the `register_property` like this: - -```js -godot.register_property(MyClass, "number_value", 3.14); -``` - -Is the simplified version of: - -```js -godot.register_property(MyClass, "number_value", { - type: godot.TYPE_REAL, - hint: godot.PropertyHint.PROPERTY_HINT_NONE, - hint_string: "", - default: 3.14, -}); -``` - -For more detail on how to use it, [click here](https://github.com/Geequlim/ECMAScript/issues/24#issuecomment-655584829). - -### About the API +Read the [getting-started](https://geequlim.github.io/ecmascript/getting-started). -All of Godot's APIs are defined within the `godot` namespace. +## Getting the engine -No API names have been renamed or changed, so you shouldn't need to change your habits. - -| GDScript | JavaScript | -| ---------------------- | ---------------------------- | -| null | null | -| int | number | -| float | number | -| String | string | -| Array | Array | -| Dictionary | Object | -| NodePath | string | -| Object | godot.Object | -| Resource | godot.Resource | -| Vector2 | godot.Vector2 | -| Color | godot.Color | -| sin(v) | godot.sin(v) | -| print(v) | godot.print(v) | -| PI | godot.PI | -| Color.black | godot.Color.black | -| Control.CursorShape | godot.Control.CursorShape | -| Label.Align.ALIGN_LEFT | godot.Label.Align.ALIGN_LEFT | - -#### API specification: - -- Keys of Dictionary are converted to String in JavaScript -- Signals are defined as constants to their classes - ``` - godot.Control.resized === 'resized' // true - ``` -- Additional functions - - `godot.register_signal(cls, signal_name)` to register signals - - `godot.register_property(cls, name, default_value)` to define and export properties - - `godot.register_class(cls, name)` to register named class manually - - `godot.set_script_tooled(cls, tooled)` to set `tooled` of the class - - `godot.set_script_icon(cls, path)` to set icon of the class - - `godot.get_type(val)` Returns the internal type of the given `Variant` object, using the `godot.TYPE_*` - - `godot.yield(target, signal)` Returns a Promise which will be resolved when the signal emitted - - `requestAnimationFrame(callback)` registers a callback function to be called every frame, returns a request ID. - - `cancelAnimationFrame(request_id)` to cancel a previously scheduled frame request - - `require(module_id)` to load a CommonJS module or load a resource file - - `$` is the alias of `Node.get_node` -- Using signals in the ECMAScript way - - Allow passing functions for `godot.Object.connect`, `godot.Object.disconnect`, and `godot.Object.is_connected` - ```js - this.panel.connect(godot.Control.resized, (size) => { - console.log("The size of the panel changed to:", size); - }); - ``` - - Using `await` to wait for signals - ```js - await godot.yield( - this.get_tree().create_timer(1), - godot.SceneTreeTimer.timeout - ); - console.log("After one second to show"); - ``` -- Preload resources with ECMAScript import statement - ```js - import ICON from "res://icon.png"; - ``` -- Multi-threading with minimal [Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Worker) (**This is an experimental feature**) - - - Start a new thread with Worker - ```js - const worker = new Worker("worker.js"); // Run worker.js in a new thread context - worker.postMessage({ type: "load_dlc", value: "dlc01.pck" }); - worker.onmessage = function (msg) { - console.log("[MainThread] received message from worker thread:", msg); - }; - ``` - - Transfer value in different thread context with `godot.abandon_value` and `godot.adopt_value` - - ```js - // In worker thread - let id = godot.abandon_value(object); - postMessage({ type: "return_value", id: id }); - - // In the host thread - worker.onmessage = function (msg) { - if (typeof msg === "object" && msg.type === "return_value") { - let value_from_worker = godot.adopt_value(msg.id); - } - }; - ``` - -### TypeScript support - -- Run the menu command `Project > Tools > JavaScript > Generate TypeScript Project` from the Godot editor to generate a TypeScript project -- Run `tsc -w -p .` under your project folder in the terminal to compile scripts - -#### Code completion - -- Code completion in TS will automatically work once the TypeScript project is generated by the above steps. -- Code completion in VSCode is achieved by the property `"types": "./godot.d.ts"` in the generated package.json file of the TypeScript project. The `godot.d.ts` file can be generated alone via the `Project > Tools > ECMAScript > Generate TypeScript Declaration File` editor menu option and added to a `package.json` file manually to achieve this without a full TypeScript project. - -#### Example TypeScript Usage - -Compile your `ts` script to a `.mjs` file then we can attach it to a node in godot editor. - -Most of the `register` functions are available as various decorators as seen below. - -```ts -import { signal, property, tool, onready, node } from "./decorators"; - -@tool // make the script runnable in godot editor -export default class InputLine extends godot.HBoxContainer { - // define a signal - @signal - static readonly OnTextChanged: string; - - // expose a node property - @node - icon: godot.Sprite; - - // register offset property with the godot inspector with default value of Vector2(0, 0) - @property({ default: godot.Vector2.ZERO }) - offset: godot.Vector2; - - // register properties for godot editor inspector - @property({ type: godot.VariantType.TYPE_STRING }) - get title() { - return this._title; - } - set title(v: string) { - this._title = v; - if (this._label) { - this._label.text = v; - } - } - private _title: string; - - @property({ default: "Input text here" }) - get hint() { - return this._hint; - } - set hint(v: string) { - this._hint = v; - if (this.edit) { - this.edit.hint_tooltip = v; - this.edit.placeholder_text = v; - } - } - private _hint: string; - - get label(): godot.Label { - return this._label; - } - protected _label: godot.Label; - - // call get_node('LineEdit') and assign the returned value to 'this.edit' automatically when the node is ready - @onready("LineEdit") - edit: godot.LineEdit; - - get text(): string { - return this.edit?.text; - } - - _ready() { - // get first child with the type of godot.Label - this._label = this.get_node(godot.Label); +No installation or setup necessary. The binaries for download are the complete, usable Godot editor and engine with JavaScript/TypeScript language support. - // Apply the inspector filled values with property setters - this.title = this.title; - this.hint = this.hint; +### Binary downloads +Download the binaries from the [release page](https://github.com/GodotExplorer/ECMAScript/releases). - this.edit.connect(godot.LineEdit.text_changed, (text: string) => { - this.emit_signal(InputLine.OnTextChanged, text); - }); - } -} -``` +### Compiling from source +- Clone the source code of [godot](https://github.com/godotengine/godot): + - ``git clone git@github.com:godotengine/godot.git`` or + - ``git clone https://github.com/godotengine/godot.git`` +- Clone this module and put it into `godot/modules/javascript`: + - ``git clone git@github.com:Geequlim/ECMAScript.git godot/modules/javascript`` or + - ``git clone https://github.com/Geequlim/ECMAScript.git godot/modules/javascript`` +- [Recompile the godot engine](https://docs.godotengine.org/en/4.1/development/compiling/index.html) + - Use ``scons`` with those additional options ``warnings=extra werror=yes module_text_server_fb_enabled=yes`` to show all potential errors -## Demo +## Documentation, Tutorials & Demos -You can try demos in the [ECMAScriptDemos](https://github.com/Geequlim/ECMAScriptDemos) +Read this [documentation](https://geequlim.github.io/ecmascript/getting-started) or look at the tutorials or demos: -## Developer notes -- This package is not compatible with MSVC, you will get build errors in quickjs. -- The script also build the `on_tag.yml` script which automates the github release publishing. - \*\* The `on_tag.yml` functionality tries to sleep until all jobs with the same `sha` are completed. It should be fine to run whenever, but depending on how github actions culls long running jobs you might want to make sure that all/(most of) the builds look good before tagging. -- They should definitely be fixed & enabled at some point, **so please submit a PR if you have any ideas of how to do that!** +- [ECMAScriptDemos](https://github.com/Geequlim/ECMAScriptDemos) - Demos +- [godot-ECMAScript-cookbook](https://github.com/why-try313/godot-ECMAScript-cookbook/wiki) - Tutorial +- [godot-typescript-starter](https://github.com/citizenll/godot-typescript-starter) - Template diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..8ed8da3e --- /dev/null +++ b/docs/api.md @@ -0,0 +1,100 @@ +All of Godot's APIs are defined within the `godot` namespace. + +No API names have been renamed or changed, so you shouldn't need to change your habits. + +| GDScript | JavaScript | +| ---------------------- | ---------------------------- | +| null | null | +| int | number | +| float | number | +| String | string | +| Array | Array | +| Dictionary | Object | +| NodePath | string | +| Object | godot.Object | +| Resource | godot.Resource | +| Vector2 | godot.Vector2 | +| Color | godot.Color | +| sin(v) | godot.sin(v) | +| print(v) | godot.print(v) | +| PI | godot.PI | +| Color.black | godot.Color.black | +| Control.CursorShape | godot.Control.CursorShape | +| Label.Align.ALIGN_LEFT | godot.Label.Align.ALIGN_LEFT | + +## API specification: + +- Keys of Dictionary are converted to String in JavaScript +- Signals are defined as constants to their classes + ``` + godot.Control.resized === 'resized' // true + ``` + +### Additional functions + +- `godot.register_signal(cls, signal_name)` to register signals +- `godot.register_property(cls, name, default_value)` to define and export properties +- `godot.register_class(cls, name)` to register named class manually +- `godot.set_script_tooled(cls, tooled)` to set `tooled` of the class +- `godot.set_script_icon(cls, path)` to set icon of the class +- `godot.get_type(val)` Returns the internal type of the given `Variant` object, using the `godot.TYPE_*` +- `godot.yield(target, signal)` Returns a Promise which will be resolved when the signal emitted +- `requestAnimationFrame(callback)` registers a callback function to be called every frame, returns a request ID. +- `cancelAnimationFrame(request_id)` to cancel a previously scheduled frame request +- `require(module_id)` to load a CommonJS module or load a resource file +- `$` is the alias of `Node.get_node` + +### Using signals + +Allow passing functions for `godot.Object.connect`, `godot.Object.disconnect`, and `godot.Object.is_connected`: + +```js +this.panel.connect(godot.Control.resized, (size) => { + console.log("The size of the panel changed to:", size); +}); +``` + +Using `await` to wait for signals + +```js +await godot.yield( + this.get_tree().create_timer(1), + godot.SceneTreeTimer.timeout +); +console.log("After one second to show"); +``` + +Preload resources with ECMAScript import statement + +```js +import ICON from "res://icon.png"; +``` + +### Multi-threading + +Multi-threading with minimal [Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Worker) (**This is an experimental feature**) + +Start a new thread with Worker: + +```js +const worker = new Worker("worker.js"); // Run worker.js in a new thread context +worker.postMessage({ type: "load_dlc", value: "dlc01.pck" }); +worker.onmessage = function (msg) { + console.log("[MainThread] received message from worker thread:", msg); +}; +``` + +Transfer value in different thread context with `godot.abandon_value` and `godot.adopt_value`: + +```js +// In worker thread +let id = godot.abandon_value(object); +postMessage({ type: "return_value", id: id }); + +// In the host thread +worker.onmessage = function (msg) { + if (typeof msg === "object" && msg.type === "return_value") { + let value_from_worker = godot.adopt_value(msg.id); + } +}; +``` diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 00000000..a7f7c579 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,67 @@ +### How to export script class to Godot + +1. Define your JavaScript class and inherit from a Godot class, then export it as the **default** entry: + +```js +// The default export entry is treated as an exported class to Godot +export default class MySprite extends godot.Sprite { + // this is _init() in GDScript + constructor() { + super(); + } + + _ready() {} + + _process(delta) {} +} +``` + +2. Save the script with extension `.mjs` +3. Attach the script file to the node or resource object like you do with GDScript + +### How to export signals + +```js +export default class MySprite extends godot.Sprite {} +// register game_over signal to MySprite class +godot.register_signal(MySprite, "game_over"); +``` + +### How to export properties + +```js +export default class MySprite extends godot.Sprite { + _process(delta) { + // Yes! We can use operators in JavaScript like GDScript + this.position += this.direction * delta; + } +} +// export 'direction' properties to MySprite Godot inspector +godot.register_property(MySprite, "direction", new godot.Vector2(1, 0)); +``` + +There are 2 ways of using the `godot.register_property`. The third parameter can either be a default value for the property you're trying to export or an object giving a more detailed description of how the editor should show it. + +```js +function register_property(target: GodotClass | godot.Object, name: string, value: any); +function register_property(target: GodotClass | godot.Object, name: string, info: PropertyInfo); +``` + +So calling the `register_property` like this: + +```js +godot.register_property(MyClass, "number_value", 3.14); +``` + +Is the simplified version of: + +```js +godot.register_property(MyClass, "number_value", { + type: godot.TYPE_REAL, + hint: godot.PropertyHint.PROPERTY_HINT_NONE, + hint_string: "", + default: 3.14, +}); +``` + +For more detail on how to use it, [click here](https://github.com/Geequlim/ECMAScript/issues/24#issuecomment-655584829). diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..da4d6aac --- /dev/null +++ b/docs/index.md @@ -0,0 +1,44 @@ +# GodotJS - JavaScript language binding for Godot game engine + +This module implements JavaScript/TypeScript language support for the Godot game engine using [QuickJS](https://bellard.org/quickjs/) as the JavaScript engine. + +## Features + +- Almost complete ES2020 support +- All Godot API available +- Operator overriding for built-in types (Vector3, Color, etc) +- TypeScript support +- [Using third-party libraries from npm](https://github.com/GodotExplorer/ECMAScriptDemos/tree/master/npm_module) +- Multi-thread support with Worker API +- Full code completion support for all Godot APIs including signals and enumerations +- Debug in Visual Studio Code with the [plugin](https://marketplace.visualstudio.com/items?itemName=geequlim.godot-javascript-debug) - currently not available for 4.x + +### Getting started + +Read the [getting-started](https://geequlim.github.io/ecmascript/getting-started). + +## Getting the engine + +No installation or setup necessary. The binaries for download are the complete, usable Godot editor and engine with JavaScript/TypeScript language support. + +### Binary downloads +Download the binaries from the [release page](https://github.com/GodotExplorer/ECMAScript/releases). + +### Compiling from source +- Clone the source code of [godot](https://github.com/godotengine/godot): + - ``git clone git@github.com:godotengine/godot.git`` or + - ``git clone https://github.com/godotengine/godot.git`` +- Clone this module and put it into `godot/modules/javascript`: + - ``git clone git@github.com:Geequlim/ECMAScript.git godot/modules/javascript`` or + - ``git clone https://github.com/Geequlim/ECMAScript.git godot/modules/javascript`` +- [Recompile the godot engine](https://docs.godotengine.org/en/4.1/development/compiling/index.html) + - Use ``scons`` with those additional options ``warnings=extra werror=yes module_text_server_fb_enabled=yes`` to show all potential errors + +## Documentation, Tutorials & Demos + +Read this [documentation](https://geequlim.github.io/ecmascript/) or look at the tutorials or demos: + + +- [ECMAScriptDemos](https://github.com/Geequlim/ECMAScriptDemos) +- [godot-ECMAScript-cookbook](https://github.com/why-try313/godot-ECMAScript-cookbook/wiki) +- [godot-typescript-starter](https://github.com/citizenll/godot-typescript-starter) diff --git a/docs/typescript.md b/docs/typescript.md new file mode 100644 index 00000000..96c7d359 --- /dev/null +++ b/docs/typescript.md @@ -0,0 +1,84 @@ +- Run the menu command `Project > Tools > JavaScript > Generate TypeScript Project` from the Godot editor to generate a TypeScript project +- Run `tsc -w -p .` under your project folder in the terminal to compile scripts + +## Code completion + +- Code completion in TS will automatically work once the TypeScript project is generated by the above steps. +- Code completion in VSCode is achieved by the property `"types": "./godot.d.ts"` in the generated package.json file of the TypeScript project. The `godot.d.ts` file can be generated alone via the `Project > Tools > ECMAScript > Generate TypeScript Declaration File` editor menu option and added to a `package.json` file manually to achieve this without a full TypeScript project. + +## Example + +Compile your `ts` script to a `.mjs` file then we can attach it to a node in godot editor. + +Most of the `register` functions are available as various decorators as seen below. + +```ts +import { signal, property, tool, onready, node } from "./decorators"; + +@tool // make the script runnable in godot editor +export default class InputLine extends godot.HBoxContainer { + // define a signal + @signal + static readonly OnTextChanged: string; + + // expose a node property + @node + icon: godot.Sprite; + + // register offset property with the godot inspector with default value of Vector2(0, 0) + @property({ default: godot.Vector2.ZERO }) + offset: godot.Vector2; + + // register properties for godot editor inspector + @property({ type: godot.VariantType.TYPE_STRING }) + get title() { + return this._title; + } + set title(v: string) { + this._title = v; + if (this._label) { + this._label.text = v; + } + } + private _title: string; + + @property({ default: "Input text here" }) + get hint() { + return this._hint; + } + set hint(v: string) { + this._hint = v; + if (this.edit) { + this.edit.hint_tooltip = v; + this.edit.placeholder_text = v; + } + } + private _hint: string; + + get label(): godot.Label { + return this._label; + } + protected _label: godot.Label; + + // call get_node('LineEdit') and assign the returned value to 'this.edit' automatically when the node is ready + @onready("LineEdit") + edit: godot.LineEdit; + + get text(): string { + return this.edit?.text; + } + + _ready() { + // get first child with the type of godot.Label + this._label = this.get_node(godot.Label); + + // Apply the inspector filled values with property setters + this.title = this.title; + this.hint = this.hint; + + this.edit.connect(godot.LineEdit.text_changed, (text: string) => { + this.emit_signal(InputLine.OnTextChanged, text); + }); + } +} +``` diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..7d37e3d3 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,20 @@ +site_name: GodotJS +site_url: https://geequlim.github.io/ecmascript/ +repo_url: https://github.com/Geequlim/ECMAScript +site_description: JavaScript language binding for Godot game engine +nav: + - Home: index.md + - Getting Started: getting-started.md + - API: api.md + - TypeScript: typescript.md +theme: + name: material + icon: + repo: fontawesome/brands/github + features: + - navigation.instant + - navigation.instant.prefetch + - navigation.path + - navigation.top + - search.suggest + - search.highlight