GDScript でルールを書く
stateMachine のルームのルールは、C# のほかに GDScript でも書けます。Godot だけでゲームを作る場合は、C# の環境を用意しなくてもルールを書けます。
GDScript のルールは Godot では実行されません。GS2 のリアルタイムルールコンパイラ(gs2-realtime-compiler)が、C# のルールと同じ GSL と、Godot で使うクラスに変換します。そのため、できることと実行規則は C# のルールと同じです。Room API の意味、実行規則、上限値は ルーム定義リファレンスを参照してください。このページでは、GDScript での書き方と、生成・登録の手順を説明します。
ルールのスクリプトでは、SDK のスタブ(Gs2RoomRules、Gs2Record、Gs2Step、Gs2Room、Gs2RoomSync)を使います。そのため、エディタの補完と静的型チェックが効きます。
例
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)ルールの構成
| 要素 | 書き方 | 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
| 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)で指定します。
タグ
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
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 を使えます。
名前の対応
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 の書き方と名前で表示されます。
書ける構文
var x: int = .../var x := ...、constif/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") を書いてください。
生成と登録
コンパイラを用意する
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 つの設定は使いません。
コマンドラインから生成することもできます。
gs2-realtime-compiler --build path/to/rules --output path/to/buildgenerated_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 の payload に指定し、mainStateMachineName にはルールの class_name を指定します。Realtime のネームスペースの設定は ルーム定義リファレンスの生成と登録を参照してください。
GDScript から生成した GSL は、同じ内容の C# のルールから生成した GSL と、元のファイルの位置を示すコメントを除いて同じです。