> For the complete documentation index, see [llms.txt](/ko/llms.txt)


# 3. 퀘스트를 시작하고 보상 받기

퀘스트를 클리어하면 코인을 받도록 시작부터 보상 수령까지 만들어 봅니다.



목록에 표시된 **숲 탐험** 을 실제로 플레이할 수 있게 만들어 봅시다. **시작** 으로 모험을 떠나고 **클리어** 로 돌아오면 10코인을 받습니다. 몇 번을 클리어해도 보상은 같고 첫 클리어 보너스는 넣지 않습니다.

## 클리어 보상은 GS2에 설정하기

보상 수량은 GS2의 퀘스트 설정에서 정합니다. 일반 클리어 보상을 무상 코인 10개로 설정하면 Unity가 클리어를 보고할 때 GS2가 코인을 지급합니다. Unity에서는 잔액 변경에 맞춰 화면을 갱신합니다.

코인은 GS2-Money2의 **지갑** 에 저장합니다. 지갑을 구분하는 번호를 **슬롯** 이라고 하며, 이 게임에서는 `0`을 사용합니다. 이 장에서 Money2 설정을 추가하고 이후에도 같은 지갑을 읽고 씁니다.

퀘스트 완료와 코인 지급은 함께 처리해야 하는 작업입니다. 이런 일련의 처리를 **트랜잭션** 이라고 합니다. 일반 클리어 보상에 입금을 설정해 두면 Unity는 클리어만 보고하면 됩니다.

설정에서 코인을 지급하는 등의 처리를 **획득 액션** 이라고 합니다. 다음 단계에서 일반 클리어 보상에 코인 획득 액션을 추가합니다.

## 따라 하기



**Studio**

### 통화와 보상 설정하기

1. **Add features** 에서 **Currency** 를 추가합니다.

2. **Quest (Quest)** 에서 `forest`의 **Completion rewards** 셀을 더블 클릭합니다. 오른쪽의 **Add item** 을 누르고 추가된 **Not set** 항목을 엽니다.
3. **Action** 에서 `DepositByUserId`를 검색하고 `money2`의 지갑 잔액 추가 액션을 선택합니다. **Target resource** 는 **None** 으로 두고 다음 값을 입력합니다.

| 항목 | 값 |
| --- | --- |
| namespaceName | `Currency` |
| userId | `#{userId}` |
| slot | `0` |

`#{userId}`는 퀘스트를 클리어한 플레이어를 가리킵니다. 실제 플레이어 ID로 바꾸지 말고 그대로 입력합니다.

4. **depositTransactions** 의 **Add** 를 누르고 생성된 **DepositTransaction** 을 엽니다. `price`는 `0`, `currency`는 **No value** 로 두고 `count`에 `10`을 입력합니다. 무상 코인 10개를 지급하는 설정입니다.

<a href="/images/adventure/later-chapters/03-coin-reward.jpg" target="_blank" rel="noopener" aria-label="일반 클리어 보상 설정. Currency의 슬롯 0에 price 0, count 10으로 무상 코인을 지급 (새 탭에서 확대)"><img src="/images/adventure/later-chapters/03-coin-reward.jpg" alt="일반 클리어 보상 설정. Currency의 슬롯 0에 price 0, count 10으로 무상 코인을 지급" loading="lazy"></a>

첫 클리어 보상과 실패 시 액션은 0개로 둡니다. **Publish** 에서 서버 배포와 Unity 프로젝트 업데이트를 진행합니다.

### 시작·클리어·코인 표시 연결하기

Unity의 `Gs2` 오브젝트에 `ProgressHandler`와 `WalletHandler`를 추가합니다. Wallet Handler의 **Slot** 은 `0`입니다. `AdventureStudioView.cs`를 이 장의 코드로 교체하고 **Progress** 와 **Wallet** 에 각각 할당합니다. **Quests** 에는 기존 `QuestListHandler`를 사용합니다.

