Documentation index for AI agents

GS2-StateMachine SDK for Game Engine API 레퍼런스

게임 엔진용 GS2-StateMachine SDK의 모델 사양과 API 레퍼런스

모델

EzStatus

스테이트 머신의 상태

특정 사용자에 대한 스테이트 머신의 실행 인스턴스를 나타냅니다. 변수, 서브 스테이트 머신용 콜 스택, 전이 횟수, 난수 상태를 포함한 현재 실행 상태를 추적합니다. 스테이터스는 Running, Wait, Pass(성공), Error 상태를 전이합니다.

타입활성화 조건필수기본값값 제한설명
statusIdstring
~ 1024자스테이트 머신 상태 GRN
※ 서버가 자동으로 설정
namestring
UUID~ 36자스테이터스 이름
스테이트 머신 상태의 고유한 이름을 보유합니다.
이름은 UUID(Universally Unique Identifier) 형식으로 자동 생성되며, 각 스테이트 머신의 상태를 식별하는 데 사용됩니다.
enableSpeculativeExecution문자열 열거형
enum {
  “enable”,
  “disable”
}
“disable”투기적 실행을 활성화할지 여부
활성화하면 스테이트 머신 정의와 난수 상태가 이 Status 엔티티에 포함됩니다. 이를 통해 클라이언트가 서버 확인 전에 로컬에서 전이를 시뮬레이션할 수 있어 체감 지연을 줄일 수 있습니다.
정의설명
enable활성화
disable비활성화
stateMachineDefinitionstring{enableSpeculativeExecution} == “enable”~ 16777216자스테이트 머신 정의
투기적 실행을 위해 이 스테이터스에 포함된 GSL 정의입니다. enableSpeculativeExecution이 활성화된 경우에만 존재합니다. 일반적인 API 응답에서는 필터링되며, 클라이언트 측 시뮬레이션에 필요한 경우에만 포함됩니다.

※ enableSpeculativeExecution이(가) “enable” 이면 활성화
randomStatusEzRandomStatus{enableSpeculativeExecution} == “enable”난수 상태
이 실행 인스턴스의 난수 생성 상태입니다. 투기적 실행 시 클라이언트와 서버에서 결정론적인 난수 동작을 보장하기 위해 사용됩니다. enableSpeculativeExecution이 활성화된 경우에만 존재합니다.

※ enableSpeculativeExecution이(가) “enable” 이면 활성화
stacksList<EzStackEntry>[]0 ~ 1024 items스택
서브 스테이트 머신 호출의 콜 스택입니다. 스테이트 머신이 서브 스테이트 머신을 호출하면 호출한 쪽의 스테이트 머신 이름과 반환 태스크가 푸시됩니다. 서브 스테이트 머신이 완료되면 엔트리가 팝되고 반환 태스크에서 실행이 재개됩니다.
variablesList<EzVariable>[]0 ~ 1000 items스테이트 머신별 상태 변수
콜 계층 내 각 스테이트 머신의 현재 변수 값입니다. 각 엔트리는 스테이트 머신 이름과, int, float, string, bool, array, map 타입을 지원하는 JSON 직렬화된 값을 보유합니다. 변수는 동일한 실행 인스턴스 내 상태 전이를 거쳐 유지됩니다.
status문자열 열거형
enum {
  “Running”,
  “Wait”,
  “Pass”,
  “Error”
}
“Running”스테이터스
이 스테이트 머신 인스턴스의 현재 실행 상태입니다. “Running"은 머신이 전이를 처리 중임을 의미합니다. “Wait"는 머신이 외부 이벤트(emit)를 대기 중임을 의미합니다. “Pass"는 정상적으로 완료되었음을 의미합니다. “Error"는 에러로 종료되었음을 의미합니다.
정의설명
Running실행 중
Wait대기 중
Pass정상 종료
Error이상 종료
lastErrorstring~ 1024자마지막 에러
마지막으로 발생한 에러의 메시지입니다. 스테이터스가 “Error"로 전이되었을 때 설정됩니다. 스테이트 머신이 이상 종료된 원인의 상세 내용을 포함합니다.
transitionCountint00 ~ 2147483645전이 횟수
이 실행 인스턴스에서 수행된 상태 전이의 총 횟수입니다. 전이할 때마다 증가합니다. 이 값이 1000을 초과하면 무한 루프를 방지하기 위해 스테이트 머신은 에러로 종료됩니다.

EzStackEntry

스택 엔트리

서브 스테이트 머신의 콜 스택 내 하나의 엔트리를 나타냅니다. 스테이트 머신이 서브 스테이트 머신을 호출하면 호출한 쪽의 이름과 반환 태스크가 스택에 푸시됩니다. 서브 스테이트 머신이 완료되면 팝된 엔트리의 반환 태스크에서 실행이 재개됩니다.

