Together with my friend Simon, I’ve built a web-based marble run simulator called Marbleo.us. Here’s a fun example map.
It was our submission for the annual informatiCup, a competition for students interested in computer science. Unfortunately, it did not make it into the final round but I still very much consider this to be a success.
So let me give you a quick overview of what we did, how we did it and what I’ve learnt building Marbleo.us.
No data store is faster than no data store.
Early on, we decided to give users the ability to share their creation with their friends. However, learning from the popular sandbox game Minecraft, we wanted to have a counter measure against what is called griefing, malicious players destroying your creation.
After considering different data stores and ways to handle both read-only and read-write links to the maps, we came up with a far more elegant solution: encoding the whole map into the fragment of the URL.
Standing on the shoulders of github
We are using git, which is unfortunately not used often in our academic circles, but it meant the natural choice for hosting our repository was github. Using the awesome github-pages feature, we can also serve http://marbleo.us from their servers at no additional cost and with great response times.
I like my sugar with coffee and
Only a short time before the start of this project came I across CoffeeScript - thanks to Dmytri for tipping me off.
Jeremy also wrote backbone.js which, amongst other things, powers the SoundCloud mobile site.
Drawing the marble run
After briefly investigating CSS-3 transformations like in this article for composing the map, I decided to go with a more straightforward approach by drawing the maps onto a canvas.
A single block is then stitched together from multiple components, cached, and placed on the canvas at its correct position in space. The individual components are all stored in one file to minimize requests.
Additionally, a second, smaller canvas is used to allow the use to drag a stack of blocks freely across the browser window without doing costly redraws at every mouse position.
We also save yet another representation of the marble run, to quickly determine face of a block is under the current mouse position. This also allows us to draw a cute little hand cursor whenever the user places the mouse over a block with pixel precision.
We simply compile a map using a special, color-coded sprite and then quickly look up what color (if any) is stored at the mouse position. Blue for the top, red and green of the block’s sides and black for the floor.
Now we have the run ready, here comes the marble!
Animating the marble
We use a mixed approach to move the marble over the run by discriminating between two different types of movement: free movement in space and movement on the tracks of the marble run.
Movement in space
This part of the physics engine engine was simple, by using a basic collision detection we check if the marble collided with a block and then invert the appropriate velocity components. If the marble is rolling over the top of a block, we also test if it crosses a track which leads us to the next point:
Movement on the track
For every block with its different grooves and tunnels, we defined small, directed graphs that we then join together to form the large, overall track graph. It’s important that the nodes of this graph have a degree of at most two, so that there is no ambiguity in which node to pick.
We then move the marble along these paths from node to node, until we encounter a node that has no neighbor we haven’t visited before. It is then that we leave the track and switch back to the previous mode of movement.
Drawing the marble
Now that we have the animation of the marble in place, we of course need to draw it, but to do that, we first have to figure out where the marble is visible.
First we draw the the marble onto a blank, offscreen canvas.
Then we iterate over all blocks in map that could potentially obstruct our view to the marble, drawing only the parts that are actually in the way onto another offscreen canvas.
As a last step, we join our two canvases by drawing the canvas that contains the obstructing parts onto the marble using the blend mode
destination-out, this removes the marble everywhere there is a block and leaves us with a nice precisely drawn marble.
I hope you enjoy marbleo.us and found this write-up useful. If you have any more questions or comments, feel free to drop me a line at firstname.lastname@example.org.