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


# 8. 通信エラーとボタンの連打に対応する

作ったショップで、連打やコイン不足、通信エラーが起きたときの動きを確かめます。



作ったショップで、購入ボタンを続けて押したり、通信を切ったりしてみましょう。これまで使ってきたプレイヤーで進めます。

## 処理中にもう一度押してみる

1. コインを 20 枚以上用意します。足りない場合は冒険をクリアして集めます。
2. **20 コインでスタミナを 10 回復** を押し、すぐにもう一度押してみます。
3. 処理中はボタンが無効になり、次の購入を始められないことを確かめます。

コードでは、操作を始めるときに `busy` を `true` にし、`finally` で `false` に戻しています。ボタンの表示だけでなく、Studio の `Run()`、SDK の `RunWrite()` でも処理中の呼び出しを止めています。

購入が完了してボタンが有効になった後に押すと、それは新しい購入になります。

## コインが足りないときの表示を確かめる

1. 購入を繰り返し、コイン残高を 20 枚未満にします。
2. もう一度購入ボタンを押し、コイン不足のメッセージが表示されることを確かめます。
3. 失敗した購入でコインが減らず、スタミナも回復していないことを確認します。自然回復で増えた分は除いて比べてください。
4. 森の探索を始め、冒険の操作は続けられることを確かめます。確認できたらクリアします。

コインやスタミナの不足は、理由を表示して次の操作を受け付けます。

## 同じ購入を再試行できるようにする

通信が切れたとき、サーバーでは購入が終わっていて、返事だけが届いていないことがあります。そのまま新しい購入を送ると、もう一度コインを使ってしまいます。

そこで、1回の購入に一つのキーを付けます。`duplicationAvoider` に同じキーを渡して同じ内容を送り直すと、GS2 は完了済みの購入を繰り返さず、その結果を返します。

Unity の再生を止め、使っているコースのファイルで、同名のファイルを置き換えてください。Inspector の参照はそのまま使います。


**Studio**

<a href="/samples/adventure/08/AdventureStudioView.cs" download>AdventureStudioView.cs</a> をダウンロードします。

購入を行う `Buy()` は、次のようになります。`purchaseId` はクラスのフィールドです。

```csharp
private string purchaseId;

private async Task Buy()
{
    purchaseId ??= Guid.NewGuid().ToString();
    var request = Gs2ClientHolder.Instance.Gs2.Exchange.Namespace("EnergyProduct")
        .Me(Gs2GameSessionHolder.Instance.GameSession).Exchange();
    var transaction = await request.ExchangeAsync(
        "refill", 1, speculativeExecute: false, duplicationAvoider: purchaseId);
    if (transaction != null) await transaction.WaitAsync(all: true);
    purchaseId = null;
}
```


**SDK（Unity）**

<a href="/samples/adventure/08/AdventurePlay.cs" download>AdventurePlay.cs</a> をダウンロードします。`AdventurePlayer.cs` と `AdventureLogin.cs` はそのまま使います。

購入を行う `BuyRefill()` は、次のようになります。`purchaseId` はクラスのフィールドです。

```csharp
private string purchaseId;

private async UniTask BuyRefill()
{
    purchaseId ??= Guid.NewGuid().ToString();
    var transaction = await player.Gs2.Exchange.Namespace("adventure-shop").Me(player.Session)
        .Exchange().ExchangeAsync(
            "refill", 1, speculativeExecute: false, duplicationAvoider: purchaseId);
    awaitingResult = true;
    if (transaction != null) await transaction.WaitAsync(all: true);
    purchaseId = null;
}
```




`??=` は、値がないときだけ代入する書き方です。通信に失敗しても `purchaseId` が残るので、再試行には同じキーを使います。購入が完了したら空に戻し、次の購入では新しいキーを作ります。コイン不足で購入できなかったときも、エラー処理で空に戻します。

通信エラー後のボタンは **同じ購入を再試行** に変わります。結果を受け取るまでは、別の冒険や新しい購入を始めず、この購入を送り直せるようにしています。

## 通信を切ってみる

1. Unity を再生してログインします。コインを 20 枚以上用意し、スタミナを 10 以下にして、購入前の値を控えます。
2. パソコンのネットワーク接続を切り、購入ボタンを押します。
3. 通信エラーが表示され、ボタンが **同じ購入を再試行** に変わるまで待ちます。
4. ネットワーク接続を戻し、Unity を再生したまま **同じ購入を再試行** を押します。
5. コインが 20 減り、スタミナが 10 回復することを確かめます。自然回復で増えた分は除いて比べてください。完了すると、ボタンは通常の購入表示に戻ります。

次は、接続した状態で購入ボタンを押した直後に通信を切り、同じように試してみましょう。応答が先に届いて購入が完了した場合は、通常の購入ボタンに戻ります。再試行の表示になった場合だけ、接続を戻して送り直します。

このサンプルは、Unity の再生中にキーを保持します。Play を止めずに試してください。GS2 が重複回避の結果を保存する期間は1時間なので、この練習では通信を戻したらすぐに再試行します。

## 練習：状況に合うメッセージを考える

「処理中です」「コインが足りません」「通信を確認してください」など、今の状況が分かるメッセージに変えてみましょう。Studio は `Run()`、SDK は `RunWrite()` と `WriteError()` に表示文があります。

[次へ：応用編へ進む]()