타입활성화 조건필수기본값값 제한설명
stateMachineNamestring
~ 128자스테이트 머신 이름
이 스택 엔트리를 푸시한 호출 측 스테이트 머신의 이름입니다. 서브 스테이트 머신에서 돌아올 때 어떤 스테이트 머신의 컨텍스트를 복원할지 식별하는 데 사용됩니다.
taskNamestring
~ 128자태스크 이름
서브 스테이트 머신이 완료되었을 때 돌아갈 태스크(상태)의 이름입니다. 호출 측 스테이트 머신의 이 태스크에서 실행이 재개됩니다.

EzVariable

스테이트 머신별 상태 변수

호출 계층 내 특정 스테이트 머신의 현재 변수 상태를 보유합니다. 값은 int, float, string, bool, array, map 데이터 타입을 지원하는 JSON 직렬화 표현입니다.

타입활성화 조건필수기본값값 제한설명
stateMachineNamestring
~ 128자스테이트 머신 이름
이들 변수를 소유하는 스테이트 머신의 이름입니다. 중첩된 호출 계층에서는 각 스테이트 머신이 이 이름으로 식별되는 독립적인 변수 스코프를 가집니다.
valuestring
~ 1048576자
이 스테이트 머신의 JSON 직렬화된 변수 값입니다. int, float, string, bool, array, map 데이터 타입을 지원합니다. 스테이트 머신이 전이나 액션을 처리할 때 갱신됩니다.

EzChangeStateEvent

상태 변경 이벤트

스테이트 머신 내에서 발생한 상태 전이를 기록합니다. 전이 대상 태스크 이름, 무결성 검증용 해시, 전이가 발생한 타임스탬프를 포함합니다.

타입활성화 조건필수기본값값 제한설명
taskNamestring
~ 128자태스크 이름
스테이트 머신이 전이한 대상 태스크(상태)의 이름입니다.
hashstring
~ 64자해시
상태 전이의 무결성을 검증하기 위한 해시 값입니다. 전이가 올바르게 수행되었고 상태가 일관되어 있음을 검증하는 데 사용됩니다.
timestamplong
타임스탬프

EzEmitEvent

메시지 전송 이벤트

외부 액션을 트리거하기 위해 스테이트 머신이 전송하는 메시지를 나타냅니다. 이벤트 이름은 액션의 종류를 식별하고, 파라미터는 액션별 데이터를 제공합니다.

타입활성화 조건필수기본값값 제한설명
eventstring
~ 128자이벤트 이름
전송된 이벤트의 종류를 식별하는 이름입니다. 보상 지급이나 리소스 소비 등, 어떤 외부 액션을 호출할지 판단하는 데 사용됩니다.
parametersstring
~ 1024자파라미터
전송된 이벤트와 함께 전달되는 파라미터입니다. 이 이벤트에 의해 트리거되는 외부 액션을 설정하는 데 사용되는, 액션별 데이터를 직렬화된 형식으로 포함합니다.
timestamplong
타임스탬프

EzEvent

이벤트

스테이트 머신 실행 중에 발생한 이벤트를 나타냅니다. 상태 변경 이벤트(상태 전이를 기록)이거나 emit 이벤트(외부 액션을 트리거하기 위한 메시지 전송) 중 하나입니다.

타입활성화 조건필수기본값값 제한설명
eventType문자열 열거형
enum {
  “change_state”,
  “emit”
}
이벤트 종류
이벤트의 종류입니다. “change_state"는 스테이트 머신 내의 상태 전이를 기록합니다. “emit"은 보상 지급이나 리소스 소비 등의 외부 액션을 트리거하기 위한 메시지 전송을 나타냅니다.
정의설명
change_state상태 변경
emit메시지 전송
changeStateEventEzChangeStateEvent{eventType} == “change_state”
✓※
상태 변경
※ eventType이(가) “change_state” 이면 필수
emitEventEzEmitEvent{eventType} == “emit”
✓※
메시지 전송
※ eventType이(가) “emit” 이면 필수

EzRandomStatus

난수 상태

스테이트 머신 실행 인스턴스의 난수 생성 상태를 관리합니다. 시드 값과 카테고리별 사용 현황 추적을 포함합니다. 추측 실행 시 클라이언트와 서버 간에 결정론적인 난수 동작을 보장하기 위해 사용됩니다.

타입활성화 조건필수기본값값 제한설명
seedlong
0 ~ 4294967294난수 시드
스테이트 머신 실행 내에서 결정론적인 난수 생성을 위한 시드 값입니다.
usedList<EzRandomUsed>0 ~ 1000 items사용된 난수 목록
카테고리별로 소비된 난수를 추적합니다. 각 카테고리는 난수 사용의 서로 다른 목적을 나타내며, 추측 재실행 시 일관성을 유지하기 위한 독립적인 추적을 가능하게 합니다.

