Documentation index for AI agents

GS2-News

공지 배포 기능

게임 내 공지사항을 HTML 형식으로 배포하기 위한 구조입니다. 배포하는 HTML 콘텐츠는 hugo(https://gohugo.io/) 로 생성해야 합니다.

GS2-News 는 기사 데이터를 업로드하면 GS2 측에서 hugo를 통한 빌드를 수행하여, 정적 웹 콘텐츠로 호스팅합니다. 게임 클라이언트는 배포된 임시 URL에 접근함으로써 공지사항을 열람할 수 있습니다.

graph LR
  Author["운영자"] -- "ZIP 업로드" --> GS2["GS2-News"]
  GS2 -- "hugo build (공개/비공개의<br/>모든 패턴을 사전 생성)" --> CDN["CDN (HTML / ZIP)"]
  Player["플레이어"] -- "GetContentsUrl" --> GS2
  GS2 -- "URL + Cookie<br/>(1시간 유효)" --> Player
  Player -- "WebView or ZIP DL" --> CDN

hugo

hugo 는 정적 웹페이지를 생성하기 위한 제너레이터입니다.

페이지의 레이아웃과 디자인을 결정하기 위한 템플릿과, 마크다운 형식으로 작성한 기사 데이터를 빌드함으로써 HTML 파일을 생성합니다. hugo의 이용 방법에 대해서는 hugo 공식 사이트 또는 각종 해설 사이트를 확인해 주십시오.

GS2에서는 최소한의 hugo 템플릿과 기사 데이터 샘플을 GitHub에서 공개하고 있습니다.

https://github.com/gs2io/gs2-news-sample

이벤트와 연동되는 기사

GS2-News 에서는 GS2-Schedule 로 관리하는 이벤트와 연동되는 기사 데이터를 관리할 수 있습니다.

기사 데이터의 마크다운 파일의 Front Matter에 다음과 같이 기재함으로써 연동을 활성화할 수 있습니다.

x_gs2_scheduleEventId: ${GS2-Schedule의 이벤트 GRN}

또한, GS2-Schedule 의 Event ID에는 다음 플레이스홀더를 사용할 수 있습니다.

플레이스홀더치환되는 값
{region}리전 이름
{ownerId}GS2의 오너ID

작성 예시는 다음과 같습니다.

x_gs2_scheduleEventId: grn:gs2:{region}:{ownerId}:schedule:schedule-0001:event:event-0001

이벤트가 개최 중인 경우에만 표시되는 기사나, 특정 이벤트 기간에만 비표시되는 기사 등을, 운영 도구에서 전환 조작을 하지 않고도 자동으로 제어할 수 있습니다.

기사 데이터의 크기 제한

GS2-News 에 업로드하는 기사 데이터는 전체적으로 100MB 이내에 담아야 합니다.

또한, 기사 데이터의 빌드에는 --buildDrafts 옵션이 포함되지 않습니다. Front Matter에 draft: true 로 기재된 콘텐츠는 배포되지 않습니다.

기사 데이터 빌드

GS2-News 에서 배포하는 기사 데이터는 zip으로 압축한 hugo 템플릿과 기사 파일을 업로드함으로써 갱신합니다. 그 후, GS2-News 는 기사 데이터를 빌드하여 배포합니다.

기사 데이터 안에 x_gs2_scheduleEventId 기재가 있는 경우에는, 각 기사가 공개 상태·비공개 상태의 각 버전을 사전에 빌드하여 웹 서버에 배치합니다.

이후, 플레이어가 기사 데이터에 접근하려 할 때 현재 이벤트의 개최 상황에 따라 가장 적절한 콘텐츠의 URL을 플레이어에게 응답합니다.

sequenceDiagram
  participant U as 운영자
  participant N as GS2-News
  participant CDN as CDN
  U->>N: PrepareUpdateCurrentNewsMaster
  N-->>U: 업로드용 URL
  U->>N: HTTP PUT (zip)
  U->>N: UpdateCurrentNewsMaster
  N->>N: hugo로 모든 패턴<br/>빌드
  N->>CDN: 정적 콘텐츠 배치

진행 상황은 Progress 모델(generated / patternCount)로 확인할 수 있으며, 오래 걸리는 빌드가 완료되었는지 폴링할 수 있습니다.

기사 데이터의 접근 권한

기사 데이터는 사전에 모든 패턴을 빌드하여 배포한다고 설명했습니다. 악의적인 플레이어가 URL을 특정하여, 원래 아직 배포해서는 안 되는 콘텐츠에 접근할 수 있어서는 안 됩니다.

이 문제를 방지하기 위해, GS2-News 는 Cookie를 이용한 접근 제한을 구현하고 있습니다. 현재 유효한 콘텐츠의 URL을 취득함과 동시에, 해당 URL에 접근하기 위한 Cookie 정보를 배포합니다. 브라우저에 해당 Cookie를 설정한 후 콘텐츠 URL에 접근함으로써 콘텐츠 다운로드가 가능해집니다.

Cookie의 유효 기간

취득한 Cookie는 유효 기간이 설정되어 있으며, 1시간이 경과하면 해당 Cookie로는 콘텐츠에 접근할 수 없게 됩니다. 장시간 게임을 플레이하는 플레이어에 대해서는 재취득을 수행해 주십시오.

Zip 형식으로 웹 콘텐츠 다운로드

GS2-News 가 호스팅한 HTML에 WebView를 사용해 접근하는 방법뿐만 아니라, GS2-News 가 빌드한 HTML 파일 전체를 zip 형식으로 다운로드하는 것도 가능합니다.

이 방법을 이용하면, zip으로 다운로드한 HTML 콘텐츠를 로컬 스토리지에 전개하고 이를 브라우저에서 표시함으로써 더욱 쾌적하게 콘텐츠를 열람할 수 있게 됨과 동시에, 같은 콘텐츠를 여러 번 다운로드하지 않아도 됩니다.

zip 파일을 다시 다운로드해야 하는지 판단하기 위해, GS2-News 는 템플릿 파일의 해시값과, GS2-Schedule 에 기반하여 공개 상태가 된 콘텐츠의 해시값을 취득할 수 있도록 하고 있습니다. 이 값들을 사용함으로써 zip 파일을 재다운로드해야 할지 판단하는 것이 가능해집니다.

해시값설명
TemplateHash업로드된 템플릿 ZIP의 해시값. 템플릿 자체가 갱신되면 변화합니다.
ContentHash현재의 이벤트 개최 상황에 따라 결정되는, 공개 상태 콘텐츠 집합의 해시값. 이벤트의 시작·종료로 변화합니다.

스크립트 트리거

GS2-News 에서는 스크립트 트리거를 제공하지 않습니다.

마스터 데이터 관리

마스터 데이터를 등록함으로써 마이크로서비스에서 이용 가능한 데이터나 동작을 설정할 수 있습니다.

마스터 데이터의 종류에는 다음이 있습니다.

  • 템플릿: 뉴스 표시용 Hugo 템플릿
  • 기사: Markdown 형식의 뉴스 기사

마스터 데이터 등록은 관리 콘솔에서 등록하는 것 외에, GitHub에서 데이터를 반영하거나, GS2-Deploy를 사용해 CI에서 등록하는 워크플로우를 구성하는 것도 가능합니다.

트랜잭션 액션

GS2-News 에서는 트랜잭션 액션을 제공하지 않습니다.

구현 예제

콘텐츠 URL 취득

GetContentsUrl 의 반환값에는 Cookie 목록과, BrowserUrl(WebView 열람용) / ZipUrl(ZIP 다운로드용)이 포함됩니다. 취득한 Cookie를 WebView 또는 HTTP 클라이언트에 등록한 후, 각각의 URL에 접근해 주십시오.

    var domain = gs2.News.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).News(
    );
    var result = await domain.GetContentsUrlAsync(
    );

    List<EzSetCookieRequestEntry> cookies = new List<EzSetCookieRequestEntry>();
    var items = result.ToList();
    foreach (var item in items)
    {
        var entry = await item.ModelAsync();
        cookies.Add(entry);
    }
    var browserUrl = domain.BrowserUrl;
    var zipUrl = domain.ZipUrl;
    const auto Domain = Gs2->News->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->News(
    );
    const auto Future = Domain->GetContentsUrl(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    TArray<EzSetCookieRequestEntryPtr> Cookies;
    const auto It = Future->GetTask().Result();
    foreach (auto Item in It)
    {
        const auto Future2 = Item.Model();
        Future2->StartSynchronousTask();
        if (Future2->GetTask().IsError()) return false;
        Cookies.Add(Future2->GetTask().Result());
    }
    const auto BrowserUrl = Domain->BrowserUrl;
    const auto ZipUrl = Domain->ZipUrl;
var domain = ez.news.namespace_(
        "namespace-0001"
    ).me(game_session).news(
    )

var async_result = await domain.get_contents_url(
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

WebView 표시 예시

Unity 에서는 unity-webview 를 사용하여, 취득한 Cookie를 WebView에 설정한 후 BrowserUrl 을 열어 공지사항을 표시할 수 있습니다. Godot 에서는 이용하는 WebView 플러그인의 Cookie 설정 API에 취득한 Cookie를 전달한 후, 마찬가지로 BrowserUrl 을 엽니다. WebView의 API 이름은 플러그인마다 다릅니다.

    foreach (var cookie in cookies) {
        webView.SetCookie(browserUrl, cookie.Key, cookie.Value);
    }
    webView.LoadURL(browserUrl);
    webView.SetVisibility(true);
# 사용하는 WebView 플러그인의 Cookie 설정·URL 표시 API로 교체합니다.
for cookie in cookies:
    web_view.set_cookie(browser_url, cookie.key, cookie.value)
web_view.open_url(browser_url)
web_view.show()

기사 데이터 목록 취득

각 기사는 SectionTitleContentFrontMatter 등을 보유하고 있습니다. 제목 목록을 자체 UI로 표시하고, 선택된 기사만 WebView로 표시하는 구성도 가능합니다.

    var domain = gs2.News.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );
    var items = await domain.NewsesAsync(
    ).ToListAsync();
    var templateHash = domain.TemplateHash;
    var contentHash = domain.ContentHash;
    const auto Domain = Gs2->News->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto It = Domain->Newses(
    );
    TArray<Gs2::UE5::News::Model::FEzNewsPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.news.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).newses(
    )

var async_result = await iterator.load()
if async_result.error != null:
    # 오류를 처리
    push_error(str(async_result.error))
    return

var items = async_result.result

캐시 재다운로드 판정

ZIP 다운로드 방식을 채택하는 경우, TemplateHashContentHash 를 로컬에 저장해 두고, 시작 시 다시 취득한 값과 비교함으로써 불필요한 ZIP 재다운로드를 피할 수 있습니다.

    if (savedTemplateHash != domain.TemplateHash ||
        savedContentHash != domain.ContentHash) {
        // 재다운로드를 실행
    }
if saved_template_hash != domain.template_hash \
        or saved_content_hash != domain.content_hash:
    # 재다운로드를 실행
    download_contents()

상세 레퍼런스