The Glodot is real!

"Can we get much higher?"

What?

Glodot == Godot + Gleam. Nó là cách để chạy code Gleam trong Godot.

Who?

"Ai nghĩ ra cách này?" - Mình nghĩ ra cách này!

"Glodot phù hợp cho ai?" - Cho nhưng người thích FP (Functional programming) và muốn dùng nó cho gamedev.

When/where?

Việc sử dụng Glodot trở nên rất lý tưởng khi ta cần viết thuật toán và xử lý các thứ bên trong Domain logic, sử dụng Gleam kể cả khi viết code đơn giản như xứ lí hành động vuốt cho game 2048 cũng trở nên dễ thở hơn chán vạn so với GDscript.

Tuy nhiên, chứ nó khá là khó để tương tác trực tiếp với node tree bên ngoài domain logic, như xử lý vật lý chẳng hạn...

Why?

Godot là một game engine xịn nhưng GDscript thì không hẳn, nó không tốt bên trong domain logic. Và C# có tốt hơn nhưng không đủ tốt như một ngôn ngữ FP hẳn hoi.

Gleam là một sự cứu thế, là mảnh ghép hoàn hảo cho sự thiếu hụt của Godot. Syntax của nó đẹp, hiện đại mà lại quen thuộc, nó dễ đọc, dễ quản lý và dễ học (có thể học chỉ trong một ngày), không chỉ thế tooling của nó còn rất tiện, tiện hơn của Lua, Python, C#...

Vì thế nên đây là tổ hợp hoàn hảo:

  • GDscript để tiện cho việc xử lý tương tác và vật lý với "thế giới thực".
  • Gleam để viết cho domain logic functions với input/output đã định sẵn, cho các thuật toán hoặc tính toán phức tạp.

How?

Con đường kết nối Godot với Gleam đi như sau:

Dự án Glodot sẽ có cấu trúc files như sau:

.  (Root của Godot project)
├── core  (Root của Gleam package)
│   ├── gleam.toml
│   ├── manifest.toml
│   ├── package.json
│   ├── src
│   │   └── core
│   │       ├── core.gleam  (File chứa tất cả các function cần truy cập từ ngoài Gleam)
│   │       └── ...
├── project.godot
├── project.csproj
├── core.txt  (Code Gleam-ra-JS đã được bundle)
├── core.gd  (Util để chạy code đã bundle trong Godot)
└── V8.cs  (Util để chạy JS trong Godot)

Phía Gleam

Đầu tiên, tạo một package Gleam bên trong root của dự án Godot:

gleam new core # Không nhất thiết phải tên là `core`, để tên là gì cũng được.

Rồi viết các functions domain logic. Khi xong, tạo một file duy nhất chứa tất cả các hàm để có thể truy cập từ bên ngoài Gleam (đặt tên là core.gleam):

import core/a_stuffs
import core/b_stuffs

pub fn fn_a(input: String) -> String {
  a_stuffs.fn_a(input)
}

pub fn fn_b(input: String) -> String {
  b_stuffs.fn_b(input)
}

// ...

⚠️ CẢNH BÁO! CẢNH BÁO LỚN! custom Type bên trong Gleam không thể được truy cập từ bên ngoài, ít ra thì nó không hành xử như ta mong muốn đâu. Chỉ có Int, FloatString là có thể dùng được thôi. Thậm chí ListDict cũng không phải là Array và Object của JS. Tốt hơn hết là nên bọc input và output thành string JSON để có thể xử lý.

Sau khi viết Gleam code để có thể chạy từ Godot xong, hãy đảm bảo là mình đặt target của package là JS trong config gleam.toml:

  name = "core"
  version = "1.0.0"
+ target = "javascript"

...

Rồi bundler code JS sau mỗi lần compiler:

cd ./core
bun build ./build/dev/javascript/core/core/core.mjs --outfile ../core.txt

Đúng vấy, để đuôi của file nó là .txt chứ không phải .js để Godot có thể nhận dạng là file chữ và đọc nội dung bên trong đấy.

