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


# GDScript でルールを書く

stateMachine のルームのルールを GDScript で書き、Godot から生成・登録する方法



`stateMachine` のルームのルールは、C# のほかに GDScript でも書けます。Godot だけでゲームを作る場合は、C# の環境を用意しなくてもルールを書けます。

GDScript のルールは Godot では実行されません。GS2 のリアルタイムルールコンパイラ（`gs2-realtime-compiler`）が、C# のルールと同じ GSL と、Godot で使うクラスに変換します。そのため、できることと実行規則は C# のルールと同じです。Room API の意味、実行規則、上限値は [ルーム定義リファレンス]()を参照してください。このページでは、GDScript での書き方と、生成・登録の手順を説明します。

ルールのスクリプトでは、SDK のスタブ（`Gs2RoomRules`、`Gs2Record`、`Gs2Step`、`Gs2Room`、`Gs2RoomSync`）を使います。そのため、エディタの補完と静的型チェックが効きます。

## 例 {#example}

```gdscript
class_name TickRoom
extends Gs2RoomRules

const STATE := TickState
const RESULT := TickResult
const ERROR := TickError


class TickState extends Gs2Record:
	var steps: int


class TickResult extends Gs2Record:
	var finished: bool


enum TickError { INVALID_OPERATION = 5 }

## @room_schema(5)
enum Character { X = 0, Y = 1, LAST_TICK = 2 }

## @room_input
enum TickInput {
	## @input_range(-1, 1)
	MOVE_X = 0,
}


static func start() -> Gs2Step:
	Gs2Room.define_tick_rate(30)
	Gs2Room.define_simulated_prefab(Character)
	return Gs2Step.wait_for(on_tick, TickState.new({steps = 0}))


static func on_tick(state: TickState, tick: int) -> Gs2Step:
	for i in range(Gs2Room.object_count(Character)):
		_move(Gs2Room.object_at(Character, i), tick)
	return Gs2Step.stay(state.with({steps = state.steps + 1}))


## @predict(Character)
static func _move(id: int, step: int) -> void:
	Gs2Room.set_field(id, Character.X, Gs2RoomMath.clamp(Gs2Room.get_field(id, Character.X) + Gs2Room.input(id, TickInput.MOVE_X) * 10, -1000, 1000))
	Gs2Room.set_field(id, Character.LAST_TICK, step)
```

## ルールの構成 {#structure}

| 要素 | 書き方 | C# での対応 |
| --- | --- | --- |
| ルール | `class_name <名前>` と `extends Gs2RoomRules`。1 つのスクリプトに 1 つのルール | ルールのクラス |
| 状態・結果・エラー | `const STATE := <レコード>`、`const RESULT := <レコード>`、`const ERROR := <enum>` | `Step` の型引数 |
| レコード | `class <名前> extends Gs2Record:` に、型付きのフィールドだけを書く。`<名前>.new({field = value, ...})` で全フィールドを指定して作り、`record.with({field = value})` でコピーする | record |
| 開始 | `static func start() -> Gs2Step` | `Start` |
| ハンドラー | `on_interact(state, object_id: int, method: int)`、`on_timer(state, object_id: int, event_id: int)`、`on_tick(state, tick: int)`、`on_spawned(state, object_id: int)` | `Interact`・`Timer`・`Tick`・`Spawned` |
| ヘルパー | `static func _name(...)`。値を返すか `-> void` | private メソッド |
| 定数 | `const NAME := 3` | `const int` |

関数はすべて `static` にし、引数と戻り値に型を付けます。ハンドラーの引数名はルームが渡す名前なので変えられません（使わない引数は `_object_id` のように `_` を付けられます）。

使える型は、`int`（ルームでは int32 として計算し、範囲を超えるとその処理全体が失敗する）、`bool`、`String`、レコード、enum です。`float` は、float のフィールドに書く定数（`Gs2Room.set_float`）と、タグの引数にだけ使えます。

エラーの enum に `Error` という名前は使えません（GDScript の組み込みの `Error` と重なります）。`TickError` のように、ルールごとの名前を付けてください。

## Step {#step}

