The killer feature in my (fab) game (as least as far as my son is concerned) is chatting. Sure I can have multiple people in a room, I could probably get rudimentary collision detection working in it, I can even animate players as they move about the room. None of that matters in my son's eyes. He wants to be able to chat.
Kids.
I have a lot of fairly decoupled pieces at the moment. In the frontend, largely raphaël.js with a sprinkle of jQuery, I have a bunch of
Player
s that roam about a Room
. I also have a PlayerList
that holds the list of players in a room and, as such, serves as the middleman for comet push communication from the fab.js backend. Speaking of the backend, there are two parts that will factor into chatting: notifying the backend of the message and broadcasting the message to all players.That is a lot of moving pieces. To make sure I do this right, I am going to use the tried-and-true method of tracer bullets to make sure I know my target before I hit it for real. I start with a simple form at the bottom of my
Room
, which I add via a jQuery insertAfter
:Room.prototype.draw = function() {I know that this message eventually needs to hit the server, but first, the room needs to tell player that I said something so that my player can tell the fab.js backend. But even before that, I need to know that I can capture the form input. So, my first tracer bullet:
var self = this;
// Initialize the Raphaël.js room
this.paper = Raphael(this.container, 500, 500);
this.paper.clear();
this.paper.
rect(0, 0, 500, 500, 4).
attr({fill: "#fff", stroke: "none"});
// Form to capture chat
$('<div id="chat">' +
'<form id="chat-form" action="#">' +
'<label for="change-message">Chat:</label>' +
'<input type="text" name="message" id="chat-message" />' +
'<input type="submit" name="commit" value="Go" />' +
'</form>' +
'</div>').insertAfter($(self.paper.canvas).parent());
}
Room.prototype.draw = function() {Now, when I enter a message and press "Go", I get nice debug output in Chrome's javascript console. Obviously, this
var self = this;
// Initialize the Raphaël.js room
// Form to capture chat
// handle submits
$('#chat-form').submit(function(e) {
console.debug($('#chat-message').val());
subscriber.notify(event);
$('#chat-message').val('');
return false;
});
}
submit
handler is of little use to me at this point, but that is the whole point of tracer bullets. I make sure that I can hit the target (handling chat messages) before I do it for real.So let's do it for real. This is being done in my
Room
, but my current player needs to be told that a message was posted. I could hold a reference to my player in the room and call the Player.say()
method directly, but that would pretty heavy handed coupling. Instead I have a pub/sub for events which allows me to send events, like a new chat message, to all interested parties (like my Player
):$('#chat-form').submit(function(e) {Time for another tracer bullet at this point. My
self.subscribers.forEach(
function(subscriber) {
var event = {
type: "message",
value: $('#chat-message').val()
};
subscriber.notify(event);
$('#chat-message').val('');
}
);
return false;
});
Player
currently ignores all notifications other than move:Player.prototype.notify = function(evt) {Rather than going for full blown support of the "message" event, I add some more simple debugging to make sure that I am aiming the right way:
switch(evt.type) {
case "click":
this.stop();
this.walk_to(evt.x, evt.y);
this.notify_server('move', {id:this.id, x:evt.x, y:evt.y});
break;
}
};
Player.prototype.notify = function(evt) {Now, when I send a chat message, I get more nice debug output in Chrome's javascript console. Since my aim is good, I can add the real stuff to handle message notifications:
switch(evt.type) {
case "click":
this.stop();
this.walk_to(evt.x, evt.y);
this.notify_server('move', {id:this.id, x:evt.x, y:evt.y});
break;
default:
console.debug("[notify] type: " + evt.type + " value: " + evt.value);
}
};
Player.prototype.notify = function(evt) {New in there is notifying the server of a chat event. For that, I need to move down into my fab.js backend where a unary will handle the request:
switch(evt.type) {
case "click":
this.stop();
this.walk_to(evt.x, evt.y);
this.notify_server('move', {id:this.id, x:evt.x, y:evt.y});
break;
case "message":
this.stop();
this.walk_to(this.x, this.y);
this.notify_server('move', {id:this.id, x:evt.x, y:evt.y});
this.notify_server('chat', {id:this.id, say:evt.value});
break;
default:
console.debug("[notify] type: " + evt.type + " value: " + evt.value);
}
};
( /chat/ )Nothing too fancy there. It is a simple unary app with a listener to grab the data being posted (the chat message payload). I then use the already-in-place
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
broadcast(comet_player_say(obj.body));
}
return listener;
};
} )
broadcast
method to tell all attached clients that someone had something to say. I am not using tracer bullets here because I know these fab.js targets well. What I am not so sure about is the ultimate broadcast call. For that, I will tell the
PlayerList
on each connected browser that somebody said something:function comet_player_say(player_string) {This brings me to my final bullet point for the night, the
return comet_wrap('player_list.player_say('+ player_string +')');
}
player_say
method in PlayerList
:PlayerList.prototype.player_say = function(attrs) {Here I use good, old fashioned
alert(attrs.id + " says " + attrs.say);
};
alert()
for my tracer bullets. Eventually, I would like what the player said displayed immediately above the player in the room. But, for now, an alert will provide a reasonable facsimile:Nice!
Bit of a whirlwind, but I have basic chatting working. Tomorrow, I will tidy things up a bit and replace the last of my tracer bullets with in-room chat messages.
Day #110
No comments:
Post a Comment