Skip to content

Ruby on enchant.js : HotRubyにおける組み込みクラスの継承の実装

HotRubyを利用したRuby on enchant.jsもいよいよ佳境(?)に入って来た。
いままではとりあえずRubyからenchant.jsを呼び出せるかどうかという実験色が濃かったのに対し、今回は実際にクラス継承も含めて実装してみたぞ。

今回の追加コードは、やはりHotRuby.prototype.classesの修正がメインだ。
いままでSpriteでなんでもやっていたが、新たにSceneとEntityというクラスを追加した。

EntityはSpriteの親クラスであり、他にEntityを利用したクラスとしてLabelを用意してみた。

    "Sprite" : {
        "initialize" : function(recver, args) {
            var sprite = new Sprite(args[0],args[1]);
            recver.__instanceVars.entity = sprite;
            HotRuby.prototype.classes.extendRubyClass("Sprite","Entity");
        },
        "image" : function(recver,args) {
            return  HotRuby.prototype.classes.
                    newNativeObject("String",
                            recver.__instanceVars.entity.imageName);
        },
        "image=" : function(recver,args) {
            recver.__instanceVars.entity.imageName=args[0].__native;
            return recver.__instanceVars.entity.image=
                        game.assets[args[0].__native];
        }
    },
    "Label" : {
        "initialize" : function(recver, args) {
            var label = new Label(args[0].__native);
            recver.__instanceVars.entity = label;
            HotRuby.prototype.classes.extendRubyClass("Label","Entity");
        },
        "text" : function(recver,args) {
            return  HotRuby.prototype.classes.
                    newNativeObject("String",
                            recver.__instanceVars.entity.text);
        },
        "text=" : function(recver,args) {
            return recver.__instanceVars.entity.text=args[0].__native;
        }
    },
    "Entity" : {
        "initialize" : function(recver, args) {
            var obj = HotRuby.prototype.classes["Entity"];
            'x y scaleX scaleY rotation frame'.split(' ').forEach(function(prop) {
              obj[prop] = function(recver,args){
                    return recver.__instanceVars.entity[prop];};
              obj[prop+"="] = function(recver,args){
                    recver.__instanceVars[prop]=args[0];
                    return recver.__instanceVars.entity[prop]=args[0];};
              obj["@"+prop+"="] = function(recver,args){
                    recver.__instanceVars[prop]=args[0];
                    return recver.__instanceVars.entity[prop]=args[0];};
              obj[prop+"+="] = function(recver,args){
                    return recver.__instanceVars.entity[prop]+=args[0];};
              obj[prop+"-="] = function(recver,args){
                    return recver.__instanceVars.entity[prop]-=args[0];};
              obj[prop+"*="] = function(recver,args){
                    return recver.__instanceVars.entity[prop]*=args[0];};
              obj[prop+"/="] = function(recver,args){
                    return recver.__instanceVars.entity[prop]/=args[0];};
              obj[prop+"++"] = function(recver,args){
                    return recver.__instanceVars.entity[prop]++;};
              obj[prop+"--"] = function(recver,args){
                    return recver.__instanceVars.entity[prop]--;};
              obj[prop+"<"] = function(recver,args){
                    return recver.__instanceVars.entity[prop] < args[0] ? this.trueObj :  this.falseObj;};
              obj[prop+"<="] = function(recver,args){
                    return recver.__instanceVars.entity[prop] <= args[0] ? this.trueObj :  this.falseObj;};
              obj[prop+">"] = function(recver,args){
                    return recver.__instanceVars.entity[prop] > args[0] ? this.trueObj :  this.falseObj;};
              obj[prop+">="] = function(recver,args){
                    return recver.__instanceVars.entity[prop] >= args[0] ? this.trueObj :  this.falseObj;};
              obj[prop+"=="] = function(recver,args){
                    return recver.__instanceVars.entity[prop] == args[0] ? this.trueObj :  this.falseObj;};
            });
        },
        "setupEventListener" : function(recver,args) {
            var classObj = ruby.__proto__.classes[recver.__className];
            var hr = this;
            'enterframe touchstart touchend touchmove'.split(
            ' ').forEach(function(eventName){
                if(classObj[eventName]){
                    var func = function() {
                        var hr = arguments.callee.hr;
                        var proc = arguments.callee.proc;
                        hr.runOpcode(
                            proc.__opcode,
                            proc.__parentStackFrame.classObj,
                            proc.__parentStackFrame.methodName,
                            recver,
                            hr.nativeAryToRubyObjectAry(arguments),
                            proc.__parentStackFrame,
                            true);
                    };
                    func.hr = hr;
                    func.proc={};
                    func.proc.__parentStackFrame=ruby.topSF;
                    func.proc.__opcode= classObj[eventName];
                    recver.__instanceVars.entity.addEventListener(
                        eventName,func);
                }
            });
        },
        "addEventListener" : function(recver,args) {
            var func = function() {
                var hr = arguments.callee.hr;
                var proc = arguments.callee.proc;
                hr.runOpcode(
                    proc.__opcode,
                    proc.__parentStackFrame.classObj,
                    proc.__parentStackFrame.methodName,
                    recver,
                    hr.nativeAryToRubyObjectAry(arguments),
                    proc.__parentStackFrame,
                    true);
            };
            func.hr = this;
            func.proc = args[1];
            recver.__instanceVars.entity.addEventListener(
                args[0].__native,
                func);

        },
    },

