Bookmark this on Hatena Bookmark
Hatena Bookmark - The Ruby enchant.js experiment continues: event driven model test
Share on Facebook
Post to Google Buzz
Bookmark this on Yahoo Bookmark
Bookmark this on Livedoor Clip
Share on FriendFeed

Can enchant.js be used on Ruby? Yesterday’s experiment continues. This time I revised HotRuby.js while analyzing HotRuby.

If you take a look at the guts of HotRuby.js, you realize that it’s rather rough. It’s like the classes are just simply packaged and that’s it.

The packaging in this area is around line 1110 of HotRuby.js.

HotRuby.prototype.classes = {
"Object" : {
"==" : function(

In order to make it respond to enchant.js, it’s relatively easy to fiddle around with this area.

Let’s forget about game.preload for the time being and try packaging the Sprite class.

"Game" : {
"assets" : function(recver, args) {
return args[0];
}
},
"Sprite" : {
"initialize" : function(recver, args) {
var sprite = new Sprite(args[0],args[1]);
game.rootScene.addChild(sprite);
sprite.image=game.assets['andy.gif'];
recver.__instanceVars.sprite = sprite;
var obj = HotRuby.prototype.classes["Sprite"];
//Package each parameter's operator manually
//Still a bit rough, but puts into action
'x y scaleX scaleY rotation frame'.split(' ').forEach(function(prop) {
obj[prop] = function(recver,args){
return recver.__instanceVars.sprite[prop];};
obj[prop+"="] = function(recver,args){
recver.__instanceVars[prop]=args[0];
return recver.__instanceVars.sprite[prop]=args[0];};
obj["@"+prop+"="] = function(recver,args){
console.log("hoge");
recver.__instanceVars[prop]=args[0];
return recver.__instanceVars.sprite[prop]=args[0];};
obj[prop+"+="] = function(recver,args){
return recver.__instanceVars.sprite[prop]+=args[0];};
obj[prop+"-="] = function(recver,args){
return recver.__instanceVars.sprite[prop]-=args[0];};
obj[prop+"*="] = function(recver,args){
return recver.__instanceVars.sprite[prop]*=args[0];};
obj[prop+"/="] = function(recver,args){
return recver.__instanceVars.sprite[prop]/=args[0];};
obj[prop+"++"] = function(recver,args){
return recver.__instanceVars.sprite[prop]++;};
obj[prop+"--"] = function(recver,args){
return recver.__instanceVars.sprite[prop]--;};
obj[prop+"<"] = function(recver,args){
return recver.__instanceVars.sprite[prop] < args[0] ? this.trueObj : this.falseObj;};
obj[prop+"<="] = function(recver,args){
return recver.__instanceVars.sprite[prop] <= args[0] ? this.trueObj : this.falseObj;};
obj[prop+">"] = function(recver,args){
return recver.__instanceVars.sprite[prop] > args[0] ? this.trueObj : this.falseObj;};
obj[prop+">="] = function(recver,args){
return recver.__instanceVars.sprite[prop] >= args[0] ? this.trueObj : this.falseObj;};
obj[prop+"=="] = function(recver,args){
return recver.__instanceVars.sprite[prop] == args[0] ? this.trueObj : this.falseObj;};
});
},
//Accessor for image property is treated and packaged specially
"image" : function(recver,args) {
return HotRuby.prototype.classes.
newNativeObject("String",
recver.__instanceVars.sprite.imageName);
},
"image=" : function(recver,args) {
recver.__instanceVars.sprite.imageName=args[0].__native;
return recver.__instanceVars.sprite.image=
game.assets[args[0].__native];
},
//The key here, event listener, is called up and packaged with a block-equipped method
"addEventListener" : function(recver,args) {
//Block area extracted as a JavaScript function
var func = function() {
var hr = arguments.callee.hr;
var proc = arguments.callee.proc;
hr.runOpcode(
proc.__opcode,
proc.__parentStackFrame.classObj,
proc.__parentStackFrame.methodName,
recver, // Object is recver
hr.nativeAryToRubyObjectAry(arguments),
proc.__parentStackFrame,
true);
};
func.hr = this;
func.proc = args[1]; //Block is accepted as secondary argument
// Connects to enchant.js event listener
recver.__instanceVars.sprite.addEventListener(
args[0].__native,
func);
},
//Used during class definition style. Mentioned later.
"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.sprite.addEventListener(
eventName,func);
}
});
},

As I wrote in the comments, the key here is the packaging of addEventListener.

enchant.js’s secret for simply creating games lies in its event driven model.

If this is clearly defined on the Ruby end of things, we’ve made quite a lot of progress.

Last time we forcefully pulled things out of the JavaScript end, but in order for the Ruby side to be packaged neatly, this time we’re making use of Ruby’s trademark block method.

Thanks to these improvements in HotRuby.js, this Ruby code is much more akin to enchant.js.

bear = Sprite.new(32,32)
bear.image = Game.assets "chara1.gif"
bear.addEventListener('enterFrame'){|e|
self.x+=1
}

You can display these bears and animate them to move to the right. Quite like enchant.js on JavaScript!

Because functions aren’t a first-level object in Ruby, we can’t deliver nameless functions as in JavaScript. It feels quite unnatural.

In addition, because Ruby is a programming language with such precisely defined classes, you can think of event processing by overriding class methods, like enterFrame, as the muscle.

Consider the following example.

class Bear < Sprite
def initialize w,h,image
super(w,h)
self.image = Game.assets(image)
self.y=50
self.setupEventListener(); //Sets up event listener
end
def enterframe
self.x+=1
end
end
bear = Bear.new(32,32,"chara1.gif")

This is a much easier way of writing things. Of course, part of Ruby’s appeal is the fact that you don’t need to select a way of writing things.

In the C++-based MFC (Microsoft Foundation Class Library) and the Java-based AWT (Abstract Window Toolkit), it’s normal to package event processing by overriding.

However, in these types of programming frameworks, the system for receiving events from the OS is not a direct callup method. As a result, some of the elegance is lost, but by calling up setupEventListener within initialize the JavaScript-side events are set.

I’m no HotRuby expert, so perhaps there’s a slicker method out there (perhaps processing within “super?”).

However, I feel like our reasons for writing in Ruby and not JavaScript have become clear. When all the objects appearing in a game are expressed as classes, it’s quite elegant and simple to write as event listeners overriding in classes.

I feel like we’ve approached our goal here. If we make incompatible Scenes, Groups, and Labels functional in Ruby, it’s not that difficult to make an HTML5 game on Ruby and enchant.js.

I’ve only just started to read the HotRuby source code. If you know of a more elegant way of handling it, please let me know!