EzRandomUsed

사용한 난수

스테이트 머신 실행 내 특정 카테고리에서 소비된 난수의 수를 추적합니다. 각 카테고리는 서로 다른 목적을 위한 독립적인 난수 추적을 가능하게 합니다.

타입활성화 조건필수기본값값 제한설명
categorylong
0 ~ 4294967294카테고리
난수 사용 카테고리의 숫자 식별자입니다. 각 카테고리는 난수 소비를 독립적으로 추적하여, 스테이트 머신이 서로 다른 목적에 별도의 난수 시퀀스를 사용할 수 있도록 합니다.
usedlong
0 ~ 4294967294사용 횟수
이 카테고리에서 소비된 난수의 수입니다. 이 카테고리의 시퀀스에서 난수가 뽑힐 때마다 증가합니다.

EzVerifyActionResult

검증 액션 실행 결과

타입활성화 조건필수기본값값 제한설명
action문자열 열거형
enum {
"Gs2Dictionary:VerifyEntryByUserId",
"Gs2Distributor:IfExpressionByUserId",
"Gs2Distributor:AndExpressionByUserId",
"Gs2Distributor:OrExpressionByUserId",
"Gs2Enchant:VerifyRarityParameterStatusByUserId",
"Gs2Experience:VerifyRankByUserId",
"Gs2Experience:VerifyRankCapByUserId",
"Gs2Grade:VerifyGradeByUserId",
"Gs2Grade:VerifyGradeUpMaterialByUserId",
"Gs2Guild:VerifyCurrentMaximumMemberCountByGuildName",
"Gs2Guild:VerifyIncludeMemberByUserId",
"Gs2Inventory:VerifyInventoryCurrentMaxCapacityByUserId",
"Gs2Inventory:VerifyItemSetByUserId",
"Gs2Inventory:VerifyReferenceOfByUserId",
"Gs2Inventory:VerifySimpleItemByUserId",
"Gs2Inventory:VerifyBigItemByUserId",
"Gs2Limit:VerifyCounterByUserId",
"Gs2Matchmaking:VerifyIncludeParticipantByUserId",
"Gs2Mission:VerifyCompleteByUserId",
"Gs2Mission:VerifyCounterValueByUserId",
"Gs2Ranking2:VerifyGlobalRankingScoreByUserId",
"Gs2Ranking2:VerifyClusterRankingScoreByUserId",
"Gs2Ranking2:VerifySubscribeRankingScoreByUserId",
"Gs2Schedule:VerifyTriggerByUserId",
"Gs2Schedule:VerifyEventByUserId",
"Gs2SerialKey:VerifyCodeByUserId",
"Gs2Stamina:VerifyStaminaValueByUserId",
"Gs2Stamina:VerifyStaminaMaxValueByUserId",
"Gs2Stamina:VerifyStaminaRecoverIntervalMinutesByUserId",
"Gs2Stamina:VerifyStaminaRecoverValueByUserId",
"Gs2Stamina:VerifyStaminaOverflowValueByUserId",
}
검증 액션에서 실행할 액션의 종류
verifyRequeststring
~ 524288자액션 실행 시 사용되는 요청의 JSON 문자열
statusCodeint0 ~ 999상태 코드
verifyResultstring~ 1048576자결과 내용

EzConsumeActionResult

소비 액션 실행 결과

타입활성화 조건필수기본값값 제한설명
action문자열 열거형
enum {
"Gs2AdReward:ConsumePointByUserId",
"Gs2Dictionary:DeleteEntriesByUserId",
"Gs2Enhance:DeleteProgressByUserId",
"Gs2Exchange:DeleteAwaitByUserId",
"Gs2Experience:SubExperienceByUserId",
"Gs2Experience:SubRankCapByUserId",
"Gs2Formation:SubMoldCapacityByUserId",
"Gs2Grade:SubGradeByUserId",
"Gs2Guild:DecreaseMaximumCurrentMaximumMemberCountByGuildName",
"Gs2Idle:DecreaseMaximumIdleMinutesByUserId",
"Gs2Inbox:OpenMessageByUserId",
"Gs2Inbox:DeleteMessageByUserId",
"Gs2Inventory:ConsumeItemSetByUserId",
"Gs2Inventory:ConsumeSimpleItemsByUserId",
"Gs2Inventory:ConsumeBigItemByUserId",
"Gs2JobQueue:DeleteJobByUserId",
"Gs2Limit:CountUpByUserId",
"Gs2LoginReward:MarkReceivedByUserId",
"Gs2Mission:ReceiveByUserId",
"Gs2Mission:BatchReceiveByUserId",
"Gs2Mission:DecreaseCounterByUserId",
"Gs2Mission:ResetCounterByUserId",
"Gs2Money:WithdrawByUserId",
"Gs2Money:RecordReceipt",
"Gs2Money2:WithdrawByUserId",
"Gs2Money2:VerifyReceiptByUserId",
"Gs2Quest:DeleteProgressByUserId",
"Gs2Ranking2:CreateGlobalRankingReceivedRewardByUserId",
"Gs2Ranking2:CreateClusterRankingReceivedRewardByUserId",
"Gs2Schedule:DeleteTriggerByUserId",
"Gs2SerialKey:UseByUserId",
"Gs2Showcase:IncrementPurchaseCountByUserId",
"Gs2SkillTree:MarkRestrainByUserId",
"Gs2Stamina:DecreaseMaxValueByUserId",
"Gs2Stamina:ConsumeStaminaByUserId",
}
소비 액션에서 실행할 액션의 종류
consumeRequeststring
~ 524288자액션 실행 시 사용되는 요청의 JSON 문자열
statusCodeint0 ~ 999상태 코드
consumeResultstring~ 1048576자결과 내용

