メインコンテンツまでスキップ

こんにちは Ruby!

シーンを作成する

まず、シーンを作成します。どんなノードであってもいいのですが、今回は Control ノードからシーンを作成します。

シーンを作成する

次に、GDScript をアタッチします。

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 でシグナルを受け取る

実行すると、出力パネル に以下が出力されたと思います。

[method_missing] Alice: [{ &"says": "Hello Ruby! ❤" }]
At: res://control.gd:14:_method_missing()

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引数 :spawnkey
  • 第2引数 { name: 'Alice', job: :wizard, level: 1 }payload

Ruby で、:spawnSymbol クラスですが、GDScript で受け取る際は StringName 型になります。

GDScript の Variant 型は任意の型を表す型です。どんなデータが渡ってきても処理する場合はこの型を使います。

型変換

perform で Ruby (mruby) のコードを実行して、 method_missing シグナル、channel シグナルで渡ってくるデータは以下のように GDScript の型に変換されてきます。

mrubyGDScript
truetrue
falsefalse
nilnull
Floatfloat
Integerint
SymbolStringName
StringString
HashDictionary
ArrayArray
RangeArray
TimeDictionary
(その他)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 シグナルを使ってみてください。