Lingara Lingara 문서 학습 가이드 API 라이브러리 앱 만들기 임베드 웹 앱
언어: 한국어

NPC 대화

이 페이지는 영어에서 번역되었습니다. 두 내용이 다를 경우 영어 페이지가 정확합니다. 영어 페이지 읽기

게임 속 캐릭터는 플레이어가 배우는 언어로, 플레이어의 레벨에 맞춰 대화할 수 있습니다. 플레이어가 말하는 한 줄이 한 턴입니다. 플레이어 키트가 그것을 보내고, 캐릭터의 응답이 조각조각 스트리밍되어 돌아옵니다.

턴에 담기는 것

Lingara는 턴 사이에 대화를 보관하지 않으므로, 모든 턴은 캐릭터에게 필요한 것 전부를 보냅니다.

  • npc: 말하는 사람. 그 name은 132자이고, 선택적인 persona는 “만두 장수”처럼 한 줄짜리 설명으로 1120자입니다.
  • scene: 선택 사항으로, 대화가 일어나는 장소이며 1~160자입니다.
  • source_lang와 target_lang: 플레이어 자신의 언어와 배우는 언어. 둘은 서로 달라야 하며, 캐릭터는 target_lang로만 말합니다.
  • level: 플레이어의 레벨로, 1부터 9까지입니다. Lingara의 가이드 “레벨의 의미”에서 그 척도를 설명합니다.
  • line: 플레이어가 방금 한 말로, 1~500자입니다.
  • history: 지금까지의 대화로, 오래된 것부터 최대 12줄입니다. 각 줄에는 speaker(player 또는 npc)와 그 text(1~500자)가 있습니다.

각 텍스트는 한 줄입니다. 대화는 여러분의 게임이 보관합니다. 플레이어 키트는 여러분이 여는 대화마다 창을 보관하고, 끝난 턴을 거기에 추가하고, 각 응답을 처음 500자로 자르고, 12줄을 넘는 오래된 줄을 버립니다.

Lingara는 페르소나와 장면을 게임에서 온 신뢰할 수 없는 텍스트로 취급합니다. 그것들은 캐릭터의 말투를 만들 뿐, 캐릭터에게 허용되는 일을 정하지는 않습니다.

응답이 도착하는 방식

응답은 텍스트 조각으로 스트리밍되며, 게임은 도착하는 대로 이를 이어 붙입니다. 조각 사이에 응답이 중간에 잘렸다는 알림 같은 공지가 올 수 있습니다. 턴은 응답이 완료되면 done으로, 실패하면 error로 끝납니다. 턴은 재개할 수 없습니다. 연결이 끊기면 플레이어가 그 줄을 다시 말합니다.

각 캐릭터에게는 한 번에 한 줄씩 말하세요. 캐릭터가 아직 응답하는 중에 말한 줄은 대기열에 들어가지 않고 거부됩니다. 각 턴에 요금이 부과되기 때문입니다.

누가 비용을 내는가

대화할 수 있는 것은 사용량 기반 결제 클라이언트의 플레이어뿐이며, 각 턴은 그 클라이언트에 청구됩니다. 플레이어의 토큰에는 embed:play가 필요합니다.

코드 예제 언어

Bevy

fn spawn_seller(mut commands: Commands) {
    let mei = NpcDialogue::new("Mei", "zh", 2)
        .and_then(|npc| npc.with_persona("a dumpling seller"))
        .and_then(|npc| npc.with_scene("the night market"))
        .expect("within Lingara's bounds");
    commands.spawn(mei);
    commands.spawn(Player);
}

/// One turn at a time: a line said while the NPC is replying is a `Busy` error.
fn talk(keys: Res<ButtonInput<KeyCode>>, npcs: Query<Entity, With<NpcDialogue>>, mut say: MessageWriter<NpcSay>) {
    if keys.just_pressed(KeyCode::Digit1) {
        for entity in &npcs {
            say.write(NpcSay { entity, line: "饺子多少钱?".into() });
        }
    }
}