EzAcquireActionResult

획득 액션 실행 결과

타입활성화 조건필수기본값값 제한설명
action문자열 열거형
enum {
"Gs2AdReward:AcquirePointByUserId",
"Gs2Dictionary:AddEntriesByUserId",
"Gs2Enchant:ReDrawBalanceParameterStatusByUserId",
"Gs2Enchant:SetBalanceParameterStatusByUserId",
"Gs2Enchant:ReDrawRarityParameterStatusByUserId",
"Gs2Enchant:AddRarityParameterStatusByUserId",
"Gs2Enchant:SetRarityParameterStatusByUserId",
"Gs2Enhance:DirectEnhanceByUserId",
"Gs2Enhance:UnleashByUserId",
"Gs2Enhance:CreateProgressByUserId",
"Gs2Exchange:ExchangeByUserId",
"Gs2Exchange:IncrementalExchangeByUserId",
"Gs2Exchange:CreateAwaitByUserId",
"Gs2Exchange:AcquireForceByUserId",
"Gs2Exchange:SkipByUserId",
"Gs2Experience:AddExperienceByUserId",
"Gs2Experience:SetExperienceByUserId",
"Gs2Experience:AddRankCapByUserId",
"Gs2Experience:SetRankCapByUserId",
"Gs2Experience:MultiplyAcquireActionsByUserId",
"Gs2Formation:AddMoldCapacityByUserId",
"Gs2Formation:SetMoldCapacityByUserId",
"Gs2Formation:AcquireActionsToFormProperties",
"Gs2Formation:SetFormByUserId",
"Gs2Formation:AcquireActionsToPropertyFormProperties",
"Gs2Friend:UpdateProfileByUserId",
"Gs2Grade:AddGradeByUserId",
"Gs2Grade:ApplyRankCapByUserId",
"Gs2Grade:MultiplyAcquireActionsByUserId",
"Gs2Guild:IncreaseMaximumCurrentMaximumMemberCountByGuildName",
"Gs2Guild:SetMaximumCurrentMaximumMemberCountByGuildName",
"Gs2Idle:IncreaseMaximumIdleMinutesByUserId",
"Gs2Idle:SetMaximumIdleMinutesByUserId",
"Gs2Idle:ReceiveByUserId",
"Gs2Inbox:SendMessageByUserId",
"Gs2Inventory:AddCapacityByUserId",
"Gs2Inventory:SetCapacityByUserId",
"Gs2Inventory:AcquireItemSetByUserId",
"Gs2Inventory:AcquireItemSetWithGradeByUserId",
"Gs2Inventory:AddReferenceOfByUserId",
"Gs2Inventory:DeleteReferenceOfByUserId",
"Gs2Inventory:AcquireSimpleItemsByUserId",
"Gs2Inventory:SetSimpleItemsByUserId",
"Gs2Inventory:AcquireBigItemByUserId",
"Gs2Inventory:SetBigItemByUserId",
"Gs2JobQueue:PushByUserId",
"Gs2Limit:CountDownByUserId",
"Gs2Limit:DeleteCounterByUserId",
"Gs2LoginReward:DeleteReceiveStatusByUserId",
"Gs2LoginReward:UnmarkReceivedByUserId",
"Gs2Lottery:DrawByUserId",
"Gs2Lottery:ResetBoxByUserId",
"Gs2Mission:RevertReceiveByUserId",
"Gs2Mission:IncreaseCounterByUserId",
"Gs2Mission:SetCounterByUserId",
"Gs2Money:DepositByUserId",
"Gs2Money:RevertRecordReceipt",
"Gs2Money2:DepositByUserId",
"Gs2Quest:CreateProgressByUserId",
"Gs2Schedule:TriggerByUserId",
"Gs2Schedule:ExtendTriggerByUserId",
"Gs2Script:InvokeScript",
"Gs2SerialKey:RevertUseByUserId",
"Gs2SerialKey:IssueOnce",
"Gs2Showcase:DecrementPurchaseCountByUserId",
"Gs2Showcase:ForceReDrawByUserId",
"Gs2SkillTree:MarkReleaseByUserId",
"Gs2Stamina:RecoverStaminaByUserId",
"Gs2Stamina:RaiseMaxValueByUserId",
"Gs2Stamina:SetMaxValueByUserId",
"Gs2Stamina:SetRecoverIntervalByUserId",
"Gs2Stamina:SetRecoverValueByUserId",
"Gs2StateMachine:StartStateMachineByUserId",
}
입수 액션에서 실행할 액션의 종류
acquireRequeststring
~ 524288자액션 실행 시 사용되는 요청의 JSON 문자열
statusCodeint0 ~ 999상태 코드
acquireResultstring~ 1048576자결과 내용

