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


# C# ステートマシン定義チュートリアル

Unity Editor で小さなゲームを作り、C# のルールを編集してゲーム画面から操作するチュートリアル



このページでは、**数値をボタン操作で増やし、9 になると終了するサンプル `MyGame`** を作ります。ルールは次の三つです。

- 数値 `Progress` は `0` から始まります。
- `Small` を選ぶと `1`、`Large` を選ぶと `3` 増えます。
- `Progress` が `9` になると終了します。`9` を超える操作でも、値は `9` に収めます。

手順 1〜2 でひな形を Editor のプレビューで動かし、手順 3 でルールを変更、手順 4 で Unity のシーンから操作します。手順 5 でサーバーによる検証を追加します。

## 1. ひな形を作る

GS2 SDK for Unity、GS2-CSharp-SDK、LocalStateMachineKit の C# 生成機能を含む版を導入した Unity プロジェクトで操作します。生成機能は Unity 2021.2 以降が対象です。

1. Unity のメニューから **GS2 > Local State Machine > Generate from C#** を選びます。**GS2 C# Authoring** ウィンドウが開きます。
2. **New definition** の **Name** に `MyGame` を入力します。
3. **Create and preview** を押します。
4. 生成とコンパイルが終わると、**GS2 Local Preview** ウィンドウが開きます。

**この手順では、Project の選択や Generation settings の展開・変更は不要です。** 作成先と生成先は自動で設定されます。ログインやサーバー設定もまだ必要ありません。

`MyGame` がすでに存在する場合は作り直さず、**Existing definition > Project** の **…** から `GameRules/MyGame/project.json` を選び、**Generate and preview** を押してください。

## 2. プレビューで操作する

**GS2 Local Preview** には、上側の **View** と下側の **Operations** があります。

- **View > State > Progress**：操作で増やす数値。最初は `0` です。
- **Operations**：`choice` の選択欄と **Advance** ボタンがあります。

`choice` を **Small** に変更し、**Advance** を押してください。Progress が `1` になり、`Accepted: Advance` と表示されます。ここでの **Advance はゲームを進める操作ボタン**です。

| choice で選ぶ値 | Advance を押した結果 |
| --- | --- |
| `Small` | Progress が 1 増える |
| `Large` | Progress が 3 増える。ただし 9 を超えない |
| `None` | Progress は変わらず、`Ignored: InvalidChoice (1001)` と表示される |

**Reset** を押してから `Large` を 3 回実行すると、Progress が `0 → 3 → 6 → 9` と変化します。9 になると **Result > Progress** にも `9` が表示され、Advance ボタンは押せなくなります。もう一度試すときは **Reset** を押します。

このプレビューは Unity の Play ボタンを押さずに使えます。

## 3. Small で増える量を 1 から 2 に変える

**GS2 C# Authoring** ウィンドウへ戻り、**Open rules** を押します。Finder / エクスプローラーにルールのフォルダーが表示されるので、`MyGame.cs` をテキストエディターや IDE で開きます。

ファイルは Unity プロジェクト直下の `GameRules/MyGame/Rules/MyGame.cs` にあります。`Assets` の外にあるため、Unity の Project ウィンドウには表示されません。

ひな形の内容は次のルールです（読みやすいように改行しています）。

```csharp
using System;
using Gs2.LocalStateMachine.Authoring;
using Step = Gs2.LocalStateMachine.Authoring.Step<
    GameRules.MyGameState, GameRules.MyGameResult, GameRules.MyGameError>;

namespace GameRules;

public enum MyGameError { InvalidChoice = 1001 }
public enum MyGameChoice { None, Small, Large }
public record MyGameState(int Progress);
public record MyGameResult(int Progress);

public static class MyGame
{
    public static Step Start() =>
        Step.WaitFor(nameof(Advance), new MyGameState(0));

    public static Step Advance(MyGameState state, MyGameChoice choice)
    {
        if (choice != MyGameChoice.Small && choice != MyGameChoice.Large)
            return Step.Ignore(MyGameError.InvalidChoice);

        var amount = 1;
        if (choice == MyGameChoice.Large)
            amount = 3;

        var next = state with { Progress = Math.Min(9, state.Progress + amount) };
        if (next.Progress == 9)
            return Step.End(new MyGameResult(next.Progress), next);

        return Step.WaitFor(nameof(Advance), next);
    }
}
```

