Lingara Lingara Tài liệu Cẩm nang API Thư viện Ứng dụng Tạo Nhúng Ứng dụng web
Ngôn ngữ: Tiếng Việt

Godot

Trang này được dịch từ tiếng Anh. Nếu hai bản khác nhau, trang tiếng Anh là bản đúng. Đọc trang tiếng Anh

Godot cung cấp hai ngôn ngữ, và Lingara có một bộ công cụ cho mỗi ngôn ngữ. Trò chơi GDScript dùng addon Lingara. Trò chơi Godot viết bằng C# dùng Lingara.Embed, cùng gói mà trò chơi Unity sử dụng. Hãy dùng một trong hai trong một dự án, không bao giờ dùng cả hai. Các thẻ bên dưới trình bày từng cách.

Cài đặt

GDScript. Cài addon Lingara từ Godot Asset Library, rồi bật nó trong Project Settings, mục Plugins. Việc bật addon sẽ thêm một autoload tên là Lingara, mà mọi cảnh truy cập được tại /root/Lingara.

C#. Thêm Lingara.Embed vào dự án của bạn dưới dạng một tham chiếu gói. Nút mẫu bên dưới, LingaraEmbedNode, không thuộc gói: hãy thêm mã nguồn của nó vào dự án và đặt nó vào một cảnh.

Lấy mã từ máy chủ của bạn

Không bộ công cụ nào giữ khóa bí mật. Khi lõi cần một mã, nó hỏi máy chủ của chính bạn; máy chủ cấp một mã bằng bộ công cụ máy chủ và trả lời bằng mã cùng thời điểm hết hạn của nó.

Với addon, hãy đặt tuyến cấp mã của máy chủ trong Project Settings và Lingara sẽ gọi tuyến đó trên luồng riêng của nó. Nếu tuyến cần phiên của người chơi, hãy đặt nó lúc chạy, vì cài đặt dự án được phát hành bên trong bản xuất của bạn. Hoặc kết nối token_needed và tự trả lời từng yêu cầu. Với C#, nút mẫu gửi POST tới tuyến cấp mã của bạn mỗi khi máy khách yêu cầu một mã. Dù theo cách nào, hãy báo lỗi bằng token_unavailable hoặc TokenUnavailable, để các lệnh gọi đang chờ thất bại ngay lập tức.

Xử lý sự kiện mỗi khung hình

Lõi chạy I/O mạng trên luồng riêng của nó. Autoload của addon rút hàng đợi một lần mỗi khung hình và phát một tín hiệu cho mỗi sự kiện, nên bạn chỉ cần kết nối những tín hiệu trò chơi của bạn cần và không bao giờ phải tự rút gì cả. Nút C# rút trong _process, tối đa 256 sự kiện mỗi khung hình, và phát lại từng sự kiện dưới dạng một tín hiệu Godot.

Cũng chính các tín hiệu đó mang các câu trả lời NPC được truyền về (xem hội thoại NPC) và các cập nhật luyện tập (xem kết quả luyện tập).

<PackageReference Include="Lingara.Embed" Version="0.1.0-alpha.6" />
Ngôn ngữ của mã mẫu

Godot C#

// Your own server mints the token: never put a client secret in a game. The
// continuation may run off the main thread, which the client allows.
private async Task FetchToken()
{
    try
    {
        using var response = await Http.PostAsync(TokenUrl, new StringContent("{}"));
        response.EnsureSuccessStatusCode();
        using var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
        Client?.ProvideToken(body.RootElement.GetProperty("token").GetString(), body.RootElement.GetProperty("expires_at").GetString());
    }
    catch (Exception e) when (e is HttpRequestException || e is JsonException || e is KeyNotFoundException)
    {
        Client?.TokenUnavailable(e.Message); // fail the waiting calls now
    }
}

GDScript

# The common case: set Project Settings > lingara/token_url to your server's
# token route, which answers exactly {"token", "expires_at"}. Lingara POSTs to
# it on its own thread whenever it needs a token. If the route needs the
# player's session, set it at run time instead (never in ProjectSettings,
# which ships inside the export):
func use_my_session(session_cookie: String) -> void:
	lingara.set_token_endpoint("https://game.example.com/lingara-token", {"cookie": session_cookie})


# Or answer each token_needed yourself, with whatever login your game has.
func _on_token_needed(_request: int, _reason: String) -> void:
	var http := HTTPRequest.new()
	add_child(http)
	http.request("https://game.example.com/lingara-token", [], HTTPClient.METHOD_POST, "{}")
	var reply: Array = await http.request_completed
	http.queue_free()
	var body: Variant = JSON.parse_string((reply[3] as PackedByteArray).get_string_from_utf8())
	if reply[1] == 200 and body is Dictionary:
		lingara.provide_token(body["token"], body["expires_at"])
	else:
		lingara.token_unavailable("the token route answered %d" % reply[1])
Ngôn ngữ của mã mẫu

Godot C#

public override void _Process(double delta)
{
    if (Client == null) return;
    events.Clear();
    Client.Drain(events); // at most 256 a frame; the rest wait for the next
    foreach (var e in events)
    {
        switch (e)
        {
            case EmbedEvent.TokenNeeded _: _ = FetchToken(); break;
            case EmbedEvent.NpcTurn { Update: NpcTurnUpdate.Fragment f } t:
                EmitSignal(SignalName.NpcTurnFragment, t.Dialogue.Value, t.Request.Value, f.Text); break;
            case EmbedEvent.NpcTurn { Update: NpcTurnUpdate.Finished f } t:
                EmitSignal(SignalName.NpcTurnFinished, t.Dialogue.Value, t.Request.Value, f.Text); break;
            case EmbedEvent.Practice p: EmitSignal(SignalName.PracticeChanged, p.Session.Value, p.Snapshot.Phase.ToString()); break;
            case EmbedEvent.Failed f: EmitSignal(SignalName.Failed, f.Error.KindName, f.Error.Message, f.Error.Request?.Value ?? 0); break;
            case EmbedEvent.Log log: GD.Print($"[lingara] {log.Level} {log.Target}: {log.Message}"); break;
        }
    }
}

GDScript

# The Lingara autoload the plugin adds, reached by path and typed, so every
# call and signal below is checked against LingaraEmbed.
@onready var lingara: LingaraEmbed = get_node("/root/Lingara")

# Enable the plugin (Project > Project Settings > Plugins): it adds the
# Lingara autoload, which drains the core once per frame and emits one signal
# per event. Connect the signals your game needs; you never call drain.
func _ready() -> void:
	lingara.token_needed.connect(_on_token_needed)
	lingara.event_received.connect(_on_event)
	lingara.error_raised.connect(func(error: Dictionary) -> void: push_warning(error.message))
	lingara.practice_changed.connect(_on_practice_changed)
	lingara.practice_finished.connect(_on_practice_finished)
	mei.replied.connect(_on_mei_replied)


func _on_event(_id: String, event_type: String, _created_at: String, data: Dictionary) -> void:
	if event_type == "lesson_plan.ready":
		print("a plan is ready: ", data.get("plan_id"))