EzTransactionResult

트랜잭션 실행 결과

서버 사이드에서 트랜잭션 자동 실행 기능을 이용하여 실행된 트랜잭션의 실행 결과

타입활성화 조건필수기본값값 제한설명
transactionIdstring
36 ~ 36자트랜잭션 ID
verifyResultsList<EzVerifyActionResult>0 ~ 10 items검증 액션의 실행 결과 목록
consumeResultsList<EzConsumeActionResult>[]0 ~ 10 items소비 액션의 실행 결과 목록
acquireResultsList<EzAcquireActionResult>[]0 ~ 100 items획득 액션 실행 결과 리스트

메서드

emit

이벤트를 전송하여 상태 전이를 트리거한다

스테이트 머신에 이름이 지정된 이벤트를 전송하여, 정의된 전이 규칙에 따라 현재 상태에서 다음 상태로 이동시킵니다.

게임 클라이언트가 스테이트 머신을 진행시키는 주된 방법입니다. 예를 들면:

  • 플레이어가 퀘스트 다이얼로그에서 “수락"을 탭 → “accept” 이벤트 전송 → 상태가 “제시 중"에서 “진행 중"으로 변경
  • 플레이어가 보스를 물리침 → “boss_defeated” 이벤트 전송 → 상태가 “보스 스테이지"에서 “완료"로 변경
  • 플레이어가 스토리에서 선택지를 선택 → “choose_path_a” 이벤트 전송 → 선택한 분기 경로의 상태로 전이

추가 데이터를 JSON 인수(args)로 전달할 수 있습니다. 예를 들어 “submit_answer” 이벤트를 전송할 때 {“answer”: “B”}를 args에 포함할 수 있습니다.

스테이트 머신은 현재 상태에서 유효한 이벤트만 받아들입니다. 현재 상태에서 정의되지 않은 이벤트를 전송하면 오류가 반환되며 전이는 발생하지 않습니다.

Request

타입활성화 조건필수기본값값 제한설명
namespaceNamestring
~ 128자네임스페이스 이름
네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.
gameSessionGameSession
GameSession
statusNamestring
~ 36자상태 이름
eventNamestring
~ 36자이벤트 이름
argsstring“{}”~ 4096자스테이트 머신에 전달할 인자

Result

타입설명
itemEzStatus스테이트 머신 상태

구현 예제

    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );
    var result = await domain.EmitAsync(
        eventName: "event-0001",
        args: "{\"value1\": \"value1\", \"value2\": 2.0, \"value3\": 3}"
    );
    var item = await result.ModelAsync();
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );
    var future = domain.EmitFuture(
        eventName: "event-0001",
        args: "{\"value1\": \"value1\", \"value2\": 2.0, \"value3\": 3}"
    );
    yield return future;
    if (future.Error != null)
    {
        onError.Invoke(future.Error, null);
        yield break;
    }
    var future2 = future.Result.ModelFuture();
    yield return future2;
    if (future2.Error != null)
    {
        onError.Invoke(future2.Error, null);
        yield break;
    }
    var result = future2.Result;
    const auto Domain = Gs2->StateMachine->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Status(
        "status-0001" // statusName
    );
    const auto Future = Domain->Emit(
        "event-0001", // eventName
        "{\"value1\": \"value1\", \"value2\": 2.0, \"value3\": 3}" // args
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // 변경된 값 / 결과 값을 취득
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();
var domain = ez.state_machine.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "status-0001"
    )