Và hãy nhớ bỏ phần export bên trong file đã bundle để V8 không báo lỗi.

Phía Godot

Đầu tiên cài ClearScript trong máy:

nuget install Microsoft.ClearScript.Complete

Sau đấy thêm nó vào project.csproj:

  <Project Sdk="Godot.NET.Sdk/4.4.1">
    <PropertyGroup>
      <TargetFramework>net8.0</TargetFramework>
      <EnableDynamicLoading>true</EnableDynamicLoading>
      <RootNamespace>project</RootNamespace>
    </PropertyGroup>
+   <ItemGroup>
+     <PackageReference Include="Microsoft.ClearScript.Complete" Version="7.5.0" />
+   </ItemGroup>
  </Project>

Viết một util tên là V8.cs để chạy code JS:

using Godot;
using System;
using Microsoft.ClearScript.V8;

public partial class V8 : Node
{
    private static V8ScriptEngine _engine = new V8ScriptEngine();

    public static string eval(string code)
    {
        return Convert.ToString(_engine.Evaluate(code));
    }
}

Cuối cùng, tạo một util để chạy code Gleam đã xuất và bundle (đặt tên file là core.gd đi cho đồng bộ):

extends Node

const _v8 := preload("res://src/V8.cs")


func _ready() -> void:
    var core_file := FileAccess.open(&"res://core.txt", FileAccess.READ)
    var core_code := core_file.get_as_text()
    _v8.eval(core_code)


func fn_a(arg: Variant) -> Variant:
	return _invoke(&"fn_a", arg)


func fn_b(arg: Variant) -> Variant:
	return _invoke(&"fn_b", arg)


func _invoke(funcName: String, argument: Variant) -> Variant:
    var code: Variant = &"{funcName}(\"{argument}\")".format({
        &"funcName": funcName,
        &"argument": JSON.stringify(argument).json_escape(),
    })

    var result: Variant = _v8.eval(code)

    var json := JSON.new()
    var error := json.parse(result)

    if error:
        if result != &"":
            printerr(result)

        return null

    return json.data

Xuất ra web

Như đã đề cập trong doc của Godot:

Dự án viết bằng C# với Godot 4 hiện tại không thể xuất ra web. Đọc blog này để biết thêm chi tiết.

Tuy nhiên bạn có thể fallback về dùng JavaScriptBridge để chạy code JS thay cho C# + ClearScript khi xuất lên web.

Một số bổ sung

Dưới đây là một số thư viện Gleam hưu dụng cho việc làm game:

  • gleam_community_maths: Thư viện toán cơ bản chứa các functions and utilities toán học cơ bản.
  • prng: Thư viện xử lý số và các loại thứ ngẫu nhiên PRNG.
  • vec: Thư viện xử lý vectors, có hầu hết các thứ có thể làm được như trong Godot.
  • vec_dict: Thư viện này dùng để thể hiện và xử lý grid 2D, 3D và cả 4D.

Một Glodot khác?

Phương pháp Godot Mono - ClearScript được đề cập ở trên có vẻ khá vòng vèo nhưng đấy hiện tại là cách "chính thức nhất". Godot Mono là một phiên bản chính thức của Godot và ClearScript là thư viện của Microsoft (nhà sáng lập ra C#) chính vì thế nên nó khá là ổn định.

Glodot có thể được thực thi được bằng cách Dùng GodotJS để thay thế cho cách trên, Nối từ Godot đến Gleam code đã được complie chỉ qua JS/TS.

Tuy nhiên ở thời điểm đã viết bài viết này GodotJS khá là buggy/janky, mình mong là nó có thể trở nên tốt hơn vì nếu GodotJS trở nên ổn định và cài đặt tiện được như Godot Mono thì đấy sẽ là một cách rất lý tưởng.

Mà được voi dòi tiên, mình ước là có một cái Godot plugin mà có thể tự complie package Gleam được chỉ định rồi cho một cái API để dễ dàng chạy nó.

Well that's all fork, stay gleamy~!