Ruby
JavaScript
Opal
RPGツクールMV

RPGツクールMVのプラグインをRubyで書く

More than 3 years have passed since last update.


この記事は 『RPGツクールMV Advent Calendar 2015』5日目の記事です。

はじめましてこんにちは! ru_shalm です。

鳥小屋.txt というブログでRGSS3素材とかMVプラグインとか公開してます。よろしくね!(露骨な宣伝)


どういうわけか?


  1. 「RPGツクールMV」はJavaScriptでいろいろいじれる!

  2. 前作「RPGツクールVX Ace」はRubyでいろいろいじれる!

  3. じゃあ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プラグインです

opal_mv.jpg

タイトル画面のコマンドウィンドウをいじるプラグインを作りました。


  • 位置を右下にする

  • 幅を何となくデフォルトの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と向き合ったのは今回が初めてだったので楽しかったです!



おまけ

RPGツクールMVのプラグインをRubyで書く / Ru/むっくRuさん - ニコナレ

ニコナレにスライド版も投稿しました。よろしくです。