var async_result = await domain.emit(
    "event-0001", # event_name
    "{\"value1\": \"value1\", \"value2\": 2.0, \"value3\": 3}" # args
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

exit

완료된 스테이트 머신을 정리한다

실행이 종료된 스테이트 머신 인스턴스를 삭제합니다. 스테이트 머신의 상태가 “Pass”(정상 완료) 또는 “Error”(실패)인 경우에만 호출할 수 있습니다.

스테이트 머신이 종료 상태에 도달한 후에도, Exit로 명시적으로 삭제하기 전까지는 시스템에 남아 있습니다. 이를 통해 다음과 같은 작업이 가능합니다:

  • 플레이어에게 완료 결과를 표시(예: “퀘스트 클리어!” 화면)
  • 최종 상태와 변수를 읽어 보상을 결정
  • 오류 상태를 처리하고 다음 동작을 결정

일반적인 흐름:

  1. 스테이트 머신이 종료 상태에 도달 → 상태가 “Pass"가 됨
  2. 게임이 최종 상태를 읽고 플레이어에게 보상을 부여
  3. 게임이 완료 화면을 표시
  4. 플레이어가 화면을 닫음 → 게임이 Exit를 호출하여 정리

Running 상태의 스테이트 머신에는 Exit를 호출할 수 없습니다. 실행 중인 스테이트 머신을 강제로 정지해야 하는 경우에는 서버 측 작업이 필요합니다.

Request

타입활성화 조건필수기본값값 제한설명
namespaceNamestring
~ 128자네임스페이스 이름
네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.
gameSessionGameSession
GameSession
statusNamestring
~ 36자상태 이름

Result

타입설명
itemEzStatus종료된 스테이트 머신 상태

구현 예제

    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );
    var result = await domain.ExitAsync(
    );
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );
    var future = domain.ExitFuture(
    );
    yield return future;
    if (future.Error != null)
    {
        onError.Invoke(future.Error, null);
        yield break;
    }
    const auto Domain = Gs2->StateMachine->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Status(
        "status-0001" // statusName
    );
    const auto Future = Domain->Exit(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }
    const auto Result = Future->GetTask().Result();
var domain = ez.state_machine.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "status-0001"
    )

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

var result = async_result.result

getStatus

특정 스테이트 머신 인스턴스의 현재 상태를 취득한다

특정 스테이트 머신 인스턴스의 상세 정보를 취득합니다. 현재 어떤 상태에 있는지, 저장된 변수 등이 포함됩니다.

워크플로의 현재 진행 상황을 플레이어에게 표시할 때 사용합니다. 예를 들면:

  • 퀘스트 트래커에서 “현재 단계: 몬스터 3마리 처치(2/3)” 표시
  • 튜토리얼 인디케이터로 플레이어가 어느 단계에 있는지 표시
  • 프로세스 상태가 진행 중인지, 완료되었는지, 오류가 발생했는지 표시

응답에는 다음이 포함됩니다:

  • 현재 상태 이름: 머신이 지금 어떤 상태에 있는지
  • 변수: 스테이트 머신에 저장된 데이터(예: 진행 카운터, 선택 결과)
  • 상태: 머신이 Running(동작 중), Pass(완료), Error(오류) 중 어느 것인지
  • 스택 트레이스: 상태 전이 이력(디버깅에 유용)

Request

타입활성화 조건필수기본값값 제한설명
namespaceNamestring
~ 128자네임스페이스 이름
네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.
gameSessionGameSession
GameSession
statusNamestring
~ 36자상태 이름

Result

타입설명
itemEzStatus스테이트 머신 상태

구현 예제

    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );
    var item = await domain.ModelAsync();
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );
    var future = domain.ModelFuture();
    yield return future;
    var item = future.Result;
    const auto Domain = Gs2->StateMachine->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Status(
        "status-0001" // statusName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }
var domain = ez.state_machine.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "status-0001"
    )

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

