게임에 콘텐츠 연결하기
VNLE는 텍스트, 번역, 작성한 진행 흐름을 내보냅니다. 게임은 이 데이터를 읽고 대화창을 표시하며 게임 액션을 자신의 시스템에 연결합니다. 직접 작성한 스크립트나 커뮤니티 연동을 사용하세요. 이 예제는 VNLE 핸들러 없이 데이터를 처리하는 방법을 설명합니다.
파일, JSON, 시작점
내보내기 → 파일과 JSON 이해하기 → 진행 규칙 찾아보기
모든 예제는 실제 The Harbour Key 내보내기를 사용합니다. 긴 ID는 해당 프로젝트의 것이므로 자신의 내보내기 ID로 바꾸세요. 이름이나 번역된 텍스트는 참조 키가 아닙니다.
story.json · story-index.json · 편집 가능한 예 프로젝트
엔진 선택하기
각 페이지는 해당 엔진에서 데이터를 불러오고 표시하는 방법을 설명합니다. 아래 학습 과정에서 공통 데이터 규칙을 안내합니다. 다른 엔진도 같은 JSON 규약을 사용할 수 있지만, 완성된 플러그인이 제공된다는 뜻은 아닙니다.
1 · JSON에서 하나의 텍스트 표시
“Welcome to the harbour. I am Mira.”부터 표시해 보세요. 대화에는 문자열 자체가 아닌 textRef가 있고, 텍스트 테이블에는 세그먼트가 있습니다. 이 JavaScript 예제는 story.json을 이미 story로 읽었다고 가정합니다. 불러오기와 표시 방법은 엔진별 페이지를 참고하세요.
// story is the parsed story.json. This excerpt reads text-only messages.
const locale = 'en';
const textRef = 'fd7b61a3-8233-4ba7-8f77-b88b119a91bf';
const record = story.content.texts[textRef];
const translation = story.content.translations[locale]?.[textRef];
const message = translation ? translation.message : record.sourceMessage;
const text = message.segments.map(segment => {
if (segment.kind !== 'text') throw new Error('This example needs text-only segments');
return segment.text;
}).join('');
console.log(text);첫 테스트는 고정 텍스트 ID를 사용합니다. 빈 번역과 없는 번역을 혼동하지 마세요. 존재하며 유효한 빈 번역은 빈 상태로 표시할 수 있습니다.
2 · 시작, 계속하고 대화를 종료
플레이어가 미라에게 말을 걸면 게임은 story-index.json에서 해당 시작점을 고르고 대화창을 연 뒤 첫 노드를 찾습니다. JSON 속성 순서가 아닌 참조를 따라가세요.
const entryRef = '5e6b2e12-1ac1-4c46-8221-1a051e87370a';
const entry = story.execution.entries[entryRef];
const node = story.execution.nodes[entry.nodeRef];
console.log(node.kind, node.text.textRef);
// Read this dialogue's text; wait for the player's Continue action.
// node.continuation describes the next step, not the next array element.Continue 이후에는 continuation을 따릅니다. kind: node는 nodeRef로 다음 노드를 가리키고 kind: end는 대화를 끝냅니다. kind가 end인 노드도 같습니다. 게임은 대화창을 닫고 조작을 되돌립니다. VNLE가 앱을 종료하거나 레벨을 자동으로 끝내지는 않습니다.
{
"continuation": {
"kind": "node",
"nodeRef": "7c804744-b002-4136-9a7c-703fcf2d0880"
},
"flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
"id": "d789af84-d51d-4b97-b564-86a6756e2f2e",
"kind": "dialogue",
"speakerRef": "807b2957-7bab-4a28-83a4-1f3d1589bbc2",
"text": {
"bindings": {},
"textRef": "cbe1a637-991f-4857-81d1-6cc053d68927"
}
}이 코드에서 “Maybe later”는 End 노드로 이어집니다. 예제 인사말은 먼저 Call Substory로 이어집니다. 대화와 종료만 지원하는 기본 스크립트는 Call/Return을 지원하기 전까지 그곳에서 설명과 함께 멈춰야 합니다.
3 · 답변 제시하기
미라는 “Buy the key (5 gold)”와 “Maybe later”를 제시합니다. optionOrder가 순서를 정하고 options가 각 항목을 담습니다. 버튼마다 선택지 ID를 유지하세요. 클릭하면 선택한 답변의 effects와 continuation만 적용합니다.
// Read this exact choice from the Harbour story. No condition on these two options.
const choice = story.execution.nodes['42edfff8-b8fe-46bf-ae0d-9e2347b6e726'];
for (const optionId of choice.optionOrder) {
const option = choice.options[optionId];
console.log(optionId, option.text.textRef, option.continuation);
}
// Each answer button must retain its optionId.
// On click: check availability, apply that option's effects once,
// then follow that option's continuation.option.condition이 있으면 답변을 제시할지 결정합니다. 항구 내보내기의 이 두 답변에는 자체 조건이 없고, 금화 검사는 다음 branch 노드에 있습니다. 답변이 하나도 없으면 whenEmpty에 따라 지정된 다음 경로로 진행하거나 설명이 있는 오류를 표시합니다.
4 · 조건 확인 및 값 변경
금화 10개로는 열쇠를 살 수 있고 2개로는 살 수 없습니다. 조건은 이미 JSON에 있으며 게임이 현재 금화 값을 제공합니다. 이 예제는 gte 비교를 보여 줍니다. 다른 연산자와 중첩 조건은 진행 규칙을 참고하세요.
// This excerpt evaluates the Harbour purchase check: Gold >= 5.
const branch = story.execution.nodes['c1c3c372-0a73-4cd0-851d-ede59d5709bb'];
const condition = branch.condition;
const gold = 10; // Supply the current value from your game's saved state.
const minimum = condition.right.value.value; // 5, from the exported typed literal.
const continuation = gold >= minimum ? branch.whenTrue : branch.whenFalse;
console.log(story.execution.nodes[continuation.nodeRef].kind);
// Repeat with gold = 2: the next node is a dialogue explaining the price.
// The full condition grammar is documented in Flow rules.조건과 변경에 사용하는 실제 JSON 필드
{
"condition": {
"kind": "compare",
"left": {
"kind": "variable",
"variableRef": "a547610e-661f-4b8d-a25b-21d042f1a2d5"
},
"operator": "gte",
"right": {
"kind": "literal",
"value": {
"type": "integer",
"value": 5
}
}
},
"flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
"id": "c1c3c372-0a73-4cd0-851d-ede59d5709bb",
"kind": "branch",
"whenFalse": {
"kind": "node",
"nodeRef": "eaffd4b0-397d-40b7-abc6-404a0a801e0e"
},
"whenTrue": {
"kind": "node",
"nodeRef": "242b2fd4-8101-4a7f-9306-17b789508bdc"
}
}{
"continuation": {
"kind": "node",
"nodeRef": "500c28a1-ed85-4c36-9a38-bedacce1c6b7"
},
"effects": [
{
"kind": "subtract",
"value": {
"kind": "literal",
"value": {
"type": "integer",
"value": 5
}
},
"variableRef": "a547610e-661f-4b8d-a25b-21d042f1a2d5"
},
{
"kind": "set",
"value": {
"kind": "literal",
"value": {
"type": "boolean",
"value": true
}
},
"variableRef": "76407a53-5ce1-408e-a8c6-a31c10f69ca5"
}
],
"flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
"id": "242b2fd4-8101-4a7f-9306-17b789508bdc",
"kind": "action"
}Change Variable은 effects가 있는 action으로 내보내집니다. 여기서는 금화 다섯 개를 빼고 QuestAccepted를 true로 설정합니다. 새 게임 컨텍스트를 만들 때만 execution.variables로 초기화하고, 저장 데이터를 불러올 때는 저장된 값을 복원하세요. 인벤토리에도 금화가 있다면 기준이 되는 값을 하나로 정하고 갱신을 조율하세요.
5 · 언어 변경
전체 내보내기는 content.translations에 번역을 저장합니다. 언어마다 별도 JSON을 불러올 필요가 없습니다. content.translations[locale][textRef].message가 있으면 사용하고, 없으면 content.texts[textRef].sourceMessage를 사용하세요. locale은 게임 설정에서 가져옵니다.
언어를 바꾸면 현재 대사, 화자 이름, 제시된 답변을 다시 구하세요. 위치, 선택한 답변, 변수는 유지합니다. 명령이나 변수 변경을 다시 실행하지 마세요. 현재 대사를 처음 표시할 때 확정한 자리표시자 값을 재사용하세요.
자리표시자: “금화가 5개 남았습니다” · VNLE에서 번역 유지
6 · 하나의 대화창에 이름, 대사, 초상화 표시하기
- 대화 텍스트: node.text.textRef를 해결합니다.
- 화자는 content.characters[node.speakerRef]에서 찾고 nameTextRef를 번역합니다. speakerRef가 없으면 캐릭터 없는 서술문일 수 있습니다.
- 전체 내보내기의 초상화는 요청한 감정의 이미지를 우선하고, 없으면 defaultPortraitRef를 사용합니다. story.json이 있는 폴더를 기준으로 content.assets[assetRef].relativePath를 불러오세요. JSON에는 PNG 이미지 데이터가 아닌 경로가 들어 있습니다.
- 사용 가능한 선택지 ID마다 답변 버튼을 만드세요. 일반 대화에는 Continue를 표시합니다.
- End에서는 대화창 전체를 닫고 답변 버튼을 비활성화하며, 필요하면 플레이어 조작을 복원하세요.
엔진의 자체 이미지를 사용하거나 내보내기에 초상화 연결이 없다면 게임에서 연결 정보를 정의하세요. 캐릭터 ID → 이미지 리소스 연결은 이름이나 번역이 바뀌어도 유지됩니다. 감정에는 감정 ID도 포함하세요.
// The game owns this mapping. These are real Harbour character IDs.
const portraits = {
'807b2957-7bab-4a28-83a4-1f3d1589bbc2': 'assets/portraits/mira.png'
};
const node = story.execution.nodes['ffa7141b-97ed-44c1-a346-8bba7e26d94f'];
const file = portraits[node.speakerRef];
// Load file using your engine's image loader; show a placeholder if absent.
// Optional emotion variants: portraits[characterId][emotionId].
// Do not index by the translated character name.엔진별 페이지는 가져온 텍스처, 스프라이트, 아틀라스 이미지로 연결하는 방법을 보여 줍니다. VNLE 내보내기는 언어별 이미지 선택을 제공하지 않습니다. 이미지가 없으면 이전 초상화를 지우고 필요에 따라 대체 이미지를 표시하세요.
필요할 때 더 복잡한 진행 구현하기
이동 · 서브스토리 호출 / 복귀 · 명령 · 저장 및 오류 케이스
이야기 작성자는 진행 흐름을 만듭니다. 개발자는 노드 동작을 한 번 구현하고 합의한 명령을 게임에 연결합니다. 이후 이야기 변경은 내보낸 연결 관계에 따라 실행됩니다.