| GDScript | C# | 意味 |
| --- | --- | --- |
| `Gs2Step.wait_for(on_tick, state)` | `Step.WaitFor(nameof(Tick), state)` | 新しいタスクでハンドラーのイベントを待つ |
| `.or_wait_for(on_timer)` | `.Or(nameof(Timer))` | 同時にほかのハンドラーも待つ |
| `Gs2Step.next(_claim, state)` | `Step.Next` | 同じイベントの中で、続けて関数を実行する |
| `Gs2Step.stay(state)` | `Step.Stay(state)` | 今のタスクのまま状態を更新する |
| `Gs2Step.end(result[, state])` | `Step.End` | ルールを終える |
| `Gs2Step.ignore(TickError.MEMBER)` | `Step.Ignore` | イベントを拒否する（行った変更はすべて取り消される） |

ハンドラーは、名前の文字列ではなく関数そのもの（`on_tick`）で指定します。

## タグ {#tags}

C# の属性の代わりに、`##` のコメントにタグを 1 行に 1 つずつ書きます。知らないタグや、置く場所を間違えたタグはエラーになります。

| タグ | 書く場所 | C# での対応 |
| --- | --- | --- |
| `## @room_schema(7, standard=POSITION, position_precision=0.5, rotation_precision=...)` | enum | `[RoomSchema]`。`standard=` は `POSITION` / `ROTATION` / `TRANSFORM` |
| `## @field(int\|bool\|float, precision=0.5, default=1.5)` | スキーマのメンバー | `[RoomField]` |
| `## @room_input` | enum | `[RoomInput]` |
| `## @input_range(-1, 1)` | 入力のメンバー | `[InputRange]` |
| `## @room_action` | enum | `[RoomAction]` |
| `## @room_timer` | enum | `[RoomTimer]` |
| `## @predict(Character)` | 関数 | `[Predict]`。`static func _name(id: int[, tick: int]) -> void` |

## Gs2Room {#room-api}

C# の `Room` と同じ API を、snake_case の名前で使えます。GDScript にはジェネリクスとオーバーロードがないので、次のように書き分けます。

| 書き方 | 例 | C# |
| --- | --- | --- |
| スキーマを最初の引数に渡す | `Gs2Room.define_simulated_prefab(Character)`、`Gs2Room.spawn(Crate, x, y)` | `Room.DefineSimulatedPrefab<Character>()`、`Room.Spawn<Crate>(x, y)` |
| 数値の Prefab ID を渡すときは `_raw` | `Gs2Room.define_server_prefab_raw(30, 1991)`、`Gs2Room.spawn_raw(30, 0, 0, 0)` | `Room.DefineServerPrefab(30, ...)` |
| フィールドの読み書き | `get_field` / `set_field`、bool は `get_bool` / `set_bool`、float の定数は `set_float` | `Room.Get` / `Room.Set` |
| 標準フィールド | `get_standard` / `set_standard(id, Gs2RoomSync.StandardField.ROTATION_Y, v)` | `StandardField` |
| 2D | `spawn(S, x, y)`、`raycast_2d`、`overlap_2d` | z を省いた形 |

ほかの関数（`input` / `input_clamped`、`object_count` / `object_at`、`is_prefab` / `prefab_of` / `owner_of` / `exists` / `find_owned`、`despawn`、`after` / `cancel_timer`、`sender_connection_id` / `sender_user_id`、`define_hitbox` / `define_relevance` など）は、C# と同じ意味です。整数の計算には `Gs2RoomMath`（`clamp`・`abs`・`sign`・`sqrt`・`length2`・`length3`・`mul_div`、定数 `INT32_MIN` / `INT32_MAX`）と、`mini` / `maxi` を使えます。

## 名前の対応 {#naming}

GDScript の名前は、決まった規則で GSL の名前に写されます。規則に合わない名前はエラーになります。

| GDScript | GSL での名前 |
| --- | --- |
| `start`、`on_tick`、`_move` | `Start`、`Tick`、`Move` |
| 引数とローカル変数 `object_id`（`_object_id`） | `objectId` |
| レコードのフィールド `box_id` | `BoxId` |
| enum のメンバー `LAST_TICK` | `LastTick` |
| クラスと enum | そのまま（PascalCase） |

エラーメッセージは、GDScript の書き方と名前で表示されます。

