This is part 4, the conclusion to a series that details our team’s journey to creating disasteroids.com for NodeKnockout 2012, a 48 hour coding competition that showcases projects built on node.js. You can find part one here, part two here, and part three here.
We Need Eye Candy!
Once we got the basic physics and rendering in place it was time to figure out how to make the explosions and rocket trails. We initially had something like Scorched Earth in mind where the rockets draw a permanent trail, but we weren’t tied to the idea. After some quick hunting around we came across Javascript Particle System by @jsoverson and we were sold. It’s more of a demo than a library meant for reuse, so there are no docs. Luckily it was written pretty well and it wasn’t too hard to discern how to use it and pull out the functionality we needed.
There are six objects at the core of the particle system.
Display - Wrapper for canvas element
ParticleSystem - This runs the show and tells all the other element what to do
Vector - A wrapper for x,y coords with some convenience methods
Particle - represents an individual particle
Field - Basically a source of gravity. negative gravity is repulsion.
Emitter - Emits particles
Initialization
Basically how it works is you wrap a canvas element in a Display object which you then pass into ParticleSystem’s init like so:
// wrap canvas with Display and initialize
var display = new Display(this.canvas);
display.init();
// Initialize ParticleSystem and init with display
var particleSystem = new ParticleSystem().init(display);
// set an upper limit for total number of particles
particleSystem.maxParticles = 10000;
// start
display.start();
Missile Thruster Particle Trail
Once this is done all you need to do is create an Emitter and push into the appropriate array and the particle system will do the rest. for example, here is a piece the render method for a missile:
render: function(canvas, particleSystem) {
var self = this;
// play shoot sound
app.playAudio('rocketShoot');
// create local handle for particle system
this.particleSystem = particleSystem;
// create new emitter called thruster
this.thruster = new Emitter();
// set parameters
this.thruster.position = new Vector(self.model.get('x'), self.model.get('y'));
this.thruster.velocity = Vector.fromAngle(self.model.get('angle')*180/Math.PI, 5);
this.thruster.size = 0;
this.thruster.particleLife = 100;
this.thruster.spread = .5;
this.thruster.emissionRate = 0.5;
this.thruster.jitter = 0;
this.thruster.particleColor = [255,130,0,1];
// push emitter into particle system
particleSystem.emitters.push(this.thruster);
}
This starts shooting particles out of the back of the missile. Since we already have all the math done to update the missiles position and angle, we can simple hook into that in the missiles reposition method.
reposition: function(model) {
var self = this;
// reposition the missile
this.object.set('left',self.model.get('x'));
this.object.set('top', self.model.get('y'));
this.object.set('angle', self.model.get('angle')*180/Math.PI);
// reposition the thruster emitter
var thrusterVector = Vector.fromAngle(self.model.get('angle') + Math.PI/2, self.model.get('height')/2);
this.thruster.position = new Vector(self.model.get('x') + thrusterVector.x, self.model.get('y') + thrusterVector.y);
// update the angle so it is always shooting out the tail of the missile
// velocity takes a vector that governs the angle as well as the speed
this.thruster.velocity = Vector.fromAngle(self.model.get('angle') + Math.PI/2, 2);
}
That gives us a missile with a particle trail!
Explosions and Blasts
Now we just need to handle what happens when the missile hits something. Basically what we want to do is:
remove the truster emitter
create an explosion emitter
create a repulsion field so the particles don’t just emit in a circle but get blasted away
wait a bit and remove the explosion and repulsion field.
Heres what it looks like in the missile’s exit method:
exit: function() {
var self = this;
// play impact sound
app.playAudio('rocketImpact');
// remove thruster
this.particleSystem.removeEmitter(this.thruster);
// create explosion emitter and set params
this.explosion = new Emitter();
this.explosion.position = new Vector(this.model.get('x'), this.model.get('y'));
this.explosion.velocity = new Vector(0, 5);
this.explosion.size = 0;
this.explosion.particleColor = [255,50,0,1];
this.explosion.spread = 50;
this.explosion.emissionRate = 10;
this.explosion.particleLife = 7;
// create repulsion field and set params
// remember negative gravity will repel
this.repulsion = new Field(this.explosion.position, -1000);
this.repulsion.size = 0;
// push explosion and repulsion field into particleSystem
this.particleSystem.fields.push(this.repulsion);
this.particleSystem.emitters.push(this.explosion);
// wait 250ms and remove the explosion
setTimeout(function() {
self.particleSystem.removeEmitter(self.explosion);
}, 250);
// wait 1500ms and remove the repulsion field
// the wait is longer here so the particles can all get blown away
setTimeout(function() {
self.particleSystem.removeField(self.repulsion);
}, 1500);
}
That gives us a nice particle explosion that blasts away debris
The End
Thats the gist of how the particle physics implementation works. Thanks for taking time to read and we hope you have fun playing the game as well.
Note: We did have to customize the Javascript Particle System a bit to meet our needs. Primarily to allow for different sized particles and different colors. It also depended on require.js, which we removed as well. Here is the custom code on github. Thanks again to @jsoverson for making such a cool and extendable project.
VOTE!
If you enjoyed disasteroids.com or this post, please consider throwing us a vote on our team’s page. It requires Facebook authentication to ensure that there isn’t rampant cheating, but the awesome NodeKnockout organizers @visnup and @gerad would never use your information in a malicious way. They’ve been crazy enough to organize this event for the 3rd year now despite the operational headaches of putting together such a massive event.
This is part 3 of an ongoing series that details our team’s journey to creating disasteroids.com for NodeKnockout 2012, a 48 hour coding competition that showcases projects built on node.js. You can find part one here and part two here.
REALIZING THE WORLD:
With Bundy putting together the physics and world state and Travis crafting the design assets, one of the remaining tasks was fitting the two together in a way that looked good and made sense. In essence, we needed to create the world that the user sees. This process fell fairly neatly into two main pieces:
Adjusting the state of the physical box2d objects so that they behaved in an intuitive way.
Fitting design assets (mechs, missiles, asteroids) to client-side view objects and updating those view objects with the proper state data to display them correctly.
MATHEMATICS OF ALIGNMENT:
Warning, mild math ahead: We had the physics engine running fairly early in the competition, and we quickly realized that the objects we created behaved like one would expect objects to behave in space. When objects were created and set into motion, they would maintain their orientation until they collided with something. When they collided, the involved bodies would careen off into space spinning wildly until they landed on an asteroid, often sideways, upside down, or somewhere in between. This caused two visual problems:
When a player jumped, it was very unlikely that they would land on their feet.
When a missile was fired, it didn’t always point in the direction it was traveling.
Thankfully, box2d has a method for bodies called SetAngle(), which allowed us to set the orientation of any body at any point in time. All that remained to solve these issues was a little bit of trigonometry:
The nonsensical chicken-scratches you see above were just the desperate musings of a math major who took trigonometry way too long ago. The actual calculations behind these alignment solutions were really quite simple. Let’s examine the player alignment problem first.
If we want to ensure that a player flying through space lands on their feet on an asteroid, at the point of impact we need to adjust the angle of the player object such that its angle corresponds to the angle defined by the player body’s relative position to the center of the asteroid. If that didn’t make much sense, here’s a diagram to help illustrate the idea:
We can find the angle, theta, by using the triangle formed by the coordinates of the centers of the player and asteroid bodies. Since we have the opposite and adjacent sides of the triangle relative to theta, we can use the inverse tangent to solve for theta:
Now that we have a method for alignment, we need to decide when to align. We chose to define a distance from the asteroid, or a “Radius of calculation” to govern this rule. When a player falls within the radius, it constantly recalculates its new angle and aligns itself until it comes to rest:
Perfect, right? Almost. We just need to account for multiple asteroids. We don’t want the player aligning to any old asteroid on the field. What we did was ensure that the player was only aligning to the nearest asteroid surface. This was calculated with the standard formula for distance. Once implemented, this solution worked pretty well, although unfortunately, some bugs still do exist as players are landing on their heads every now and then.
Now, using the same principles, aligning the missile is pretty easy. We just want to point the missile in the direction it’s going. Box2d has a method that allows us get the linear velocity vector of a body, basically how fast and how much in the x and y directions a body is moving. With these x and y values we can draw a similar triangle to one for the player angle, and determine the angle from the inverse tangent of the relationship of the sides:
That pretty much covers the math involved with object alignment. I will mention that determining the firing angle of the missiles involved some trigonometry as well, but the calculations were similar enough to omit here. Overall the math is simple, involving only triangles and polar coordinate conversion, but it’s the application of the formulas that give the world its proper look and feel.
ENOUGH MATH, BACK TO THE SHINY STUFF:
Now that the objects are behaving properly on the backend we need to make sure that they are represented properly on the frontend. Rather than drawing directly to HTML5 canvas, we chose to use a framework that managed the drawing for us. We landed upon fabric.js. It allowed us to define objects and render SVG (Scaleable Vector Graphics) based on an object’s x position, y position, angle, and scale. Once we created the data models to receive and hold this state data from the server, rendering the objects was as simple as updating the fabric object instances. Then each object was re-rendered with each draw cycle.
Fabric.js was a fantastic find that saved us a lot of time. It removed a lot of the headache of rendering which allowed us to focus on other aspects of the projects. Even more, it played nice with other canvas rendering systems like particle effects, which Josh will detail in our next post.
Part 4: Particle Effects Adding Texture To Your Canvas Game
VOTE!
If you enjoyed disasteroids.com or this post, please consider throwing us a vote on our team’s page. It requires Facebook authentication to ensure that there isn’t rampant cheating, but the awesome NodeKnockout organizers @visnup and @gerad would never use your information in a malicious way. They’ve been crazy enough to organize this event for the 3rd year now despite the operational headaches of putting together such a massive event.
This is part 2 of an ongoing series that details our team’s journey to creating disasteroids.com for NodeKnockout 2012, a 48 hour coding competition that showcases projects built on node.js. You can find part one here.
THE GOAL:
When we set out to create Disasteroids, we knew that how we model gravitation would be an integral part of what defines the experience when playing the game. We envisioned insane, well calculated trick shots that looped around the gravitational pull of multiple asteroids. We sat down and started to discuss the specifics of how mass, gravity, and velocity would all be handled and we were quickly faced with the decision of rolling our own simple physics engine or investing the time to familiarize ourselves with an existing open source alternative.
It was a harder call than expected considering that the competition was only a 1-2 days away and none of the members of the team had ever used a physics engine let alone heard of one that’s written in Javascript. Thankfully, some quick Google searches brought up Box2D which had a Javascript port called box2dweb. After being blown away by a few tech demos that made you ask yourself, “This is being done in the browser using Javascript?” we decided to bet on Box2D and go full force. We spent what time we could stepping through Seth Ladd’s excellent intro to using Box2D in Javascript before the final hours before the competition.
BOX2D FEATURES:
Box2D allows you to setup a “world” of arbitrary size and populate it with any number of objects that they individually refer to as a “body.” Each body has a set of properties such as “restitution” (bounciness), friction, and density. Primed with this knowledge, we created a world and threw up circular bodies as asteroids and rectangles as player and missiles. This was easy enough, but we quickly realized that Box2D is a physics engine and is only meant to simulate a virtual world. It was entirely up to us to translate the data that Box2D provided into a visual scene. How we achieved this will be detailed in a later post, but for now, we will talk about Box2D’s awesome debug draw feature.
box2dweb (the Javascript port of Box2D) includes a debug draw class that takes in a canvas reference. This class then draws a simple representation of the world which shows every body’s position, orientation, and status.
http://code.google.com/p/box2dweb/ has a great in browser example of what this looks like. The Box2D documentation made it very clear that this is to be used to debug purposes only, but it proved to be invaluable in quickly testing how our code made bodies interact without having to immediately worry about how to translate Box2D’s simulation into sprites and images on canvas. This allowed for us to independently start work on modeling Disasteroid’s physics even before any of the design assets were finalized and ready for us to use.
RADIAL GRAVITY:
“Radial Gravity,” is the term that we learned was used to refer to gravitational simulations that involved planetary bodies in space. It turned out that simulating this kind of environment is not too uncommon and we were able to pull some blog posts and tutorials detailing just how to go about implementing it using Box2D. This was an amazing find for us considering we only had 48 hours. Here’s a post by Emanuele Feronato that saved us tons of time and effort. The tutorial was written in ActionScript 3, but he provided an extensive line by line breakdown of the code, which made porting most of the core concepts effortless.
Box2D doesn’t have a built in way of applying gravity onto an arbitrary object in the simulated world. As a result, we had to custom build this feature by applying a force to all objects towards the asteroids on every tick of the physics simulation. The basic algorithm for applying radial gravity worked as follows:
1. Get the distance of the player/missile to all nearby asteroids.
2. Apply a force on the player/missile towards the asteroids.
3. Make sure the force is proportional to the radius of the asteroid
4. Make sure the force applied is lowered as the object’s distance from the asteroid increases.
That’s it! It sounds complicated, but the implementation was extremely simple.
CHALLENGES:
The main challenge using Box2D for Disasteroids was the fact that it was a multiplayer game where the node.js server would serve as the authoritative source of what was going on in the world. This would mean that the Box2D simulation needed to be run on the node.js server. We threw out the idea of running predictive simulations on the client side solely based on the fact that we only had 48 hours. This posed a few challenges:
Challenge #1: The physics simulation on node.js had to run FAST in order to properly relay the world state to all connected players.
To circumvent this issue, we had to lower the FPS of the game to 30 from 60 and also optimized/rewrote the main loop that processed the Box2D world simulation at least 4 times throughout the span of the 48 hour coding event.
Challenge #2: There was no way for us to use the debug draw class on node.js to get a picture of what was going on. Node.js isn’t a browser and doesn’t have a canvas object that we can easily hook into in realtime.
We knew of projects such as LearnBoost’s node-canvas (https://github.com/LearnBoost/node-canvas), but we didn’t really have to time to look into it and had to come up with quick tests that ensured we were translating the positional data from the node.js server to correct visual representations on the browser. This certainly created some frustrating moments where things were off center, oriented in the wrong direction, or not showing up entirely, but we somehow managed to get through all the issues in the end. There were instances, however, where we weren’t able to pinpoint issues quickly because it was unclear if things were showing up incorrectly as a result of a bug in the physics simulation on node.js or the rendering on the browser.
WRAP UP:
In the end, Box2D was an awesome choice that made Disasteroids what it is today. We were only able to scratch the surface of what it has to offer. It has already been rolled into popular HTML5 game engines such as Impact.js and Cocos2D and we hope to bring it some more exposure as it definitely helped us save the time and trouble of implementing a physics engine.
If you enjoyed disasteroids.com or this post, please consider throwing us a vote on our team’s page. It requires Facebook authentication to ensure that there isn’t rampant cheating, but the awesome NodeKnockout organizers @visnup and @gerad would never use your information in a malicious way. They’ve been crazy enough to organize this event for the 3rd year now despite the operational headaches of putting together such a massive event.
On Wednesday afternoon we made a trip to the airport.
Since the last Node Knockout some 15 months ago, 3/4 of the team had moved from Santa Monica to Boulder, CO to get involved with TechStars, but our fourth man @bundy_kim was flying in from LA for a couple days of brainstorming and wandering around town before the start of the competition.
We are all avid gamers, but had never built a game of our own before, and decided that the NKO provided a great opportunity. We considered a handful of different ideas, but settled on a modern adaptation of the classic artillery games. In short order we knew what we had to add: space, missiles, robots, and real time multiplayer.
T-Minus 24 Hours
If we learned anything from last year’s NKO, it’s that a little planning goes a long way. We spent the afternoon at the whiteboard, mapping out the game mechanics and prioritizing features: gravity, collisions, and shooting had to happen for us to have a game. Things like weapon variety, a scoreboard, and strategic game types would be cool, but only if we got done with the core features in time.
More than once an office mate would walk by and stop to see what we were staring at so intently, only to break into a broad grin as they realized what we were up to.
T-Minus 5 Hours
On our way back to the office after lunch, we ran into a couple Red Bull girls handing out free samples. This was a good omen.
T-Minus 4 Hours
We headed to Alfalfa’s, the local grocery store, for snacks and supplies. With the exception of some not-so-healthy energy drinks, we actually picked up some pretty solid food. Despite the conventional wisdom, you probably don’t want to be running on pizza for two straight days of hacking. We opted for corn chips, salsa, protein bars, beef jerky, trail mix, and lots of green tea-based beverages. All in all it seemed to work fine. We each got only 5-6 hours of sleep over the weekend, and didn’t have any major crashes or bouts of exhaustion.
T-Minus 3 Hours
In the final hours before kickoff, we held one more meeting to decide on architecture, map out specific tasks, determine which libraries we would need, and get a rough idea of who would be responsible for each item. We’re very fortunate to have a team with lots of skill overlap, so we knew it would be useful to set rough boundaries so we didn’t step on each others’ toes.
T-Minus Zero
git clone was run and we were off to the races.
I (@travhimelf) fired up Illustrator while the other guys (@bundy_kim, @joshonthweb, and @mtoymil) started installing the important modules and libraries we would need. I went to File > New Document, and Illustrator promptly decided to give me the pinwheel of doom. I briefly considered downloading Inkscape but decided it was best to not mess around, and set about installing a fresh copy of CS6.
While I waited for a fresh install, I Googled around for Mobile Frame Zero models (go ahead and browse for 20min, I’ll wait…), which I thought would make good reference material for our mech designs.
There’s plenty of mech art on the web, but most of it is uber-detailed — lego models made better references because they’re built with simple polygons. We had been toying with the idea of 16-bit-style art, but ultimately headed in a Geometry Wars direction after looking at this awesome style breakdown on Quora and deciding that we’d better keep it simple. (After all, none of us had ever designed game art before.)
For the animations I looked at sprite sheets from NES classics like MegaMan. The designers of those games achieved some pretty smooth looking sequences using only a handful of frames. We didn’t get a chance to implement walking around the surface of asteroids, but it was cool to get some rudimentary animation experience under my belt.
With the artwork well underway I decided to check in with Bundy, who was tackling the first major feature, and the key to the game design: gravity.
Check back tomorrow for Part 2, where Bundy walks through our implementation of radial gravity and the technologies behind it.
If you enjoyed disasteroids.com or this post, please consider throwing us a vote on our team’s page. It requires Facebook authentication to ensure that there isn’t rampant cheating, but the awesome NodeKnockout organizers @visnup and @gerad would never use your information in a malicious way. They’ve been crazy enough to organize this event for the 3rd year now despite the operational headaches of putting together such a massive event.
The story of eightbitbeats.com: How we built a social 8bit music sequencer in 48 hours
Node Knockout 2011, held between August 27 and 29, was a programming competition in which teams of developers were charged with designing, building, and launching a web app in a single weekend. The only basic requirements were that 1) each entry must use node.js, a javascript-based web server, and 2) that all designing, coding, tweaking, and deploying be done in the 48 hour period allowed for the contest.
Our team, made up of four developers from Mahalo.com, had not competed in last year’s Node Knockout, but we were excited and impressed by the entries we saw, and we decided we had to enter this time around. We tossed around a few concepts and eventually settled on the idea of eightbitbeats.com, a multi-user music sequencer built around an 8bit theme. At 5pm pacific time August 27, we got cracking.
But before we get into the nitty gritty, check out the finished product:
DESIGN PROCESS
We were fortunate enough to have a team with very strong backgrounds in design, UI, and product, which was great when it came time to bounce ideas around. Whenever one of us got stuck or needed input, we would call a huddle at the whiteboard, quickly churn through all the options, and make a decision, often within 20 minutes — sometimes much faster. The ability to do this came partly from having worked together in this way on a daily basis, but beyond that, we had a common understanding of user interface. We didn’t need to waste any time defending one idea or lobbying for another. We knew from experience which options would work, which wouldn’t, and which ones needed to be explored further.
In terms of visual design, the biggest challenge for eightbitbeats was making efficient use of space. Although available screen real estate gets larger every year, the standard width for a web page still tends to be around 960px, and our first mockups stuck to that standard. However, once we settled on a reasonable size for the sequence buttons (10x10) — and started laying out additional elements like user avatars, instrument selection, and labels for each instrument’s notes — we realized we would need increase the width, and eventually settled at 1300px. We made a judgement call that, at least for the purposes of the Node Knockout, we could probably assume our audience would be using fairly new hardware with wide-format, high-res displays. (In the days since the code freeze, we’ve had lots of feedback but no comments regarding page width, so it seems we guessed right.)
Next up, we needed to consider the sequencer matrix. We kept image assets to a minimum in favor of CSS3 to lighten the load, but even so we still had to contend with potentially hundreds of divs on screen at any time. Add to that the fact that each button needed several states including disabled, enabled, active, played, downbeat, measure downbeat, some of which would require nested divs, and we were quickly talking about thousands of elements. Rather than go down that road, we opted to use :before and :after pseudo classes to insert containers dynamically. (See Nicolas Gallagher’s excellent post for details on this method). Using this approach simplified our markup considerably and allowed us, with the help of less.css, to handle all the assorted states in a legible stylesheet (less than 400 lines total).
The design of the buttons was inspired by a fantastic gallery of control surfaces, mixers, and other pro audio gear at by livid instruments. We were lucky to have lots of great reference material and a well-defined real-world paradigm to follow. From there, we just had to choose a color scheme and start coding.
FRONT-END ARCHITECTURE
Due to the time limit, it was obvious that we needed to use a frontend framework to move quickly and keep logic and presentation separate. After zero deliberation we settled on Backbone.js. It gave us just the right amount of tools to be really powerful while remaining super lightweight and flexible. Using backbone’s collections allowed us to easily organize the elements of the player as well as index individual tracks, steps, and notes when it was time to play them. In addition, two devs on our team had already built multiple backbone applications together, so it was a prime choice for our frontend framework. Our architecture looked something like this:
Socket
App model
User model
Instruments collection
Instrument model
Player model
MegaMan collection
MegaMan models
Tracks Collection
Track models
Steps Collection
Step models
notes: [0,1,0,0,1,0,0,0,0,0]
ChatLog model
Messages collection
Message models
Note: It’s not shown in this diagram, but each model has a view associated with it to render its state into html. View the source of our page and you can see the full app unminified
App
At the top level we have an App model and the socket.io connection we communicate through. The App is responsible for initializing the application and is a good place to make references to app-wide resources like the User, Player, and ChatLog models. When the app starts it syncs state with the server and either creates itself based on data given, or, if there are no other users at the time, just renders itself into an empty player.
User
The User model just gets populated with the current user’s info and is referenced when permissions are calculated or messages are sent.
Instruments
This is simply a collection of all the instruments you can choose from. When ever you switch instruments it grabs the appropriate Instrument and mounts it on the Track model for reference when notes are played.
Player
The Player contains most of the meat of the application. It holds the Tracks and is used to store the state of the current iteration. Essentially what happens is when the player’s step attribute is incremented by the game loop, each the listening Tracks and the MegaMen respond by rendering themselves into the new state. Since the callbacks run asynchronously it makes it easy to keep all tracks and then Megaman in sync with eachother. The tracks also told to loop over the specified step’s notes and play the associated sound files.
We will be doing a more technical follow up post that goes over the code involved in making the player work
ChatLog
The ChatLog was thrown in last and we didn’t have time to complete it. As you may have noticed, there is no chat log in the app :). We are still mounting our Messages collection there so we can easily build it out later. The working part behaves as such: When a user chats, it adds a new Message to the Messages collection and sends that data through the socket with the username. This way we know which user to place the chat bubble in the other clients apps.
NODE.JS SERVER
For the backend architecture, we went with a classic client/server model where all events and changes are sent to the server. Upon receiving these messages, the server would record any changes, do any necessary processing, and finally notify all other clients that the change occurred, so that they could individually update their state.
In this way, eightbitbeat’s node.js instance primarily serves as a message relay system that sits between all players to make sure there aren’t any syncronization issues. It also ensures that there is an authoritative representation of the room that we can send to new user connections. This type of communication model is essentially what node.js is known to excel at, so once we coded and fired up the server, everything ran quite smoothly.
To get the client talking with the node.js server, we simply dropped in the amazing socket.io module. Using socket.io meant that all the we needed to do was establish a clear API and then split off to code our own respective pieces, without worrying about integration issues. During the entire length of the Knockout event, the members that were focused on the frontend coding didn’t need to look at the server code — nor did they have the time to. As long as communication between the client and server followed the “documentation” we outlined on the whiteboard, every piece of the app knew how to interact, and thus worked flawlessly.
Deployment
For deployment we decided to use Linode. Although there were a bunch of great deployment options provided by sponsors that we would have loved to try out, we had experience with Linode from personal projects, and knew that we could quickly get things up and running from scratch.
We were grateful that the Node Knockout team provided easy-to-follow steps for contestants to set up their local and production environments. It was clear that they really made an effort to provide solid resources and documentation to contestants to ensure maximum time could be spent coding and building a product.
THE GAME LOOP
We anticipated the main play loop to be one of the more challenging aspects of the project. It had to be optimized since it would be responsible for playing collections of audio samples precisely enough to pass as music to sensitive human ears. We began by looking to the internet for ideas for our implementation, and found this article on Javascript Game Loops by Arthur Schreiber. While the article covers some bits of screen rendering, it served as a solid starting point. The basic requirement of our play loop was that it had to loop through each step in the sequence at a consistent rate determined by a beats per minute (BPM) value and then play every active note in that step. This was easily achieved with the following:
// Pseudo javascript for demonstration
Player.play = (function() {
// Set up timing variables
var skipTicks = 60000 / 4, // 60000ms per min over 4ticks per beat
nextTick = (new Date).getTime();
return function() {
while ((new Date).getTime() > nextTick) {
// While it's time to tick, tick until it isn't time anymore.
// Do the work
$.each(Player.notes, function(note) {
note.play();
});
// Increments nextTick by the correct ms per tick
nextTick += skipTicks / Player.bpm;
}
};
})()
// Start the loop
Player.loopInterval = setInterval(Player.play, 0);
However, we noticed that it did not perform very well with a reasonable amounts of notes, so we built a sound manager object to collect every sound that needed to play on a given step, and only play each unique sound once. We achieved this by throwing every sound into a makeshift hashset (we used an associative array) and then triggering a play on each of those sounds at the end of the loop. Then we had the idea for the manager to pre-cache the notes in the coming step ahead of time, so that they would be ready to play immediately upon each new time step.
soundManager = (function () {
// Make-shift HashSets
var toPlay = {}; // Sounds to play for the current step.
var cache = {}; // Sounds to cache for the next step.
return {
// Add a file if it’s not been added.
addFile: function(file) {
if (!cache.file) {
cache[file] = file;
// This value should probably just be set to true.
}
},
// Play the collected sounds.
play: function() {
// Play sounds if we have them
if (!!_.size(toPlay)) {
_.each(toPlay, function(name) {
playSound(name);
});
}
// Update the sets.
toPlay = $.extend({}, cache);
cache = {};
}
};
})();
This was as far as we got with the loop optimizations in the allotted 48 hours. We would have liked to spend a bit more time tinkering with the loop, but it is currently holding up with 4 to 5 users worth of notes. By nature, sequencers are intimately bound with loops. At the highest level, they contain the user-facing music loop, while further down they rely on precise “tick” timing loops. Game loops were a relatively new topic to most of us. We had fun learning about them and are excited to correct our mistakes in coming versions of the application.
WRAP UP
We were pretty beat by the time Sunday afternoon rolled around, but overall very pleased with the amount of work we were able to crank out. We should note one key habit we nailed down very early Saturday morning. We realized the Feature Creature can run rampant during events like this, with minds stuck in high gear and ideas flying every which way. That’s why the minute our feature list became more than a handful of items, we drew a thick line down the middle of the board and labeled the two sides “must have” and “nice to have”. Any time a new (or previously forgotten) feature was suggested, it went into one of the two buckets, and we were sticklers about adding any features to the “must have” section. Through this process, we made sure we wouldn’t burn time on anything unless it truly fit into our notion of the minimum viable product. This saved us a ton of time (and headache), and we finished all the “must haves” on Sunday morning with time to spare, which gave us the opportunity to work on some fun, non-critical bits in the final hours.
At the time of this writing, all that remains is for judging and voting to finish on September 6th. We would be thrilled if you took the time to visit eightbitbeats.com and play around with our creation. If you dig it and want to support us in the contest, you can head over to the Node Knockout site and cast your vote (it’s a little tricky… make sure you click the heart after logging in with Facebook), or toss us an upvote on Hacker News. Or if you don’t dig it, leave a comment and let us know why! We’ll be continue development once the competition is over, so we’d love to hear your feedback suggestions.