ここでは、次の順番で処理しています。

1. `Start` で Progress が 0 の状態を作り、`Advance` の入力を待ちます。
2. `Advance` は現在の状態 `state` と、プレイヤーが選んだ `choice` を受け取ります。
3. `None` なら `Ignore` を返し、入力を採用しません。
4. 有効な選択なら、`state with { ... }` で更新後の状態を作ります。
5. 9 に到達したら `End` で終了し、それ以外は次の `Advance` を待ちます。

`MyGameState` は進行中の状態、`MyGameResult` は終了時に受け取る結果です。`using Step = ...` は、この二つの型と入力を無視する理由の型をまとめて指定するための略記です。

**`var amount = 1;` を `var amount = 2;` に変更し、保存してください。** Unity に戻って **GS2 C# Authoring > Generate and preview** を押します。プレビューは最初から始まり、今度は `Small` で Progress が `0 → 2` になります。

Generate and preview は、定義から **GSL** と **Session** を生成します。GSL はルールを実行するためのファイル、Session は Unity から操作を呼ぶための C# クラスです。以降も「ルールを編集して保存 → Generate and preview」で試せます。生成された GSL や Session は直接編集しません。

## 4. Unity のゲーム画面から呼ぶ

次に、同じ `MyGame` をシーン内のボタンから操作します。

ゲームのルールを書く C# と、Unity の画面を書く C# は役割が異なります。

| 書くもの | 役割 |
| --- | --- |
| ゲームの定義 | 操作を受け付ける条件、状態の変化、終了条件を書く |
| Unity の画面 | ボタンから操作を呼び、現在の状態を表示する |

ここでは Unity の画面側を書きます。

1. `Assets` 以下に **MyGamePreview.cs** を作り、次の内容を保存します。
2. シーンに空の GameObject を作り、このスクリプトを付けます。
3. Unity の **Play** ボタンを押します。
4. Game ビューの **Small** / **Large** を押し、Progress の変化を確認します。前の手順でルールを変更済みなので、Small を押すと Progress が 2 増えます。

```csharp
#if UNITY_EDITOR && !GS2_DISABLE_LOCAL_STATE_MACHINE_KIT
using System.IO;
using Generated.MyGame;
using Gs2.Unity.Gs2StateMachine.Local;
using UnityEngine;

public sealed class MyGamePreview : MonoBehaviour
{
    private MyGameSession game;

    private void Start()
    {
        var path = Path.Combine(Application.dataPath, "Generated/MyGame/machine.gsl");
        game = MyGameSession.FromExecutor(
            LocalStateMachineExecutor.CreatePreview(File.ReadAllText(path), "MyGame"));
    }

    private void OnGUI()
    {
        if (game == null) return;
        GUILayout.Label("Progress: " + game.View.State.Progress);

        using (new UnityEditor.EditorGUI.DisabledScope(!game.View.Actions.Advance))
        {
            if (GUILayout.Button("Small")) game.Advance(MyGameChoice.Small);
            if (GUILayout.Button("Large")) game.Advance(MyGameChoice.Large);
        }

        if (game.View.Result != null)
            GUILayout.Label("Finished: " + game.View.Result.Progress);
    }
}
#endif
```

`game.Advance(...)` が操作の呼び出し、`game.View.State` が表示に使う現在の状態です。定義の `Advance` にある `state` 引数は実行環境が渡すため、画面側から渡すのは `choice` だけです。

`game.View.Actions.Advance` は「今、Advance を呼べるか」を表します。この例では終了後に `false` になるため、ボタンを無効にしています。素材不足などのゲーム固有の条件がある場合は、操作関数内で検査して `Ignore` を返します。