見て解る通り、前回までのSpriteの内容の大部分はEntityとして共有部品として切り出され、Sprite固有のパラメータであるimageのみをSpriteクラスで扱うようにしている。

そしてSpriteと同じくEntityをスーパークラスとして持つLabelも、同様にtextというLabel固有の要素のみを提供している。

HotRubyにおけるクラス継承をどのように実現しているかというと、以下の関数による。

    extendRubyClass:function(subClass,superClass,recver,args){
        //Rubyクラス継承
        var subClass = HotRuby.prototype.classes[subClass];
        var superClass = HotRuby.prototype.classes[superClass];
        superClass["initialize"](recver,args); //親クラスのコンストラクタ呼び出し

        //クラス構造自体の書き換えは一度だけしかしない
        if(subClass[superClass+"_done"]){
            return;
        }
        subClass[superClass+"_done"]=true; //クラス構造初期化終了フラグ
        for(var i in superClass){ //親クラスのメンバーをまるごとコピー(継承)する
            if(!subClass[i]){
                subClass[i]=superClass[i];
            }
        }
    },

HotRubyの内部構造では、組み込みクラスは全てHotRuby.prototype.classesに連想配列として格納されている。

継承するためには、単にスーパークラスの要素をそのままコピーしてくればいい。
いまのところ、まだ適当な実装なので、スーパークラスのコンストラクタを呼び出さない場合には対応していない(普通のオブジェクト指向ではスーパークラスのコンストラクタは明示的に呼び出さない限り呼ばれない)。

ともあれ、これでLabelも実装することができた。
Ruby側コードはこんな感じになる

class Bear < Sprite
    def initialize w,h,image
        super(w,h)
        self.image = Game.assets(image)
        self.y=50
        self.setupEventListener();
    end
    def enterframe
        self.x+=1
    end
end

bear = Bear.new(32,32,"chara1.gif")
label = Label.new("Hello enchant.js");

Game.rootScene.addChild(bear)
Game.rootScene.addChild(label)

冒頭のスクリーンショットで「Hello enchant.js」と表示されているのは、Labelクラスの働きによるものだ。

当然、LabelもSpriteと同様にサブクラス化してイベントハンドラを設定することができる。

class Button < Label
    def initialize x,y,text
        super text
        self.x = x
        self.y = y
        self.setupEventListener();
    end
    def touchend
        self.text="Thank you!"

    end
end

button = Button.new(100,100,"Tap Here");

Game.rootScene.addChild(button)

これで、画面に表示された「Tap Here」というラベルをクリックすると、「Thank you」という表示に変わるというプログラムを書くことが出来る。

ここまで来るとゲームを作るための最低限の部品は揃って来た感じだ。
今のままでも簡単なゲームは作ることが出来るだろう。

あとはenchant.jsのプログラミングにとってはかなり重要な「Group」クラスと「Map」クラスを実装すれば、たいていのゲームは開発できるはずだ。

Groupクラスは比較的簡単に実装できると思う。
けれどもMapクラスはちょっと面倒そうだ。やってみないと解らないけど。
Rubyの二次元配列を正しく受け取ることが出来ればそれほどでもないかもしれない。

もう一息かな

このエントリーをはてなブックマークに追加
はてなブックマーク - Ruby on enchant.js : HotRubyにおける組み込みクラスの継承の実装
Post to Google Buzz
Share on GREE

Related posts:

  1. 続・Ruby×enchant.jsへの挑戦 / イベントドリブンモデルの実現
  2. かんたんプログラミング #5 関数をもっと呼び出す
  3. [enchant.js]shi3z式ゲームプログラミング #7 タイムアタックは神!
  4. enchant.js + node.jsで作る非同期型ネットゲームのプロトタイプ
  5. Rubyでenchant.jsを使える可能性(実験編)

Facebook comments:

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*