/// `streaming` grows as the reply arrives; `turns` holds the finished ones.
fn show_reply(transcripts: Query<&NpcTranscript, Changed<NpcTranscript>>) {
    for transcript in &transcripts {
        if let Some(partial) = &transcript.streaming {
            info!("…{partial}");
        }
    }
}

Unity

public DialogueId OpenDialogue(string npc, string sourceLang, string targetLang, byte level) =>
    Client.DialogueOpen(new DialogueConfig { Npc = new Npc { Name = npc }, SourceLang = sourceLang, TargetLang = targetLang, Level = level });

// The reply streams back as NpcTurn events: append each Fragment's text to your label.
public RequestId Say(DialogueId dialogue, string line) => Client.DialogueSay(dialogue, line);

Godot C#

public long OpenDialogue(string npc, string sourceLang, string targetLang, byte level) =>
    Client.DialogueOpen(new DialogueConfig { Npc = new Npc { Name = npc }, SourceLang = sourceLang, TargetLang = targetLang, Level = level }).Value;

// The reply streams back as NpcTurnFragment signals, then one NpcTurnFinished.
public long Say(long dialogue, string line) => Client.DialogueSay(new DialogueId(dialogue), line).Value;

GDScript

# A LingaraNpc child per character: npc_name, target_lang and level in the
# inspector. say() returns false while a turn is in flight (turns are never
# queued, because each one is billed).
func talk_to_mei(line: String) -> void:
	if mei.say(line):
		bubble.text = "…"


func _on_mei_replied(text: String) -> void:
	bubble.text = text

Unreal C++

void ALingaraEmbedSnippetActor::Say(const FString& Line)
{
	ELingaraCallResult Result;
	FLingaraEmbedError Error;
	if (Dialogue == 0)
	{
		FLingaraDialogueConfig Config;
		Config.Npc.Name = TEXT("Mei");
		Config.Npc.Persona = TEXT("a tea seller at the night market");
		Config.SourceLang = TEXT("en");
		Config.TargetLang = TEXT("zh");
		Config.Level = 2;
		Dialogue = Lingara()->DialogueOpen(Config, Result, Error);
	}
	Reply.Reset();
	Lingara()->DialogueSay(Dialogue, Line, Result, Error);
}

// The reply streams back as npc_turn events: append each fragment.
void ALingaraEmbedSnippetActor::HandleNpcTurn(const FLingaraNpcTurn& Turn)
{
	if (Turn.Dialogue == Dialogue && Turn.Update.Type == ELingaraNpcTurnUpdateType::Fragment)
	{
		Reply += Turn.Update.Text;
	}
}

Kotlin

fun talkToMei(line: String) {
    val dialogue = npc ?: lingara.dialogueOpen(DialogueConfig(Npc("Mei", "a tea seller"), "the tea house", "en", "zh", 2, emptyList()))
    npc = dialogue
    lingara.dialogueSay(dialogue, line)
}

private fun onNpcTurn(update: NpcTurnUpdate) {
    when (update) {
        is NpcTurnUpdate.Opened -> bubble = ""
        is NpcTurnUpdate.Fragment -> bubble += update.text
        is NpcTurnUpdate.Finished -> bubble = update.text
        else -> Unit
    }
}

Java

void talkToMei(String line) {
  if (npc < 0) {
    Npc mei = new Npc("Mei", "a tea seller");
    npc =
        lingara.dialogueOpen(new DialogueConfig(mei, "the tea house", "en", "zh", 2, List.of()));
  }
  lingara.dialogueSay(npc, line);
}

void onNpcTurn(EmbedEvent.NpcTurn turn) {
  if (turn.getUpdate() instanceof NpcTurnUpdate.Fragment fragment) {
    bubble.append(fragment.getText());
  } else if (turn.getUpdate() instanceof NpcTurnUpdate.Finished finished) {
    bubble.setLength(0);
    bubble.append(finished.getText());
  }
}

함께 보기