<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>LAZEROIDS!!!</title>
	<atom:link href="http://lazeroids.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://lazeroids.wordpress.com</link>
	<description>Massively multiplayer javascript and rails hacking.</description>
	<lastBuildDate>Fri, 28 Aug 2009 02:41:16 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='lazeroids.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://s2.wp.com/i/buttonw-com.png</url>
		<title>LAZEROIDS!!!</title>
		<link>http://lazeroids.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://lazeroids.wordpress.com/osd.xml" title="LAZEROIDS!!!" />
	<atom:link rel='hub' href='http://lazeroids.wordpress.com/?pushpress=hub'/>
		<item>
		<title>Hello, World!</title>
		<link>http://lazeroids.wordpress.com/2009/08/28/hello-world/</link>
		<comments>http://lazeroids.wordpress.com/2009/08/28/hello-world/#comments</comments>
		<pubDate>Fri, 28 Aug 2009 02:41:16 +0000</pubDate>
		<dc:creator>gerad</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://lazeroids.wordpress.com/?p=4</guid>
		<description><![CDATA[Hi, World! For those of you who don&#8217;t know it, LAZEROIDS!!! is a networked, multiplayer, asteroids game written in JavaScript with a Ruby on Rails back-end. We built Lazeroids last weekend for the Rails Rumble, a 48 hour programming contest. Since then, we&#8217;ve gotten a ton of questions about how we did it. Because we [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=lazeroids.wordpress.com&amp;blog=9208836&amp;post=4&amp;subd=lazeroids&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Hi, World!</p>
<p>For those of you who don&#8217;t know it, <a href="http://lazeroids.com">LAZEROIDS!!!</a> is a networked, multiplayer, asteroids game written in JavaScript with a Ruby on Rails back-end. We built <a href="http://lazeroids.com/">Lazeroids</a> last weekend for the <a href="http://r09.railsrumble.com/">Rails Rumble</a>, a 48 hour programming contest.</p>
<p>Since then, we&#8217;ve gotten a ton of questions about how we did it.  Because we don&#8217;t see too many people writing massively multiplayer games in HTML and JavaScript (much less in under 48 hours), we thought some of our tricks might be useful to the rest of you.</p>
<p>As a caveat, the title of this post is actually fairly meaningful.  <a href="http://lazeroids.com">LAZEROIDS!!!</a> was a bit of a <a href="http://en.wikipedia.org/wiki/Hello_world_program">&#8220;Hello, World&#8221; program</a> for us.  None of us had ever worked extensively with comet, canvas or web sounds before this weekend (we&#8217;d dabbled, but nothing serious).  Furthermore, we&#8217;re web programmers, not video game guys, so we made up a lot of the video game stuff as we went along.  So take anything we write here with a grain of salt.  We may not know what we&#8217;re talking about.:-)</p>
<p>Now, on to the good stuff.</p>
<h1>Architecture</h1>
<p>We used a pretty standard JavaScript MVC pattern for the application.  There are <a href="http://m.alistapart.com/articles/javascript-mvc/">some frameworks out there</a> that look pretty good, but we&#8217;d never used any of them, and a 48 hour programming contest is not time to learn a new framework. We&#8217;ve built a substantial amount of JavaScript MVC coding for <a href="http://business.swivel.com/">Swivel</a>, so we were pretty comfortable with rolling our own.  However, we did use <a href="http://www.prototypejs.org/">prototype.js</a> to give us all the niceties that make working with JavaScript pleasant.</p>
<p>The controller took care of initializing the view and model and dealing with user input like keyboard commands.  The view was a canvas. We had a model class for the Universe that held masses and dealt with time. We also had various Mass classes (Spaceships, Asteroids, Bullets, etc), that all had their own movement explosion and display code.  We broke down from traditional MVC in that the masses knew how to draw themselves, and the Universe model also took care of the networking.</p>
<h1>Networking</h1>
<p>No matter what advances the Web has made in recent years, we knew that we couldn&#8217;t rely on robust, instantaneous networking.  To keep things snappy, most of the game was built to work without networking, and the parts of the game that were built with networking were built to deal with a lossy network.  The game actually degrades to single player mode if the network goes down.</p>
<p>A network message is sent when: the ship changes direction or acceleration, any object is added to the universe (this includes bullets, asteroids, or fragments of asteroids after it blows up), or any object is removed from the universe.  We additionally send b-frames with the current information for all the objects around a ship every few seconds, to make sure that asteroid positions are synced across clients.</p>
<h2>Peer-to-Peer</h2>
<p>We knew performance would be an issue, so we tried to leverage the power of the other players as much as possible.  Also, we wanted to make the application feel seamless as possible from each client&#8217;s perspective.</p>
<p>Rather than have every client check for every possible collision in the game world, we made each client responsible for its own collisions.  Your ship&#8217;s bullets check whether they hit an Asteroid (but don&#8217;t check whether they hit another ship).  You ship checks whether it hit anything (including whether somebody else&#8217;s bullet hit you).  The nice thing about this is that actions are almost always consistent from your perspective, no matter what&#8217;s happening on other clients.  If you hit something or you see a bullet hit you, you blow up.   If your bullet hits something, it blows up.</p>
<p>The one thing that can be frustrating is that other clients might see a bullet hit you, but you don&#8217;t blow up (because it misses you from your perspective).  As we do a better job of syncing clients with multiple frame rates, this sort of discrepancy should happen less often.</p>
<p>This also opens the door for people hacking their own JavaScript code and cheating, patching it to ignore any collisions with their own spaceship, basically hacking in a god mode cheat.  There are numerous other ways that they could cheat the open network protocol.  We actually thought being able to do this was cool rather than a flaw.  It spoke to the flexibility and generality of the system rather than as a detraction.  If you wanted to add a shield, you could hack it in with just JavaScript.  If you wanted a radioactive aura that melted your enemies if they came too close, you could do that too.</p>
<h2>Connections</h2>
<p>We used AJAX/HTTP to send data, and Comet/STOMP to receive it.  We let Orbited.js deal with cross-domain comet queries (assumably by abusing iframes).  For the most part, the networking &#8220;just worked.&#8221;  But we wrapped everything in a simple connection object to make the implementation a bit friendlier.</p>
<pre>Lz.Connection = Class.create({

  initialize: function(host, port, user, password) {
    this._observers = { message: [], connect: [] };
    this._stomp = this._initializeStomp(host, port, user, password);
  },

  observe: function(type, callback) { this._observers[type].push({callback: callback}); },
  fire: function(type, frame) { this._observers[type].invoke('callback', frame); },

  send: function(data) {
    new Ajax.Request('/update', {
      method: 'put',
      parameters: { data: Object.toJSON(data) }
    });
  },

  _initializeStomp: function(host, port, user, password) {
    var stomp = new STOMPClient();
    stomp.connect(host, port, user, password);

    stomp.onconnectedframe = function(frame) {
      stomp.subscribe('/topic/lazeroids');
      this.fire('connect', frame);
    }.bind(this);

    stomp.onmessageframe = function(frame) {
      this.fire('message', frame);
    }.bind(this);

    return stomp;
  }
});</pre>
<p>It does take a bit of time to establish the connection, so make sure you wait for the connect frame before sending out any messages you want to hear a response to.</p>
<h2>Domain = Domain</h2>
<p>We found out the hard way that when you are requiring your browser to make cross-domain queries, you need to include the following seemingly innocuous line in the &lt;head&gt; tag of your web page.</p>
<pre>document.domain = document.domain; // x-domain scripting!</pre>
<p>Note that allowing cross domain queries enables security holes, but we felt it was ok to sacrifice security for development speed in the context of a 48 hour competition.  It&#8217;s relatively easy to get rid of the cross domain queries if you use <a href="http://www.olivepeak.com/blog/posts/read/free-your-port-80-with-haproxy">the right proxy set-up</a>.</p>
<h2>Orbited.js and JSON</h2>
<p>For reasons unknown, Orbited.js totally munges JavaScript&#8217;s String.toJSON implementation.  We tried to code around this, but it became obnoxious, so we monkey patched a fix.</p>
<pre>String.prototype.toJSON = function() {
  return '"' + this.valueOf() + '"';
};</pre>
<h2>Out of Order Events</h2>
<p>It&#8217;s important to keep in mind that AJAX will happily send requests out of order.  What this meant for us is that you could receive old ship positions after you had received later ones.</p>
<p>Since our client was basically RESTful (weird to write a RESTful client, rather than a server), each time an update happened it sent a complete refresh of data.  We didn&#8217;t have to depend on old packets to determine current state.</p>
<p>Thus, we were able to keep a cross-client synced time-tick to determine whether we had received old messages that could be thrown away.  It was a simple change to ignore old messages.</p>
<pre>Lz.Mass = Class.create({
  update: function(options) {
    if (options.tick &lt; this._tick) return; // ignore old updates
    this._tick = options.tick;
    // other stuff here...
  }
});</pre>
<h1>Canvas</h1>
<p>Objects were drawn using canvas.  We weren&#8217;t concerned about IE support during the competition.  After the competition was over, we were able to drop in <a href="http://excanvas.sourceforge.net/">ExplorerCanvas</a> and have things mostly just work.  That was a pleasant surprise.</p>
<p>Curiously, we have had a bit of trouble getting Chrome to work.  We&#8217;re not sure if it&#8217;s our code or Google&#8217;s.</p>
<h2>Zooming</h2>
<p>When we started out the weekend, we wanted to have a radar in the bottom corner of the screen.  As the clock ran down, it became clear that we wouldn&#8217;t have time.  Instead, we were able to leverage the power of canvas to quickly implement zooming out when you press the &#8216;z&#8217; key.  The code below is the universe&#8217;s main render loop.  The highlighted zooming code assumes only two possible values for this._zoom: 1 (no zoom) and 0.5 (2x zoom).</p>
<pre>render: function() {

  var ctx = this._ctx;
  ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);

  ctx.save();
  <strong>if (this._zoom != 1) {</strong>
    <strong>ctx.scale(this._zoom, this._zoom);</strong>
    <strong>ctx.translate(this._bounds.width*0.75, this._bounds.height*0.75);</strong>
  <strong>}</strong>
  this._bounds.translate(ctx);
  this._masses.invoke('render');
  ctx.restore();
}</pre>
<h2>Panning</h2>
<p>When we started the app, screen coordinates were fixed.  If you flew your ship off screen, you were flying blind, because the screen didn&#8217;t move with you.  Unlike the traditional asteroids game, where you wrap to the other side of the screen when you go off, we wanted our game world to feel massive.  However, keeping the ship in the center of the screen didn&#8217;t feel right either (especially because we had no background to give you a sense of movement).  Instead we implemented panning.  When you near the edge of the screen, the screen swoops ahead of you.  That makes it dangerous to go over the edge of the screen, because you&#8217;re flying somewhat blind.  However, we think it adds to the excitement.</p>
<p>Canvas again makes this relatively simple with it&#8217;s drawing state stack.  All we had to do was translate to the next screen when the player hit the edge.  That&#8217;s as easy as:</p>
<pre>ctx.translate(-canvas.width, 0)</pre>
<p>to move one screen to the left.  But, we wanted flashy scrolly transitions.  That&#8217;s a little harder, but we have scriptaculous and other effect js libraries to inspire us.  What you want to do is know your final key frame value for the translation and get to it incrementally.  It l</p>
<pre>check: function(ship) {
  var p = ship._position;

  if (p.x &lt; this.l) {
    this.dx = -this.width * 0.75;
  } else if (p.x &gt; this.r) {
    this.dx = +this.width * 0.75;
  }

  if (p.y &lt; this.tR) {
    this.dy = -this.height * 0.75;
  } else if (p.y &gt; this.b) {
    this.dy = +this.height * 0.75;
  }

  if (this.dx != 0) {
    var dx = parseInt(this.dx / 8);
    this.l += dx; this.r += dx;
    this.dx -= dx;
    if (Math.abs(this.dx) &lt; 3) this.dx = 0;
  }

  if (this.dy != 0) {
    var dy = parseInt(this.dy / 8);
    this.t += dy; this.b += dy;
    this.dy -= dy;
    if (Math.abs(this.dy) &lt; 3) this.dy = 0;
  }
},

translate: function(ctx) {
  ctx.translate(-this.l, -this.t);
}</pre>
<h1>Sounds</h1>
<p>We implemented sounds early on.  They gave the game a sense of concreteness.  Sounds are the one place we broke with an all HTML / JavaScript solution.  We used the open source <a href="http://ajaxian.com/archives/soundmanager-2-a-sound-api-for-javascript">SoundManager JavaScript to SWF bridge</a> to play sounds.  It was amazingly  easy to drop in and just start using.</p>
<p>This is what the code looked like:</p>
<pre>Lz.play = function() { };  // no-op stub for before soundmanager finishes loading

soundManager.debugMode = false;
soundManager.url = '/swf/';
soundManager.onload = function() {
  soundManager.createSound('ambient', '/sounds/ambient.mp3');
  soundManager.createSound('shoot','/sounds/shoot.mp3');

  var playLoop = function(id) {
    setTimeout(function() {
      soundManager.play(id, {
        onfinish: function() { playLoop(id) }
      });
    }, 1);
  }

  playLoop('ambient');

  Lz.play = soundManager.play;
};</pre>
<p>In the near future, we want to look into the HTML5 audio element, but sound file format agreement among browser makers might keep us on soundmanager for a while.</p>
<p>That&#8217;s about it! We hope this helps any aspiring JavaScript MMO game developers.</p>
<p>If you have any questions, you can reach us on Twitter at <a href="http://twitter.com/lazeroids">@lazeroids</a>.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/lazeroids.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/lazeroids.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/lazeroids.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/lazeroids.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/lazeroids.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/lazeroids.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/lazeroids.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/lazeroids.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/lazeroids.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/lazeroids.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/lazeroids.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/lazeroids.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/lazeroids.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/lazeroids.wordpress.com/4/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=lazeroids.wordpress.com&amp;blog=9208836&amp;post=4&amp;subd=lazeroids&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://lazeroids.wordpress.com/2009/08/28/hello-world/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/b54c8f86a5b842e01d9691ff065d2824?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">gerad</media:title>
		</media:content>
	</item>
	</channel>
</rss>
