はじめましてこんにちは! ru_shalm です。
鳥小屋.txt というブログでRGSS3素材とかMVプラグインとか公開してます。よろしくね!(露骨な宣伝)
どういうわけか?
- 「RPGツクールMV」はJavaScriptでいろいろいじれる!
- 前作「RPGツクールVX Ace」はRubyでいろいろいじれる!
- じゃあMVもRubyでいじるぞ!
Opal: Ruby to JavaScript Compiler
OpalはRubyからJavaScriptコードを吐き出すAltJSです。
CoffeeScriptみたいなRuby(っぽいもの)から普通のJSコードを吐き出すという感じではなく、JSの中にRubyっぽい世界をつくるようなイメージ。
これが
class Rutan
def say
'٩(๑❛ᴗ❛๑)۶'
end
end
puts Rutan.new.say
こうなります。
(function(Opal) {
Opal.dynamic_require_severity = "error";
var self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice, $klass = Opal.klass;
Opal.add_stubs(['$puts', '$say', '$new']);
(function($base, $super) {
function $Rutan(){};
var self = $Rutan = $klass($base, $super, 'Rutan', $Rutan);
var def = self.$$proto, $scope = self.$$scope;
return (def.$say = function() {
var self = this;
return "٩(๑❛ᴗ❛๑)۶";
}, nil) && 'say'
})(self, null);
return self.$puts($scope.get('Rutan').$new().$say());
})(Opal);
わお、禁忌の魔法っぽさある。
また、Opalはブラウザ上で動くパーサが提供されているので、Rubyをそのままブラウザ上で食わせられます。MVだったらindex.htmlでこんな感じかな?
<script type="text/javascript" src="js/libs/opal.min.js"></script>
<script type="text/javascript" src="js/libs/native.min.js"></script>
<script type="text/javascript" src="js/libs/opal-parser.min.js"></script>
<script type="text/ruby" src="ruby/test.rb"></script>
<script type="text/javascript">Opal.load('opal-parser')</script>
JavaScriptからRuby(Opal)の世界を呼ぶ
先ほどのRubyで書いたクラスをJavaScriptの世界から呼ぶには以下のように呼び出します。
var rutan = Opal.Rutan.$new();
rutan.$say(); // => '٩(๑❛ᴗ❛๑)۶'
JS側からRubyのメソッドを呼ぶ際は先頭に $
を付ける必要があります。
JSとRubyの同名メソッドが混ざるの防止でOpalが自動的に付与している感じかな。
とはいえこの仕様、今の僕らには少し困ります……
なぜならば僕らが作りたいのはRPGツクールMVのプラグインだからです。
ランタイムのJavaScriptコードには $
なんて付いてません。
これじゃプラグインを呼び出してもらえない!
Opalの native を使う
Opalには native
のいろんな機能を扱うための標準ライブラリが付いています。
ネイティブといってもC++とかじゃなくてJavaScriptのことです。
require 'native' # JSの世界にぶち抜くOpalの標準ライブラリ
class Class
native_alias :new, :new # JSでの名前, Rubyでの名前
end
class Rutan
def say
'٩(๑❛ᴗ❛๑)۶'
end
native_alias :say, :say # JSでの名前, Rubyでの名前
end
var rutan = Opal.Rutan.new();
rutan.say(); // => '٩(๑❛ᴗ❛๑)۶'
native_alias
はRubyのメソッドを $
無しでJavaScriptの世界に降臨させる機能です。
ちなみに逆方向、つまりJavaScriptの世界からRubyの世界に顕現させる alias_native
もあります。間違えそう。
また、JavaScriptを直接書くのもOKです。
ラッパーとか書くときはこんな感じですね。
class Rutan
def say
`console.log('٩(๑❛ᴗ❛๑)۶');`
end
end
さて、最低限の武器を持ったところで、早速プラグインを作ってみました!
Rubyで書かれたRPGツクールMVプラグインです
タイトル画面のコマンドウィンドウをいじるプラグインを作りました。
- 位置を右下にする
- 幅を何となくデフォルトの2倍にする
require 'native'
# JSのクラスをRubyの世界で継承する(!?)
class Window_TitleCommand < `Window_TitleCommand`
# 位置の設定
def updatePlacement
@x = `Graphics.boxWidth` - @_width - 32
@y = `Graphics.boxHeight` - @_height - 32
end
# RubyのメソッドをJSの世界に持ち込む
native_alias :updatePlacement, :updatePlacement
# JSの世界のwindowWidthをRubyの世界に持ち込む
# 持ち込むついでにメソッド名を変えてエイリアスしちゃうよ
alias_native :original_windowWidth, :windowWidth
def windowWidth
original_windowWidth * 2
end
# RubyのメソッドをJSの世界に持ち込む
native_alias :windowWidth, :windowWidth
end
アアアアア、闇魔法炸裂ってかんじですね。。。
メモ1: 継承
class Window_TitleCommand < `Window_TitleCommand`
OpalはJavaScriptの function
を継承の親に指定すると、ブリッジをつくるようになっているため、こういう不思議な継承を書くことができます。
継承というよりはJSの世界の Window_TitleCommand を Rubyの世界観で塗りつぶしている感じでしょうか……この辺りの挙動、頑張ってOpalのコード読んでたんですけどまだ理解できてません><;
メモ2: プロパティ
def updatePlacement
@x = `Graphics.boxWidth` - @_width - 32
@y = `Graphics.boxHeight` - @_height - 32
end
JavaScriptの Object.defineProperty
で定義されたプロパティに @プロパティ名
でアクセスできます。
注意する必要がある…というか不思議挙動ですが、どうも最初に一度setterに nil
を渡している気がします。そのため、 width
のようなsetter内でnullチェックせずにいろいろ処理を行っているプロパティを呼びだそうとすると死に至ることがあります……謎い。
まとめ
- プラグインだけOpalにする!というのはあまり現実的では無さそう
- 「JSを呼ぶ」はいいけど、「JSに呼ばれる」はあまり得意ではないのかな……という印象
- ランタイムのコードを全部Opalに移し替えたらRGSS4的な何かにはなりそう
ちゃんとOpalと向き合ったのは今回が初めてだったので楽しかったです!
おまけ
ニコナレにスライド版も投稿しました。よろしくです。