Lingara Lingara المستندات أدلة التعلّم API المكتبات التطبيقات البناء التضمين تطبيق الويب
اللغة: العربية

Godot

هذه الصفحة مترجمة من الإنجليزية. إذا اختلفت النسختان، فالصفحة الإنجليزية هي الصحيحة. اقرأ الصفحة الإنجليزية

يوفّر Godot لغتين، ولدى Lingara حزمة لكل منهما. تستخدم لعبة GDScript إضافة Lingara. وتستخدم لعبة Godot بلغة C# الحزمة Lingara.Embed، وهي الحزمة نفسها التي تستخدمها لعبة Unity. استخدم إحداهما أو الأخرى في المشروع، ولا تستخدمهما معًا أبدًا. تعرض علامات التبويب أدناه كلًّا منهما.

التثبيت

GDScript. ثبّت إضافة Lingara من Godot Asset Library، ثم فعّلها من Project Settings، ضمن Plugins. يضيف تفعيلها تحميلًا تلقائيًا (autoload) باسم Lingara، يصل إليه كل مشهد عند /root/Lingara.

C#. أضِف Lingara.Embed إلى مشروعك كمرجع حزمة. العقدة النموذجية أدناه، LingaraEmbedNode، ليست جزءًا من الحزمة: أضِف شيفرتها المصدرية إلى مشروعك وضعها في مشهد.

احصل على الرموز من خادمك

لا تحتفظ أيّ من الحزمتين بسرّ. عندما تحتاج النواة إلى رمز، تطلبه من خادمك أنت، فيُصدر رمزًا باستخدام حزمة خادم ويجيب بالرمز وموعد انتهاء صلاحيته.

مع الإضافة، عيّن مسار الرموز في خادمك ضمن Project Settings، فتستدعيه Lingara على خيطها الخاص. إذا كان المسار يحتاج إلى جلسة اللاعب، فعيّنه وقت التشغيل بدلًا من ذلك، لأن إعدادات المشروع تُشحن داخل ملف التصدير. أو اربط token_needed وأجب عن كل طلب بنفسك. مع C#، ترسل العقدة النموذجية طلبًا إلى مسار الرموز كلما طلب العميل رمزًا. وفي الحالتين، أبلغ عن الفشل باستخدام token_unavailable أو TokenUnavailable، حتى تفشل الاستدعاءات المنتظرة فورًا.

عالج الأحداث في كل إطار

تشغّل النواة عمليات الإدخال والإخراج الشبكية على خيطها الخاص. يُفرغها التحميل التلقائي للإضافة مرة واحدة في كل إطار ويبثّ إشارة واحدة لكل حدث، فتربط الإشارات التي تحتاجها لعبتك ولا تُفرغ أي شيء بنفسك أبدًا. تُفرغ عقدة C# الأحداث في _process، بحد أقصى 256 حدثًا في الإطار، وتعيد بثّ كل منها على شكل إشارة Godot.

تحمل الإشارات نفسها ردود NPC المتدفّقة (انظر حوار NPC) وتحديثات التدريب (انظر نتائج التدريب).

<PackageReference Include="Lingara.Embed" Version="0.1.0-alpha.6" />
لغة مثال الشيفرة

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])
لغة مثال الشيفرة

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"))