var result = async_result.result
값 변경 이벤트 핸들링
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );

    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );

    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);
    const auto Domain = Gs2->StateMachine->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Status(
        "status-0001" // statusName
    );

    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->Subscribe(
        [](TSharedPtr<Gs2::StateMachine::Model::FStatus> value) {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    Domain->Unsubscribe(CallbackId);
var domain = ez.state_machine.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "status-0001"
    )

# 이벤트 핸들링 시작
var callback_id = domain.subscribe_model(func(value):
    # 값이 변화했을 때 호출됨
    # value에는 변경 후의 값이 전달됩니다
    pass
)

# 이벤트 핸들링 정지
domain.unsubscribe_model(callback_id)

listStatuses

플레이어의 스테이트 머신 인스턴스 목록을 취득한다

현재 플레이어에게 속한 모든 스테이트 머신 인스턴스를 취득합니다.
스테이트 머신은 서버에서 관리되는 워크플로로, 플레이어가 일련의 단계(상태)를 진행하는 과정을 추적합니다. 각 단계에서는 액션 실행, 플레이어 입력 대기, 조건에 따른 분기가 가능합니다.

스테이트 머신의 주요 사용 예:

  • 퀘스트 진행: “퀘스트 수락” → “진행 중” → “보스전” → “완료” → “보상 수령”
  • 튜토리얼 흐름: “환영” → “이동 튜토리얼” → “전투 튜토리얼” → “가챠 튜토리얼” → “완료”
  • 기간 한정 이벤트: 제한 시간이 있는 여러 단계로 구성된 이벤트 프로세스

각 스테이트 머신 인스턴스는 다음 세 가지 상태 중 하나를 가집니다:

  • Running: 스테이트 머신이 동작 중이며 다음 이벤트를 대기하고 있는 상태
  • Pass: 스테이트 머신이 정상적으로 완료된 상태(종료 상태에 도달)
  • Error: 스테이트 머신에서 오류가 발생한 상태

상태로 필터링할 수 있습니다. 예를 들어 퀘스트 화면에 활성(Running) 상태의 스테이트 머신만 표시하거나, 정리가 필요한 완료된 것을 찾을 때 사용할 수 있습니다.

Request

타입활성화 조건필수기본값값 제한설명
namespaceNamestring
~ 128자네임스페이스 이름
네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.
gameSessionGameSession
GameSession
status문자열 열거형
enum {
  “Running”,
  “Wait”,
  “Pass”,
  “Error”
}
스테이터스
이 스테이트 머신 인스턴스의 현재 실행 상태입니다. “Running"은 머신이 전이를 처리 중임을 의미합니다. “Wait"는 머신이 외부 이벤트(emit)를 대기 중임을 의미합니다. “Pass"는 정상적으로 완료되었음을 의미합니다. “Error"는 에러로 종료되었음을 의미합니다.
정의설명
Running실행 중
Wait대기 중
Pass정상 종료
Error이상 종료
pageTokenstring~ 1024자데이터 취득을 시작할 위치를 지정하는 토큰
limitint301 ~ 1000조회한 데이터 건수

Result

타입설명
itemsList<EzStatus>스테이트 머신 상태 목록
nextPageTokenstring목록의 나머지를 취득하기 위한 페이지 토큰

구현 예제

    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );
    var items = await domain.StatusesAsync(
        status: "Running"
    ).ToListAsync();
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );
    var it = domain.Statuses(
        status: "Running"
    );
    List<EzStatus> items = new List<EzStatus>();
    while (it.HasNext())
    {
        yield return it.Next();
        if (it.Error != null)
        {
            onError.Invoke(it.Error, null);
            break;
        }
        if (it.Current != null)
        {
            items.Add(it.Current);
        }
        else
        {
            break;
        }
    }
    const auto Domain = Gs2->StateMachine->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    );
    const auto It = Domain->Statuses(
        "Running" // status
    );
    TArray<Gs2::UE5::StateMachine::Model::FEzStatusPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
값 변경 이벤트 핸들링
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );

    // 이벤트 핸들링 시작
    var callbackId = domain.SubscribeStatuses(
        () => {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    domain.UnsubscribeStatuses(callbackId);
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );

    // 이벤트 핸들링 시작
    var callbackId = domain.SubscribeStatuses(
        () => {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    domain.UnsubscribeStatuses(callbackId);
    const auto Domain = Gs2->StateMachine->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    );

    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->SubscribeStatuses(
        []() {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    Domain->UnsubscribeStatuses(CallbackId);

report

클라이언트 측 스테이트 머신 실행 결과를 서버로 전송하여 검증받는다

게임 클라이언트에서 로컬로 처리된 이벤트의 배치를 서버로 전송하여 검증을 받습니다.
이는 “추측 실행(speculative execution)“이라 불리는 최적화 기능입니다. 모든 이벤트마다 Emit을 호출하는(매번 네트워크 지연이 발생하는) 대신, 클라이언트가 로컬에서 스테이트 머신을 실행하고 여러 이벤트를 한꺼번에 처리한 뒤, 그 결과를 한 번의 호출로 서버에 전송합니다.

추측 실행의 작동 방식:

  1. 클라이언트가 스테이트 머신 정의의 로컬 사본을 보유합니다
  2. 이벤트가 연속으로 발생하는 경우(예: 게임플레이 중), 클라이언트는 서버의 응답을 기다리지 않고 로컬에서 처리합니다
  3. 이벤트를 배치 처리한 후, 클라이언트가 Report를 호출하여 모든 이벤트를 서버로 전송합니다
  4. 서버는 이벤트를 재생(replay)하고 최종 상태가 클라이언트가 보고한 내용과 일치하는지 검증합니다
  5. 상태가 일치하면 서버가 결과를 승인합니다. 일치하지 않는 경우(예: 클라이언트가 변조된 경우) StateMismatch 오류가 반환됩니다

다음과 같은 성능이 중요한 시나리오에서 유용합니다:

  • 서버 응답을 기다리면 지연이 발생하는 빠른 템포의 게임플레이
  • 플레이어가 일시적으로 접속이 끊길 수 있는 오프라인 지원 흐름
  • 다수의 빠른 상태 전이를 배치 처리하는 경우

주의: 이 API를 사용하려면 네임스페이스에서 추측 실행이 활성화되어 있어야 합니다.

Request

타입활성화 조건필수기본값값 제한설명
namespaceNamestring
~ 128자네임스페이스 이름
네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.
gameSessionGameSession
GameSession
statusNamestring
~ 36자상태 이름
eventsList<EzEvent>0 ~ 1000 items이벤트 목록

Result

타입설명
itemEzStatus스테이트 머신 상태

Error

이 API에는 특별한 예외가 정의되어 있습니다.
GS2-SDK for Game Engine에서는 게임 내에서 핸들링이 필요할 것으로 예상되는 에러를, 일반적인 예외에서 파생된 특수화된 예외로 제공하여 다루기 쉽게 하고 있습니다.
일반적인 에러의 종류와 핸들링 방법은 여기 문서를 참고해 주세요.

타입베이스 클래스설명
StateMismatchExceptionBadRequestException리포트 검증 결과 상태가 일치하지 않습니다

구현 예제

try {
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );
    var result = await domain.ReportAsync(
        events: new List<Gs2.Unity.Gs2StateMachine.Model.EzEvent> {
            new Gs2.Unity.Gs2StateMachine.Model.EzEvent() {
                EventType = "emit",
                EmitEvent =
                new Gs2.Unity.Gs2StateMachine.Model.EzEvent() {
                    Event = "message",
                    Parameters = "{\"payload\": \"Hello World\"}",
                    Timestamp = 1000,
                },
            },
        }
    );
    var item = await result.ModelAsync();
} catch(Gs2.Gs2StateMachine.Exception.StateMismatchException e) {
    // State of the verification result of the report is inconsistent.
}
    var domain = gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: "status-0001"
    );
    var future = domain.ReportFuture(
        events: new List<Gs2.Unity.Gs2StateMachine.Model.EzEvent> {
            new Gs2.Unity.Gs2StateMachine.Model.EzEvent() {
                EventType = "emit",
                EmitEvent =
                new Gs2.Unity.Gs2StateMachine.Model.EzEvent() {
                    Event = "message",
                    Parameters = "{\"payload\": \"Hello World\"}",
                    Timestamp = 1000,
                },
            },
        }
    );
    yield return future;
    if (future.Error != null)
    {
        if (future.Error is Gs2.Gs2StateMachine.Exception.StateMismatchException)
        {
            // State of the verification result of the report is inconsistent.
        }
        onError.Invoke(future.Error, null);
        yield break;
    }
    var future2 = future.Result.ModelFuture();
    yield return future2;
    if (future2.Error != null)
    {
        onError.Invoke(future2.Error, null);
        yield break;
    }
    var result = future2.Result;
    const auto Domain = Gs2->StateMachine->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Status(
        "status-0001" // statusName
    );
    const auto Future = Domain->Report(
        []
        {
            auto v = MakeShared<TArray<TSharedPtr<Gs2::UE5::StateMachine::Model::FEzEvent>>>();
            v->Add(
                MakeShared<Gs2::UE5::StateMachine::Model::FEzEvent>()
                ->WithEventType(TOptional<FString>("emit"))
                ->WithEmitEvent(MakeShared<Gs2::UE5::StateMachine::Model::FEzEmitEvent>()
                    ->WithEvent(TOptional<FString>("message"))
                    ->WithParameters(TOptional<FString>("{\"payload\": \"Hello World\"}"))
                    ->WithTimestamp(TOptional<int32>(1000))
                );
            );
            return v;
        }() // events
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        auto e = Future->GetTask().Error();
        if (e->IsChildOf(Gs2::StateMachine::Error::FStateMismatchError::Class))
        {
            // State of the verification result of the report is inconsistent.
        }
        return false;
    }

    // 변경된 값 / 결과 값을 취득
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();
var domain = ez.state_machine.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "status-0001"
    )

var async_result = await domain.report(
    [
        Gs2StateMachineEzEvent.new()
            .with_event_type("emit")
            .with_emit_event(
            Gs2StateMachineEzEmitEvent.new()
                .with_event("message")
                .with_parameters("{\"payload\": \"Hello World\"}")
                .with_timestamp(1000)
            ),
    ] # events
)
if async_result.error != null:
    if async_result.error is Gs2StateMachineStateMismatchException:
        # 리포트 검증 결과 상태가 일치하지 않습니다
        pass
    push_error(str(async_result.error))
    return

var result = async_result.result