Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs or learn how to Submit Your Own Blog Post
This article goes over the general flow of programming and game state in Full Screen Mario, the HTML5 remake of Nintendo's Super Mario Bros.
In this article, I’ll go over the general flow of programming in Full Screen Mario for anybody interested in seeing how it works. If you're already an experienced game developer you'll probably know much of this information, but I hope it may serve as a useful guide for programmers who are just starting out modding Full Screen Mario.
The Past
I started working on Full Screen Mario around October 2012 because I was restless in class and needed something more engaging than the lecture on human psychology. Back then it was just a little pet project to be done by Thanksgiving; it seemed fun to try out making the first Super Mario Bros level in Javascript. I had been a huge SMB kid years ago, and wanted to capture that game in a more modern form than plain emulators. Older games are a lot simpler to recreate than newer games and I didn’t think it would be too much trouble – no AI, no parallax scrolling, nothing like that. What innocence. I’ve been working on this thing for about a year now, and it’s not even close to being done. Working on side projects like this one is one of the best ways for aspiring programmers and developers to get their feet wet; I certainly wouldn’t know a lot of what I do now if it weren’t for hours spent slaving away at this thing. It’s super thrilling how great the response to the project has been, and as fun as it is now I can’t wait to see how far it’ll go over the next year!
The Engine
The two main components of Full Screen Mario are gameplay (manipulating objects) and graphics (drawing objects), with a set of support modules, such as input, map loading, etc. The majority of game code resides in the gameplay section, since it involves the physics engine, events scheduler, and object manipulations. The game also has a random map generator, which manipulates map loading, and a map editor, which manipulates gameplay as well as map loading.
Game State
SMB is simple enough that there doesn’t have to be too many complicated components. For globally used singleton variables, there are lists of game objects, a current map, and a few extras like window.paused and window.mario. There is an upkeep function that fires at the refresh rate of 60 fps, and calls each of the modules in order.
Gameplay
Physics engines are always a pain to make, so it was a huge relief to see that SMB barely does anything more than collision detection and gravity. All games use some sort of detection algorithm to check objects against only nearby objects; Full Screen Mario utilizes a grid approach where the screen is split up into a grid of squares. Each object knows which square (referred to as a 'quadrant' in the game code) it’s in and vice versa, so when the game has to check for collisions each object will only be checked against others that share a quadrant.
Collisions in SMB are actually pretty simple; all objects in the game can be characterized as either a “character” (Mario, Goomba), “solid” (Brick, Block), or “scenery”. Only characters are affected by movement physics, and sceneries don’t do anything other than get drawn. When two non-Mario characters collide they’ll either do nothing or bounce off each other, while Mario colliding with an enemy will kill or be killed. Characters hitting solids will just land on the solid, in which case they’ll stop being affected by gravity, or will stop/reverse movement if it’s a horizontal collision.
Now that collisions have been resolved, maintenance of game objects has to occur. Objects who have died or are too far off screen are removed; otherwise, if they have a .movement function, it is called. Part of the beauty in how Nintendo designed the original SMB stems from how incredibly simple the character movement functions are: there are only a few, and (almost) none of them rely on the player! They are:
moveSimple: The character simply maintains an x-velocity, and reverses direction if it hits a solid.
moveSmart: The same as moveSimple, but it’ll reverse direction instead of falling (this was actually pretty hard to program!).
moveJumping: The same as moveSimple, but with a y-velocity given whenever it lands.
moveFloating: The character keeps constant x- and y-velocities, and if certain endpoints are reached gradually reverses direction.
moveHammerBro: The character slides side to side, and throws hammers on a certain interval. This does interact with the player, but only to look in the right direction.
moveBowser: The same as moveHammerBro, but with fires on an interval as well.
Once everything has moved, in-game events must be processed. A timer is kept that increments on every upkeep, meaning events may be stored under the timer value at which they’ll be executed (fans of operating systems will recognize this design). An event is stored as a function to execute, whether it’ll be repeated and times / frequency if so, along with a list of arguments. The native JavaScript setTimeout / setInterval can’t be used because pausing the game messes the timing up, and because switching between areas requires the old area’s events be canceled.
The most common in-game events are sprite animations, like characters changing walking animations or blocks flashing different colors. These are set with a special function, "addSpriteCycle", which creates an event interval to change the character's className. A special version of that function entitled "addSpriteCycleSynched" is used for characters that have to animate in time with each other, like coins or blocks; it works mostly the same, but makes the animation occur in sync with the game count.
Graphics
Thanks again to SMB’s simplicity, every single object can be thought of as a rectangular image somewhere on the screen. Full Screen Mario actually just used HTML div elements with background images for everything originally, which was horribly inefficient but much easier to debug. You can see remnants of that code in the usage of a .className string as the storage method for object classes, rather than an array.
These days a “library” of sprite data is used instead of background images. This is accessed the same way as CSS classes; library.sprites.characters.goomba.normal, for example, is the typical Goomba sprite. I won’t get into too much detail on the storage format, but they’re basically .gif images (note to editor: I could probably write an article on the graphics…). Whenever an object changes its appearance, it finds and processes the corresponding sprite in the library; for performance’s sake, library.cache keeps a record of processed images that can be re-used. Looking for an object in the library also involves the current setting (“Overworld”, “Underworld”, etc.) so the game can differentiate between different areas’ sprites.
Now that each object knows its image, the refillCanvas function can just step through each object and draw it onto the screen using canvas.drawImage, or canvas.fillRect for patterns. Astute readers may note the lack of more advanced optimizations, especially redraw regions, off screen buffers, or WebGL – those will be added with time.
Map Loading
For simple 2D horizontal platformers, programmers can think of maps as lists of objects sorted by their x-location. As Mario moves to the right, he comes across new objects in that sorted order. You can therefore keep track of which object was the last to be added, and whenever the game scrolls to the right, have it check whether the next object is close enough (a simple calculation of whether the object's x-location is less than the screen's rightmost border plus a small constant).
SMB is a bit more complicated, since solids spawn before characters: if they didn't, characters that reverse direction could walk off the map before the player ever sees them. Therefore, Full Screen Mario maps keep three separate lists, for characters, solids, and sceneries. Also, the game can't check spawning at every movement, since that would be detrimental to performance and make objects right next to each other spawn at potentially different times. Instead, the spawnMap function ties into the game's collision detection quadrants. Quadrants scroll with the screen, and whenever the leftmost column gets too far to the left, it's deleted and a new column is added to the right. Perfect! Just call spawnMap whenever this happens, and the game works surprisingly close to the original.
Putting it all together, with a bit more information than before, you get a horribly oversimplified chart of the game flow:
Most of the complexity during development came from ironing out the kinks of the physics engine, since it’s incredibly difficult to get it the exact same as the original. Physics engines are hard enough to get right as it is, and getting it as close as possible is a painful, ongoing process.
Random Map
It's funny how some parts of a project can get away from you. Each part of the random map (Overworld, Underworld, etc.) took about two or three hours each to make the first time, but overall took a week or two each to perfect. Procedural level generation is traditionally pretty difficult; too many obstacles, and the game is too difficult or physically impossible; not enough, and it becomes boring. Well designed level generators maintain a "wavy" flow of object placement and difficulty such that the user can gently alternate between intensities, without ever reaching too much or too little difficulty. It takes a ridiculous amount of tinkering to get your algorithm reliably obeying that curve. Just one impassible peak can throw off the user's flow, and then there goes the neighborhood. Full Screen Mario suffers from this occasionally, but for the most part is fairly stable.
Thankfully, Mario and other simple 2D platformers are almost completely linear, so it just has to make the level passable, and intersperse a few enemies. It does so by continuously adding regions of floor of various length. On these floor regions, a new set of obstacles is added every few blocks in a way that the user can traverse between and through them. These range from a pipe to a 1-2 levels of bricks/blocks to floating platforms; oftentimes random enemies are placed on the first two. Unusually difficult obstacles and enemies, like Hammer Bros or Bloopers, are naturally more infrequent. Furthermore, there are opportunities to switch to different settings: pipes present relatively common optional switches, while the user's current setting will forcibly switch occasionally.
Map Editor
Map editors such as those in Source and Halo typically work alongside a regular game session and provide the user with an extra set of tools during play. This made it relatively straightforward to code for object placement: during editing mode, a “follower” object of the user’s choosing is kept on the screen and treated exactly like any other object, except instead of having any velocity or collision detection enabled, it is moved along with the mouse. When the user clicks, it’s added to the screen and a new follower is created in its place. Making a map function out of editing mode is conceptually simple as well: for each object on the screen, add the appropriate creation function (a smart Koopa at (32, 64) would be “pushPreThing(Koopa, 32, 64, true);\n”). The JSON equivalent would be something like {type: ”Koopa”, position: [32,64], args: [true]}.
Whether the maps are stored as function strings, as is currently true, or JSON objects, localStorage provides a nifty way to keep the user’s currently edited map. Whenever it’s saved, Full Screen Mario stores a string of the map function into localStorage.editorLastFunc. On editor startup, this location is checked for a string – if there is one, and it’s valid, it’s used as a map function to start off.
The Release
The one part of Full Screen Mario’s development that completely took me by surprise was how difficult it was to release the thing properly. There’s a huge difference between finishing a project and getting other people to play it. I’d put the game up on GitHub and fullscreenmario.com months before it blew up, and even posted it on Reddit without much response. You’d be surprised how competitive some gaming subreddits can be (though not nearly as bad as 4chan’s /v!). It was only after an r/javascript post that the game started to attract more attention, and once BoingBoing posted an article, a bunch of news sites got wind of the game and all of a sudden the server had to deal with 30k hits a day. I ended up tapping Cloudflare for CDN/caching to handle the unexpected traffic, and thank heavens it was “only” a day’s worth of visitors ruined. The week after discovery saw an extra 100k unique visitors a day, surpassing 500k unique visitors and 2 million pageviews on a few days, for a total peak of 2 million unique visitors and 6 million pageviews the following week. It's dropped down to about 100k visitors a day as of this blog post, but I'm still floored by the terrific response the game's received.
Once you’ve gotten people playing your game, it’s extremely important they become immersed as fast as possible. Too many sites and games make the mistake of layering unnecessary levels of interaction between user and content; every single mouse click or keyboard press is a time and attention barrier preventing you from reaching the audience. No matter how great the content, if it takes fifteen seconds of active user participation to start, people are going to start the experience off bitter and annoyed. Full Screen Mario solves this by immediately starting the game when the page is loaded – no loading screen (though there are load times, they’re kept to a minimum), no play button, just pure engagement. As put by Wayne Brady, “this is not an option.” The instant you go to the site, you’re already playing.
The Future
As with all beta releases, there is a *lot* of stuff to be added or improved. The most glaring fault is the game’s browser exclusivity, which happened to be the first and most common complaint from 4chan.org/v. It does work on Firefox and Opera now (unlike when it was first released, when it was Chrome xclusive); however, performance is still only optimal in Chrome. Following that is performance – the game doesn’t make use modern methods like redraw regions or WebGL, an optional wrapper for which would be a huge performance boost. There’s also a lot of potential for social connectivity with the map editor that isn’t being explored at all. Facebook sharing of maps would be great for bringing people together on the site, and beyond that a Happy Wheels kind of server-side app would be even better.
In my opinion, the most important part of development going forward is code cleanup. This thing started as a pet project and slowly grew into a giant monstrosity of global variables and almost completely procedural code - neither of which are really acceptable in 2013. Most sections of the game could be spun off into their own separate modules fairly easily - especially sprite rendering and events timing, which I plan to be releasing in that order fairly soon. Even if Nintendo Cease & Desists the website (hasn't happened yet!), creating separate modules would allow other projects and developers to prosper in the future using this code.
At any rate, it’s hard to stress enough how excited I am to see how far forward Full Screen Mario goes! I really do hope this project starts to grow and expand the way it ought to; it’s got a lot of potential that can definitely be reached with some community participation. You can play the game now on http://www.fullscreenmario.com, and if you’d like to report a bug or pitch in with the coding, the repository is located at http://www.github.com/DiogenesTheCynic/FullScreenMario. Thanks for reading, and I hope you enjoy the game!
You May Also Like