## 書ける構文 {#syntax}

- `var x: int = ...` / `var x := ...`、`const`
- `if` / `elif` / `else`、`while`、`for i in range(...)`（終わりの値は 1 回だけ評価される）
- `match`（定数のパターンと `_`）
- `return`、`pass`、`break`、`continue`
- 算術（`+ - * / %`）、比較、`and` / `or` / `not`、`a if c else b`
- `@warning_ignore("...")`

配列、辞書（レコードの `new` / `with` の引数を除く）、添字、`self`、`await`、ラムダ、シグナル、`in` / `is` / `as`、ビット演算、`**`、float の計算は書けません。書いた場合は、位置付きのエラーになります。

整数の割り算には、GDScript の `integer_division` の警告が出ます。ルールのディレクトリでこの警告を無効にするか、文の前に `@warning_ignore("integer_division")` を書いてください。

## 生成と登録 {#deployment}

### コンパイラを用意する

SDK と同じリリースから OS に合った `gs2-realtime-compiler` を入手し（[入手の手順]()）、Godot のエディタ設定の `gs2/realtime/compiler_path` にその場所を設定します。

### 生成する

**プロジェクト > ツール > GS2 > Build Realtime Rules** を実行します。ルールが 1 つなら、次のプロジェクト設定を使います。既定の場所でよければ、設定は要りません。

| 設定 | 意味 | 既定 |
| --- | --- | --- |
| `gs2/realtime/rules_directory` | ルールのスクリプトを置く場所 | `res://rules` |
| `gs2/realtime/generated_directory` | ゲームが使うクラスの出力先 | `res://generated/realtime` |
| `gs2/realtime/package_directory` | 登録する `package.zip` の出力先 | `res://../build/realtime` |

1 つの場所に置けるルールのスクリプトは 1 つです。ルールが複数ある場合は、ルールごとに場所を分け、プロジェクト設定の `gs2/realtime/rule_sets` に `{"rules": ..., "generated": ..., "package": ...}` の組を並べます。`rule_sets` が空でなければ、上の 3 つの設定は使いません。

コマンドラインから生成することもできます。

```sh
gs2-realtime-compiler --build path/to/rules --output path/to/build
```

`generated_directory` には、次のクラスが出力されます。生成されなくなったファイルは消え、変わらないファイルの `.uid` は残るので、シーンからの参照は壊れません。

| ファイル | 使い道 |
| --- | --- |
| `<スキーマ名>.realtimeschema` | `Gs2NetworkEntity.schema` に指定する同期スキーマ |
| `<スキーマ名>_schema.gd`（`class_name <スキーマ名>Schema`） | フィールドのスロットと型付きのアクセサ |
| 入力・アクションの enum ごとのスクリプト | `world.set_input(TickInput.MOVE_X, 1)`、`door.interact(DoorAction.OPEN)` のように使う定数 |
| エラーの enum のスクリプト | 拒否の結果コードの定数 |
| `<ルール名>_prediction.gd` | 予測のコード。`<ルール名>Prediction.register(world)` で有効にする |

生成されるクラスは `class_name` を持ち、`class_name` はプロジェクト全体で共通です。ほかのルールの生成物や、プロジェクトのほかのスクリプトと名前が重なると、そのルールは出力されずにエラーになります（大文字と小文字の違いだけの名前も重なりとみなします）。たとえば 2 つのルールのどちらにも `enum PlayerState` があると、どちらも `PlayerStateSchema` を生成するので、どちらかの名前を変えてください。

ルールのスクリプトはゲームの実行には使いません。プロジェクトの中に置くとエディタの型チェックが効きますが、エクスポートのフィルターで除外してください。

### 登録する

登録の手順は C# のルールと同じです。`package.zip` の中の `main.gsl` を GS2-StateMachine の [UpdateStateMachineMaster](#updatestatemachinemaster) の `payload` に指定し、`mainStateMachineName` にはルールの `class_name` を指定します。Realtime のネームスペースの設定は [ルーム定義リファレンスの生成と登録](#deployment)を参照してください。

GDScript から生成した GSL は、同じ内容の C# のルールから生成した GSL と、元のファイルの位置を示すコメントを除いて同じです。