`View.State` は読み取り専用のコピーです。状態を直接書き換える代わりに操作関数を呼んでください。変更時だけ通知を受けたい場合は、`Start` の Session 作成直後に次の行を追加できます。

```csharp
game.ViewChanged += view => Debug.Log("Progress: " + view.State.Progress);
```

ここまでで、定義の編集・再生成・ゲーム画面からの操作を試せました。この `MyGamePreview` は Editor 内で確認するためのコードです。アプリに組み込む場合は、次の接続手順へ進みます。

## 5. サーバーの検証を使う

サーバーへ接続しても、画面側で呼ぶ `game.Advance(...)` と `game.View.State` は同じです。変更するのは、Session の作成と操作履歴の送信です。

まず GS2 側を準備します。

1. GS2-StateMachine のネームスペースを用意し、投機的実行を有効にします。
2. `Assets/Generated/MyGame/machine.gsl` を読み、マスター登録の `payload` に指定します。`mainStateMachineName` は `MyGame` です。[マスター登録の説明]()を参照してください。
3. ゲームの開始処理から、対象ユーザーのステートマシンを開始します。[開始 API](#startstatemachinebyuserid)や[開始用のアクション](#gs2statemachinestartstatemachinebyuserid)を利用します。

次は、初期化・ログイン済みのアプリ内で使う **UniTask の例**です。`gs2` と `gameSession` は初期化済みの SDK 接続とログイン情報、`namespaceName` はネームスペース名、`statusName` は手順 3 で開始したステータスの名前です。

```csharp
var game = await Generated.MyGame.MyGameSession.OpenAsync(
    gs2, gameSession, namespaceName, statusName);

var outcome = game.Advance(Generated.MyGame.MyGameChoice.Small);
if (outcome.Ignored)
    Debug.Log("入力を採用しませんでした: " + outcome.Code);

await game.ConfirmAsync(gs2, gameSession);
```

`OpenAsync` は、手順 3 で開始したステータスへ接続します。UniTask を使わない場合は `OpenFuture` / `ConfirmFuture` を使用します。

`ConfirmAsync` は、それまでの操作履歴をサーバーへ送り、検証結果を待ちます。終了時だけでなく、ゲームの途中でも呼べます。サーバーは同じルールを実行して結果を照合します。ダメージ計算や報酬条件などの重要なルールも、ゲームの定義側に書いてください。

**Editor のプレビューには、サーバーへ送る履歴はありません。** プレビューを途中まで遊んでから接続するのではなく、サーバーで開始した状態を `OpenAsync` で開きます。

## 定義の仕様を調べる

- [報酬を追加する](#rewards)：SDK のリクエスト型で報酬を指定する例。
- [受け付ける操作を切り替える](#step)：`WaitFor` / `Stay` / `Next` / `End` の使い分け。
- [乱数を使う](#random)：サーバーで再現できる乱数の使い方。
- [C# 定義リファレンス]()：宣言規則、データ型、使用できる C# の範囲。
- [StateMachine の投機的実行 Session API]()：操作・状態参照・サーバー検証の API。
- [生成設定・登録]()：project.json、SDK の参照、生成物の扱い。

## 手順どおりに進めない場合

| 状況 | 確認すること |
| --- | --- |
| Create and preview ではなく Create and generate が表示される | `GS2_DISABLE_LOCAL_STATE_MACHINE_KIT` を外し、再コンパイルする。生成機能だけを使う場合は外す必要はない |
| 同じ名前で作成できない | 既存の `project.json` を選び、Generate and preview を押す |
| Open rules を押してもコード編集画面が出ない | フォルダーを開くボタンなので、その中の `MyGame.cs` をエディターで開く |
| 編集後も動きが変わらない | ルールを保存し、Generate and preview を押す。Console に生成エラーがないか確認する |
| 9 に到達した後に操作できない | ゲームが終了した状態。プレビューウィンドウなら Reset、シーンなら Play を停止して再開する |

生成に失敗した場合は、前回成功した生成物を残します。エラーがあるまま古い生成物で動作を確認しないよう、Console のエラーを解消してから再生成してください。



