こんにちは Ruby!
シーンを作成する
まず、シーンを作成します。どんなノードであってもいいのですが、今回は Control ノードからシーンを作成します。
次に、GDScript をアタッチします。
method_missing シグナルを受け取る
GDScript に以下を書いてみます。
extends Control
@onready var res := ReDScribe.new()
func _ready() -> void:
res.method_missing.connect(_method_missing)
res.perform("""
Alice says: 'Hello Ruby! ❤'
""")
func _method_missing(method_name: String, args: Array) -> void:
print_debug('[method_missing] ', method_name, ': ', args)
その後、シーンを実行します。
実行すると、出力パネル に以下が出力されたと思います。
[method_missing] Alice: [{ &"says": "Hello Ruby! ❤" }]
At: res://control.gd:14:_method_missing()
何が起こったの?
一つずつ説明していきます。
1. extends Control
extends Control
このシーンが Control ノードを継承しているということを表しています。 この記述は ReDScribe の処理とは関係ありません。
2. @onready var res := ReDScribe.new()
@onready var res := ReDScribe.new()
ReDScribe のインスタンスを作成して、res
変数に代入しています。Godot では、ノード内で使用するメンバ変数はこのように先頭で定義します。
@onready
修飾子をつけることで、シーンが ready 状態になったとき、つまりノードとその子ノードがシーンツリーに入ったときに実行されます。メンバ変数の初期化によく使われます。
3. func _ready() -> void:
func _ready() -> void:
res.method_missing.connect(_method_missing)
res.perform("""
Alice says: 'Hello Ruby! ❤'
""")
ReDScribe の method_missing シグナルを _method_missing
関数で受け取るように設定しています。
res.perform
で Ruby のコードを実行しています。
4. func _method_missing(method_name: String, args: Array) -> void:
以下を Ruby で実行すると、Alice
メソッドに引数 says: 'Hello Ruby! ❤'
が渡されたと Ruby は解釈します。
Alice says: 'Hello Ruby! ❤'
Ruby には、Alice
というメソッドは定義されていないので、method_missing というエラーが発生しますが、ReDScribe では method_missing が発生するとシグナルを発行するようにしています。
_method_missing
関数で method_missing シグナルを受け取るようにしたので、
func _method_missing(method_name: String, args: Array) -> void:
print_debug('[method_missing] ', method_name, ': ', args)
以下のように 出力パネル に出力されたというわけです。
[method_missing] Alice: [{ &"says": "Hello Ruby! ❤" }]
アーキテクチャ
ReDScribe では、各インスタンスごとに mrb_state という mruby の実行単位を作成します。 そのため、名前空間は各インスタンスごとに閉じています。 DSL を書きたい単位で ReDScribe インスタンスを作成してください。
Object#method_missing では method_missing シグナルを発行しますが、それ以外にも Godot.emit_signal を使って channel シグナルを発行することもできます。
channel シグナルを受け取る
試しに、GDScript を以下のように書き替えてみます。
extends Control
@onready var res := ReDScribe.new()
func _ready() -> void:
res.method_missing.connect(_method_missing)
res.channel.connect(_subscribe) # Added
res.perform("""
Alice says: 'Hello Ruby! ❤'
# Added
puts "Welcome to the world of Ruby v#{RUBY_VERSION}, powered by #{RUBY_ENGINE} 💎"
# Added
Godot.emit_signal :spawn, { name: 'Alice', job: :wizard, level: 1 }
""")
func _method_missing(method_name: String, args: Array) -> void:
print_debug('[method_missing] ', method_name, ': ', args)
# Added
func _subscribe(key: StringName, payload: Variant) -> void:
print_debug('[subscribe] ', key, ': ', payload)
以下のように 出力パネル に表示されたと思います。
[method_missing] Alice: [{ &"says": "Hello Ruby! ❤" }]
At: res://control.gd:19:_method_missing()
Welcome to the world of Ruby v3.4, powered by mruby 💎
[subscribe] spawn: { &"name": "Alice", &"job": &"wizard", &"level": 1 }
At: res://control.gd:23:_subscribe()
Godot.emit_signal
を実行したことで、channel シグナルが発行されました。
Godot.emit_signal :spawn, { name: 'Alice', job: :wizard, level: 1 }
発行されたシグナルは _subscribe(key: StringName, payload: Variant)
関数で受け取りました。
- 第1引数
:spawn
がkey
- 第2引数
{ name: 'Alice', job: :wizard, level: 1 }
がpayload
Ruby で、:spawn
は Symbol クラスですが、GDScript で受け取る際は StringName 型になります。
GDScript の Variant 型は任意の型を表す型です。どんなデータが渡ってきても処理する場合はこの型を使います。
型変換
perform
で Ruby (mruby) のコードを実行して、
method_missing シグナル、channel シグナルで渡ってくるデータは以下のように GDScript の型に変換されてきます。
mruby | GDScript | |
---|---|---|
true | ⇒ | true |
false | ⇒ | false |
nil | ⇒ | null |
Float | ⇒ | float |
Integer | ⇒ | int |
Symbol | ⇒ | StringName |
String | ⇒ | String |
Hash | ⇒ | Dictionary |
Array | ⇒ | Array |
Range | ⇒ | Array |
Time | ⇒ | Dictionary |
(その他) | ⇒ | String (#inspect メソッドで文字列に変換します) |
どちらのシグナルを使うべきか?
Alice says: 'Hello Ruby! ❤'
を method_missing ではなく channel シグナルで受け取りたい場合は、例えば以下のように書きます。
extends Control
@onready var res := ReDScribe.new()
func _ready() -> void:
res.channel.connect(_subscribe)
res.perform("""
def Alice(**args)
Godot.emit_signal :action, { name: 'Alice', **args }
end
Alice says: 'Hello Ruby! ❤'
""")
func _subscribe(key: StringName, payload: Variant) -> void:
print_debug('[subscribe] ', key, ': ', payload)
[subscribe] action: { &"name": "Alice", &"says": "Hello Ruby! ❤" }
At: res://control.gd:18:_subscribe()
Alice
メソッドを定義して、Godot.emit_signal
を内部で呼ぶようにしました。
def Alice(**args)
Godot.emit_signal :action, { name: 'Alice', **args }
end
channel シグナルを使う場合は、多少の Ruby の知識が必要になってきます。
気軽に使いたい場合は method_missing シグナル、もっと凝ったことをやりたい場合は channel シグナルを使ってみてください。