VNLEWIKI이야기를 만들고, 세계를 연결하세요.
한국어
VNLE 사용 설명서

Unreal Engine

← 게임에 콘텐츠 연결하기

Unreal Engine 5 · C++

먼저 문장 하나를 화면에 표시해 보세요. 이 작은 예제는 실제 항구 내보내기 파일을 직접 읽으며, 완전한 대화 재생기는 아닙니다. 이후 데이터를 게임 UI에 연결하세요.

1 · 파일 및 장면 준비

C++ 프로젝트에서 자신의 Actor를 열린 맵에 배치하세요. .Build.cs의 모듈 의존성에 Json을 추가하고 story.json을 Content/Story에 넣으세요. 아래 함수를 Actor의 .cpp에 추가하세요. BeginPlay에서 Super::BeginPlay() 뒤에 UE_LOG(LogTemp, Display, TEXT("%s"), *ReadWelcome());를 호출하고 Output Log에서 결과를 확인하세요.

story.json · 내보낸 파일 설명

2 · 첫 번째 텍스트 표시

// Add these includes at the top of your existing Actor .cpp file.
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Dom/JsonObject.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"

// File-local helper; call from your Actor's BeginPlay.
static FString ReadWelcome()
{
    FString Raw;
    if (!FFileHelper::LoadFileToString(Raw, *(FPaths::ProjectContentDir() / TEXT("Story/story.json"))))
        return TEXT("Cannot load story.json");
    TSharedPtr<FJsonObject> Story;
    const auto Reader = TJsonReaderFactory<>::Create(Raw);
    if (!FJsonSerializer::Deserialize(Reader, Story) || !Story.IsValid())
        return TEXT("Invalid JSON");
    // Following accesses expect the supplied, validated Harbour export.
    const auto Content = Story->GetObjectField(TEXT("content"));
    const FString Id = TEXT("fd7b61a3-8233-4ba7-8f77-b88b119a91bf");
    auto Message = Content->GetObjectField(TEXT("texts"))->GetObjectField(Id)->GetObjectField(TEXT("sourceMessage"));
    const FString Locale = TEXT("en");
    const TSharedPtr<FJsonObject>* Table = nullptr;
    const TSharedPtr<FJsonObject>* Translation = nullptr;
    if (Content->GetObjectField(TEXT("translations"))->TryGetObjectField(Locale, Table)
        && (*Table)->TryGetObjectField(Id, Translation))
        Message = (*Translation)->GetObjectField(TEXT("message"));
    FString Line;
    for (const auto& Segment : Message->GetArrayField(TEXT("segments")))
    {
        const auto Part = Segment->AsObject();
        if (Part->GetStringField(TEXT("kind")) != TEXT("text"))
            return TEXT("Text-only example");
        Line += Part->GetStringField(TEXT("text"));
    }
    return Line;
}

예상 출력은 “Welcome to the harbour. I am Mira.”입니다. locale = "de" 또는 언어 매개변수 de를 사용하면 “Willkommen im Hafen. Ich bin Mira.”가 표시됩니다. 없는 언어는 원문을 사용합니다. 이 예제는 텍스트 세그먼트를 지원하며, 자리표시자를 알림 없이 무시하지 않습니다.

3 · 한 문장에서 대화

위의 고정 텍스트 ID는 첫 번째 테스트용입니다. 게임에서는 story-index.json에서 flowRef/entryRef를 선택하고 execution.entries[entryRef].nodeRef, 이어서 execution.nodes[nodeRef]를 읽으세요. 현재 대화 노드는 text.textRef와 speakerRef를 제공합니다. 다음 진행 방식은 노드 종류에 따라 달라집니다.

대화창에는 UMG의 UTextBlock::SetText(FText::FromString(Line)), UButton::OnClicked, UImage를 사용하세요. 위젯에는 UMG 모듈 의존성과 연결된 위젯 필드가 필요합니다. End에서는 RemoveFromParent() 또는 가시성 변경으로 닫고 게임 입력 모드를 복원하세요. 위의 JSON 읽기 코드에는 UMG가 필요하지 않습니다.

  1. Continue와 End: 연결을 따라 진행하고 대화창 닫기
  2. 선택지: 답변 ID, 표시 순서, 선택한 답변의 다음 진행
  3. 조건과 변수: 10 또는 2 골드
  4. 언어: 같은 ID로 새 언어의 콘텐츠 가져오기
  5. 명령: 아이템을 주고 가게를 엽니다.
  6. Call/Return: 하위 이야기로 이동했다가 돌아오기
이 프로그래밍 언어로 답변과 금화 검사 살펴보기

