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

<div class="alert alert-info text-center"><i class="fa fa-gamepad"></i> この記事は <a href="http://qiita.com/advent-calendar/2015/rmmv">『RPGツクールMV Advent Calendar 2015』</a>5日目の記事です。</div>

はじめましてこんにちは！ ru_shalm です。
[鳥小屋.txt](http://torigoya.hatenadiary.jp) というブログでRGSS3素材とかMVプラグインとか公開してます。よろしくね！（露骨な宣伝）

## どういうわけか？
1. 「RPGツクールMV」はJavaScriptでいろいろいじれる！
2. 前作「RPGツクールVX Ace」はRubyでいろいろいじれる！
3. じゃあMVもRubyでいじるぞ！

## Opal: Ruby to JavaScript Compiler

- http://opalrb.org/

OpalはRubyからJavaScriptコードを吐き出すAltJSです。
CoffeeScriptみたいなRuby（っぽいもの）から普通のJSコードを吐き出すという感じではなく、JSの中にRubyっぽい世界をつくるようなイメージ。

これが

```rb
class Rutan
  def say
    '٩(๑❛ᴗ❛๑)۶'
  end
end

puts Rutan.new.say
```

こうなります。

```javascript
(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でこんな感じかな？

```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の世界から呼ぶには以下のように呼び出します。

```javascript
var rutan = Opal.Rutan.$new();
rutan.$say(); // => '٩(๑❛ᴗ❛๑)۶'
```

JS側からRubyのメソッドを呼ぶ際は先頭に `$` を付ける必要があります。
JSとRubyの同名メソッドが混ざるの防止でOpalが自動的に付与している感じかな。

とはいえこの仕様、今の僕らには少し困ります……
なぜならば僕らが作りたいのはRPGツクールMVのプラグインだからです。
ランタイムのJavaScriptコードには `$` なんて付いてません。
これじゃプラグインを呼び出してもらえない！

### Opalの native を使う

Opalには `native` のいろんな機能を扱うための標準ライブラリが付いています。
ネイティブといってもC++とかじゃなくてJavaScriptのことです。

```ruby
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
```

```javascript
var rutan = Opal.Rutan.new();
rutan.say(); // => '٩(๑❛ᴗ❛๑)۶'
```

`native_alias` はRubyのメソッドを `$` 無しでJavaScriptの世界に降臨させる機能です。
ちなみに逆方向、つまりJavaScriptの世界からRubyの世界に顕現させる `alias_native` もあります。<del>間違えそう。</del>

また、JavaScriptを直接書くのもOKです。
ラッパーとか書くときはこんな感じですね。

```ruby
class Rutan
  def say
    `console.log('٩(๑❛ᴗ❛๑)۶');`
  end
end
```

さて、最低限の武器を持ったところで、早速プラグインを作ってみました！

## Rubyで書かれたRPGツクールMVプラグインです
![opal_mv.jpg](https://qiita-image-store.s3.amazonaws.com/0/25109/f9e336b4-6f0e-7f6e-85ac-dc9950ac9fea.jpeg)


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

- 位置を右下にする
- 幅を何となくデフォルトの2倍にする

```ruby
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： 継承

```ruby
class Window_TitleCommand < `Window_TitleCommand`
```

OpalはJavaScriptの `function` を継承の親に指定すると、ブリッジをつくるようになっているため、こういう不思議な継承を書くことができます。
継承というよりはJSの世界の Window_TitleCommand を Rubyの世界観で塗りつぶしている感じでしょうか……<del>この辺りの挙動、頑張ってOpalのコード読んでたんですけどまだ理解できてません＞＜；</del>

### メモ2: プロパティ
```rb
  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と向き合ったのは今回が初めてだったので楽しかったです！

--------
## おまけ

<a href="http://niconare.nicovideo.jp/watch/kn800" target="_blank"><img src="https://qiita-image-store.s3.amazonaws.com/0/25109/6104d645-e664-cfa2-a478-e0ba8f2879c9.png" alt="RPGツクールMVのプラグインをRubyで書く / Ru/むっくRuさん - ニコナレ"></a>

<a href="http://niconare.nicovideo.jp/watch/kn800">ニコナレにスライド版も投稿しました。</a>よろしくです。