퀘스트 조작은 아래 `DrawQuestButtons()`에 모았습니다. 시작 버튼은 `quest.Start()`, 클리어 버튼은 `progress.Binder.Complete()`를 호출합니다. `Run()`은 처리 중 연속 클릭을 막고 오류를 표시합니다.

잔액과 로딩 상태 표시는 별도 메서드에 있습니다. 아래 링크에서 전체 코드를 다운로드하여 기존 파일을 교체합니다.

```csharp
private void DrawQuestButtons()
{
    var canOperate = !busy && !unresolved && progress.HasValue;

    GUI.enabled = canOperate && !progress.Model.InProgress;
    foreach (var quest in quests.Binders)
    {
        GUILayout.Label(quest.DisplayName);
        if (GUILayout.Button("시작: " + quest.DisplayName))
        {
            Run(() => quest.Start());
        }
    }

    GUI.enabled = canOperate && progress.Model.InProgress;
    if (GUILayout.Button("클리어"))
    {
        Run(() => progress.Binder.Complete());
    }
    GUI.enabled = true;
}
```

<a href="/samples/adventure/ko/03/AdventureStudioView.cs" download>이 장의 AdventureStudioView.cs 다운로드</a>

자신의 게임에서는 적을 쓰러뜨리거나 목표 지점에 도착했을 때 이 완료 처리를 호출합니다. 위 코드의 클리어 버튼에 연결된 `Run(() => progress.Binder.Complete())`가 그 부분입니다. GS2가 보상을 지급하므로 별도로 코인을 더하는 코드는 필요하지 않습니다.




**SDK (Unity)**

### 설정 업데이트하기

<a href="/samples/adventure/ko/03/adventure-game.yaml" download>3장 템플릿을 다운로드</a>하고 **Deploy → adventure-game → Update stack** 을 열어 **Template data** 에서 파일을 선택합니다. 이전 장의 리소스도 포함한 전체 템플릿입니다. 변경 내용을 확인하여 적용하고 **Update complete** 가 될 때까지 기다립니다.

일반 클리어 보상의 `depositTransactions`에 무상 코인 10개를 지급하도록 설정합니다.

```yaml
action: Gs2Money2:DepositByUserId
request: |-
  {
    "namespaceName": "adventure-coin",
    "userId": "#{userId}",
    "slot": 0,
    "depositTransactions": [
      { "price": 0, "currency": "", "count": 10 }
    ]
  }
```

`request`는 JSON 문자열로 전달합니다. YAML의 `|-` 아래에 JSON을 작성합니다.

`StartAsync()`로 퀘스트를 시작하고 `EndAsync()`로 클리어를 보고합니다. 각 호출이 반환하는 `transaction`의 `WaitAsync(all: true)`로 서버 처리가 끝날 때까지 기다립니다. 진행 상태와 잔액은 캐시 변경 알림으로 화면에 반영합니다.

### Unity 코드 업데이트하기

다음 두 파일을 `Assets`에 넣습니다. 같은 이름의 파일이 있으면 교체합니다.

- <a href="/samples/adventure/ko/03/AdventurePlay.cs" download>AdventurePlay.cs</a>: 퀘스트 조작과 화면 표시
- <a href="/samples/adventure/ko/03/AdventurePlayer.cs" download>AdventurePlayer.cs</a>: 표시용 데이터의 구독과 해제

기존 `AdventurePlay`와 Inspector의 **Login** 설정은 그대로 사용합니다. `AdventurePlayer`는 코드에서 생성합니다.

### 데이터 변경에 맞춰 화면 갱신하기

`AdventurePlayer`는 `SubscribeWithInitialCallAsync()`로 최초 값과 이후 변경 사항을 받습니다. 예를 들어 지갑이 갱신될 때마다 코인 잔액을 표시할 모델을 교체합니다.

```csharp
var balance = Gs2.Money2.Namespace("adventure-coin").Me(Session).Wallet(0);
var subscription = await balance.SubscribeWithInitialCallAsync(value => Wallet = value);
```

화면을 그릴 때는 `player.Wallet.Summary.Free`를 참조합니다. 진행 상태도 같은 방식으로 갱신하므로 시작이나 클리어 후 다시 불러오는 처리는 필요하지 않습니다.

반환값은 구독을 해제할 때 사용합니다. 배포 코드에서는 재생이 끝나 오브젝트가 파괴될 때 `Unsubscribe()`를 호출합니다.

```csharp
balance.Unsubscribe(subscription);
```

### 퀘스트 시작과 클리어 처리하기

버튼은 `RunWrite()`를 통해 시작과 클리어 처리를 호출합니다. `RunWrite()`는 연속 클릭을 막고 오류를 표시합니다. `awaitingResult`는 요청의 응답을 받았음을 나타내며, 그 이후의 오류와 요청 자체가 거절된 경우를 구분합니다.

이 장에서 사용하는 부분은 아래와 같습니다. 이 발췌 코드를 별도 클래스로 추가할 필요는 없습니다.

```csharp
private async UniTask BeginQuest(string questName)
{
    var transaction = await player.Gs2.Quest.Namespace("adventure-quest").Me(player.Session)
        .StartAsync("main", questName, force: false, speculativeExecute: false);
    awaitingResult = true;
    if (transaction != null) await transaction.WaitAsync(all: true);
}

private async UniTask CompleteQuest()
{
    var transaction = await player.Gs2.Quest.Namespace("adventure-quest").Me(player.Session)
        .Progress().EndAsync(isComplete: true, speculativeExecute: false);
    awaitingResult = true;
    if (transaction != null) await transaction.WaitAsync(all: true);
}
```

자신의 게임에서는 적을 쓰러뜨리거나 목표에 도착했을 때 `CompleteQuest()`를 호출합니다. 배포 코드의 클리어 버튼은 `RunWrite(CompleteQuest).Forget()`으로 호출합니다. 승리 판정에서도 `RunWrite()`를 거쳐 연속 실행 방지와 오류 표시를 사용합니다. GS2가 보상을 지급하므로 별도의 코인 추가 코드는 필요하지 않습니다.





처리 중에는 시작과 클리어 버튼을 누를 수 없게 합니다. 응답이 오기 전에 같은 처리를 중복 호출하지 않고, 성공하면 화면을 전환합니다. 오류가 반환되면 이유를 표시합니다.

## 모험을 떠나 봅시다

시작 전 코인 잔액을 기록하고 퀘스트를 시작합니다. 배포 후 보상 설정이 반영되기까지 최대 5분이 걸릴 수 있습니다. 이전 수량이 지급되면 잠시 기다렸다가 새로 시작한 퀘스트로 확인합니다.

| 조작 | 기대하는 결과 |
| ---------------------------------- | ------------------------------------------ |
| 퀘스트 시작 | 진행 중인 퀘스트가 표시됨 |
| 퀘스트 클리어 | 퀘스트가 끝나고 코인이 시작 전보다 10 증가 |
| 같은 퀘스트를 새로 시작하고 클리어 | 코인이 10 더 증가 |
| 퀘스트를 시작하지 않고 클리어 시도 | 코인은 늘어나지 않음 |

통신이 끊겨 결과를 받지 못했다면 성공이나 실패로 단정하지 말고 **결과를 확인할 수 없습니다** 라고 표시합니다. 바로 클리어 요청을 다시 보내지 마세요.

## 연습: 설정 바꿔 보기

클리어 보상을 15코인으로 바꾸고 배포해 봅시다. 그 뒤 퀘스트를 새로 시작하고 클리어하여 몇 개가 늘어나는지 확인합니다.

<details>
진행 중인 퀘스트에는 시작 시점의 정보가 저장됩니다. 변경한 보상을 확인하려면 퀘스트를 새로 시작하세요.


## 다음 장으로 넘어가기 전에

일반 클리어 보상을 **10코인으로 되돌려 배포** 합니다. 같은 계정으로 숲 탐험을 새로 시작하고 클리어하여 잔액이 10 증가하는지 확인합니다.

[다음: 다시 실행하여 이어서 플레이하기]()