선택지 데이터 읽기

로딩 후 story를 사용할 수 있는 위치에 이 코드를 넣으세요. RPG Maker에서는 const story = $gameTemp.vnleExample.story;를 사용합니다. 확인용으로 ID를 출력하며, 실제 게임에서는 이 출력을 답변 버튼으로 바꾸세요. 항구의 두 답변과 이후 금화 검사를 다루는 예제일 뿐, 범용 해석기는 아닙니다.

// Inside ReadWelcome, after Story has been parsed successfully.
const auto Nodes = Story->GetObjectField(TEXT("execution"))->GetObjectField(TEXT("nodes"));
const auto Choice = Nodes->GetObjectField(TEXT("42edfff8-b8fe-46bf-ae0d-9e2347b6e726"));
for (const auto& IdValue : Choice->GetArrayField(TEXT("optionOrder")))
{
    const FString OptionId = IdValue->AsString();
    const auto Option = Choice->GetObjectField(TEXT("options"))->GetObjectField(OptionId);
    UE_LOG(LogTemp, Display, TEXT("%s: %s"), *OptionId, *Option->GetObjectField(TEXT("text"))->GetStringField(TEXT("textRef")));
}
const auto Branch = Nodes->GetObjectField(TEXT("c1c3c372-0a73-4cd0-851d-ede59d5709bb"));
const double Minimum = Branch->GetObjectField(TEXT("condition"))->GetObjectField(TEXT("right"))->GetObjectField(TEXT("value"))->GetNumberField(TEXT("value"));
const int32 Gold = 10; // Repeat with 2.
const auto Next = Branch->GetObjectField(Gold >= Minimum ? TEXT("whenTrue") : TEXT("whenFalse"));
UE_LOG(LogTemp, Display, TEXT("%s"), *Next->GetStringField(TEXT("nodeRef")));

금화가 10개이면 next는 “Pay five gold”, 2개이면 “Not enough gold”를 가리킵니다. 실제 게임에서는 선택한 경로가 이 노드에 도달했을 때만 검사하세요. 각 답변은 자신의 다음 진행 정보를 제공합니다.

4 · 자신의 이미지 연결하기

이 추가 예제는 현재 대화 객체 node와 주석에 적힌 UI 요소가 있다고 가정합니다. C++와 MonoGame에서는 speakerRef를 이미 SpeakerId 또는 speakerId로 읽은 상태입니다. 키는 미라의 실제 캐릭터 ID이며, 이미지 이름과 위치는 게임에서 정합니다. 표시된 위치에 코드를 넣으세요.

// In your UUserWidget header; assign the imported texture in its Blueprint defaults.
UPROPERTY(EditDefaultsOnly, Category="Dialogue")
TMap<FString, TObjectPtr<UTexture2D>> Portraits;
// Set the map key to: 807b2957-7bab-4a28-83a4-1f3d1589bbc2
// In widget code, PortraitImage is your bound UImage; SpeakerId comes from speakerRef.
auto* Texture = Portraits.Find(SpeakerId);
PortraitImage->SetBrushFromTexture(Texture ? Texture->Get() : nullptr);
PortraitImage->SetVisibility(Texture && Texture->Get()
    ? ESlateVisibility::Visible : ESlateVisibility::Hidden);

화자 이름도 대사처럼 content.characters[speakerRef].nameTextRef로 구하세요. 이미지 연결이 없으면 이전 초상화를 숨기거나 대체 이미지를 표시하세요. 이미지는 언어에 따라 선택되지 않습니다.

전체 내보내기의 이미지 참조 사용하기

5 · 게임과 함께 파일 배포하기

Project Settings → Packaging → Additional Non-Asset Directories to Package에 Story를 추가하세요. FFileHelper는 Unreal의 파일 접근을 사용합니다. 중첩된 ID 테이블은 DataTable 행이 아니므로 이 JSON을 DataTable로 가져오지 마세요. 이 안내는 C++용이며 Blueprint에 결과를 노출할지는 프로젝트에서 정합니다.

결과 확인

  1. 영어와 독일어 인사말이 올바르게 표시되고, 사용할 수 없는 언어를 선택하면 영어 원문이 표시되어야 합니다.
  2. 진행 로직을 추가했다면 두 답변, 금화 10개와 2개, 하위 이야기에서 돌아오기, End를 시험하세요.
  3. 빌드한 게임을 프로젝트 폴더 밖에서 실행하세요. JSON, 글꼴, 초상화가 그곳에서도 제공되어야 합니다.

공식 문서

API 및 이 접근 방식을 위한 설정 참조: