Mario5 - CodeProject
Mario5 - CodeProject
Mario5
Florian Rappl Rate me: 4.99/5 (169 votes)
Recreating a famous jump and run game for playing and creating own levels in the webbrowser.
Introduction
https://www.codeproject.com/Articles/396959/Mario5 1/18
9/2/2021 Mario5 - CodeProject
In the history of computer games some games have created and carried whole companies on their shoulders. One of those games is
certainly Mario Bros. The Mario character first appeared in the game Donkey Kong and became very famous within its own game series
starting with the original Mario Bros. in 1983. Nowadays a lot of spin-offs and 3D jump and runs are being produced centering the Mario
character. In this article we will develop a very simple Super Mario clone, which is easily extendible with new items, enemies, heros and
of course levels.
The code of the game itself will be written in object oriented JavaScript. Now that sounds like a trap since JavaScript is a prototype
based scripting language, however, there are multiple object oriented like patterns possible. We will investigate some code which will
give us some object oriented constraints. This will be very useful to stay in the same pattern through the whole coding.
Background
Mario5 YouTube
The original version of this application was developed by two students who took my lecture on "Programming Web applications with
HTML5, CSS3 and JavaScript". I gave them a basic code for the engine and they developed a game including a level editor, sounds
and graphics. The game itself did not contain a lot of bugs, however, the performance was rather poor and due to rare usage of the
prototype properties the extensibility was also limited. The main performance burner was the usage of the jQuery plug-in Spritely (which
can be found here). In this case I am the one to blame, since I recommended using it for simplicity. The issue here is that Spritely itself
does a good job on doing one animation, but not a hundred or more. Every new animation (even though spawned at the same moment)
will get its own timed interval recall loop.
For this article I decided to focus on the main things of the game. Through this article we will rewrite the whole game - with the benefits
as explained above:
The last statement sounds like a maniac writing this article. However, this is in my opinion quite important. Let's take the following code
to show my point:
$(document).ready(function() {
var sounds = new SoundManager();//**
var level = new Level('world');//world is the id of the corresponding DOM container
level.setSounds(sounds);//*
level.load(definedLevels[0]);
level.start();
keys.bind();
});
Now this does not look so nasty, but this is in fact everything that is needed to play a game of Mario with HTML5. The details we have
left out here will all be explained later. Back to the statement of above we see the line marked with a two star comment (//**): Here a
new instance of the sound manager class is created. This one will also load sound effects. What if we want to skip this line? We would
not have a working instance of the sound manager. Now the next thing to notice is that the instance of the sound manager is not saved
in a global scope, but just locally. We can do that because no object in the whole game requires a certain instance of this class. What is
done instead? If an object wants to play a sound it calls a method that is provided by the level (every object has to belong to a level in
order to exist; since a level class is the only place where the game objects are created).
Now this is where the line with the single star comment (//*) comes into play. If we do not call the setSounds() method of a
level instance the level will not have a proper sound manager class instance attached. Therefore all requests to play a sound by any
object will be trashed. This makes the sound manager class plugable, since we just have to remove two lines of code to completely
remove the sound manager. On the other side we only have to add two lines of code. This of course would be something that can be
achieved more elegant within C# by using reflection (as required by dependency injection or other patterns).
The rest of this small code is just to load a starting level (here we use the first one in a list of predefined levels) and start it. The global
keyboard object called keys can bind() or unbind() all key events in the document.
https://www.codeproject.com/Articles/396959/Mario5 2/18
9/2/2021 Mario5 - CodeProject
We will skip the sound manager implementation in this article. There will be another article about a good level editor and various other
interesting things, one of them will be the implementation of the sound manager. The basic document outline for the Super Mario game
looks like the following:
<!doctype html>
<html>
<head>
<meta charset=utf-8 />
<title>Super Mario HTML5</title>
<link href="Content/style.css" rel="stylesheet" />
</head>
<body>
<div id="game">
<div id="world">
</div>
<div id="coinNumber" class="gauge">0</div>
<div id="coin" class="gaugeSprite"></div>
<div id="liveNumber" class="gauge">0</div>
<div id="live" class="gaugeSprite"></div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="Scripts/testlevels.js"></script>
<script src="Scripts/oop.js"></script>
<script src="Scripts/keys.js"></script>
<script src="Scripts/sounds.js"></script>
<script src="Scripts/constants.js"></script>
<script src="Scripts/main.js"></script>
https://www.codeproject.com/Articles/396959/Mario5 3/18
9/2/2021 Mario5 - CodeProject
</body>
</html>
So after all we do not have much markup here. We see that the world is contained within a game area. The game area does include the
gauges (and some sprites to animate the gauges). This simple mario game only contains two gauges: one for the coins and another
one for the number of lives.
A really important section is the list of JavaScripts that will be included. For performance reasons we place them at the bottom of the
page. This is also the reason why we get jQuery from a CDN (which is Google in this case). The other scripts should be packed into one
and minimized (this is called bundling and is one of the included features of ASP.NET MVC 4). We will not bundle these scripts for this
article. Let's have a look at the content of those script files:
The testlevels.js file is quite big and should be minimized. It contains the premade levels. All of those levels where implemented
by the two students of my lecture. The first level is a clone of the first level of Super Mario Land for the classic GameBoy (if I am
not wrong here).
The oop.js file contains the code to simplify object oriented JavaScript. We will discuss that in a minute.
The keys.js file creates the keys object. If we want to design a multiplayer or other features we should also modulize this script
(as we did with the sound manager class).
The sound manager is written in the sounds.js file. The basic idea is that the Web Audio API (Official Website) could possibly
overtake the current API. Right now the problem is that the Web Audio API is limited to Google Chrome and some nightly builds
of Safari. If the distribution is right this could be the way to get the audio we need for games in the browser.
The file constants.js contains enumerations and very basic helper methods.
All other objects are bundled in the file main.js.
Before we go into details of the implementation we should have a look at the CSS file:
@font-face {
font-family: 'SMB';
src: local('Super Mario Bros.'),
url('fonts/Super Mario Bros.ttf') format('truetype');
font-style: normal;
}
#game {
height: 480px; width: 640px; position: absolute; left: 50%; top: 50%;
margin-left: -321px; margin-top: -241px; border: 1px solid #ccc; overflow: hidden;
}
#world {
margin: 0; padding: 0; height: 100%; width: 100%; position: absolute;
bottom: 0; left: 0; z-index: 0;
}
.gauge {
margin: 0; padding: 0; height: 50px; width: 70px; text-align: right; font-size: 2em;
font-weight: bold; position: absolute; top: 17px; right: 52px; z-index: 1000;
position: absolute; font-family: 'SMB';
}
.gaugeSprite {
margin: 0; padding: 0; z-index: 1000; position: absolute;
}
#coinNumber {
left: 0;
}
#liveNumber {
right: 52px;
}
#coin {
height: 32px; width: 32px; background-image : url(mario-objects.png);
background-position: 0 0; top: 15px; left: 70px;
}
#live {
height: 40px; width: 40px; background-image : url(mario-sprites.png);
background-position : 0 -430px; top: 12px; right: 8px;
https://www.codeproject.com/Articles/396959/Mario5 4/18
9/2/2021 Mario5 - CodeProject
}
.figure {
margin: 0; padding: 0; z-index: 99; position: absolute;
}
.matter {
margin: 0; padding: 0; z-index: 95; position: absolute; width: 32px; height: 32px;
}
Now this not very long again (but we did not have much markup either). On top we introduce some fancy font in order to give our game
a Mario like look (type-wise). Then we just set up everything for the characteristic 640 x 480 pixel resolution. It is quite important that the
game does hide any overflow. Therefore we can just move our world in the game. This means the game acts like a kind of view. The
level itself is placed in the world. The gauges are placed in the first rows of the view. Every figure will have a CSS class called
figure attached. The same goes for matter like ground, decoration elements or items: those elements have a CSS class called
matter. Quite important is the property z-index. We always want a dynamic object to be in front of a static one (there are
exceptions, but we will come to that later).
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
return ret;
};
})(name, prop[name]) :
prop[name];
}
return Class;
};
})();
This code is highly inspired by prototype and has been constructed by John Resig. He wrote an article about the whole coding issues at
his blog (Article from John Resig about the OO JavaScript code). The code is wrapped in a directly executed anonymous method in
order to scope internal variables. The Class object is an extension to the window object (which should be the underlying object,
i.e., this if this script file is executed from a web browser).
My extension to this code is the possibility to name the class. JavaScript does not have the powerful reflection properties, which is why
we are required to put a little bit more effort into the class's description process. When assigning the constructor to a variable (which we
will see in a moment) we have the option to pass a name of the class as second argument. If we do this then a reference to the
constructor is being placed in the object reflection with the second argument as the property name.
Here we are creating a class called TopGrass, which inherits from the Ground class. The init() method represents the
constructor of the class. In order to call the base constructor (which is not required) we have to call it over the this._super()
method. This is a special method which can be called within any overridden method.
One important remark here: It is important to distinguish here between real polymorphism (which can be done in object oriented
languages with static types like C#) and the one presented here. It is obviously not possible to access the parent's method from outside
(since we cannot change how we see the object - it's always a dynamic object). So the only way to access the method of the parent is to
call the this._super() method within the corresponding overridden method. It should also be noted that this statement is not
absolute, however, it is true with the code presented above.
The Ground class is quite uninteresting (just a middle layer). So let's have a look at the base class of Ground, called Matter:
https://www.codeproject.com/Articles/396959/Mario5 6/18
9/2/2021 Mario5 - CodeProject
Here we extend the Base class (which is one of the top classes). All classes that inherit from Matter are static 32 x 32 pixel blocks
(they cannot move) and contain a blocking variable (even though it could be set to no blocking, which is the case e.g. for decorations).
Since every Matter as well as Figure instance represents a visible object we need to create a proper view for it (using jQuery).
This also explains why the setImage() method has been extended in order to set the image on the view.
The same reason can be applied for overriding the setPosition() method. The addToGrid method has been added in order
to give child classes the possibility to deactivate the standard behavior of adding the created instance to the obstacles array of the
given level.
var keys = {
//Method to activate binding
bind : function() {
$(document).on('keydown', function(event) {
return keys.handler(event, true);
});
$(document).on('keyup', function(event) {
return keys.handler(event, false);
});
},
//Method to reset the current key states
reset : function() {
keys.left = false;
keys.right = false;
https://www.codeproject.com/Articles/396959/Mario5 7/18
9/2/2021 Mario5 - CodeProject
keys.accelerate = false;
keys.up = false;
keys.down = false;
},
//Method to delete the binding
unbind : function() {
$(document).off('keydown');
$(document).off('keyup');
},
//Actual handler - is called indirectly with some status
handler : function(event, status) {
switch(event.keyCode) {
case 57392://CTRL on MAC
case 17://CTRL
case 65://A
keys.accelerate = status;
break;
case 40://DOWN ARROW
keys.down = status;
break;
case 39://RIGHT ARROW
keys.right = status;
break;
case 37://LEFT ARROW
keys.left = status;
break;
case 38://UP ARROW
keys.up = status;
break;
default:
return true;
}
event.preventDefault();
return false;
},
//Here we have our interesting keys
accelerate : false,
left : false,
up : false,
right : false,
down : false,
};
Another way to set this up would be to use the power of jQuery. We could assign the same method to the up and down key event with
some custom argument. This argument would then determine which event was responsible for the method call. However, this way it is a
little bit cleaner and more understandable.
CSS Spritesheets
All graphics in the game are done by using CSS spritesheets. This feature is quite simple to use if we know the following lines. First of
all in order to use an image as a spritesheet we need to assign the image as a background image to the corresponding element. It is
important to not disable the background repeat, since this will give us the advantage of periodic boundary conditions. Usually this would
not result in any bad consequences, however, with periodic boundary conditions we can do a lot more.
The next step is then to set a kind of offset to the background image we just assigned. Usually the offset is just (0, 0). This means that
we top left coordinate of our element is also the top left coordinate of our spritesheet. The offset we will enter is relative to the element,
i.e. by setting an offset of (20, 10) we would set the top left (0, 0) coordinate of the spritesheet to 20 pixels to the left side and 10 pixels
to the top side of the element. If we use (-20, -10) instead, we will have the effect of having the top left (0, 0) coordinate of the
spritesheet outside of our element. Therefore the visible part of the image will be within the image (and not on the border).
https://www.codeproject.com/Articles/396959/Mario5 8/18
9/2/2021 Mario5 - CodeProject
This illustrates how spritesheets work in CSS. All we need are just our coordinates and we are good to go. Overall we could distinguish
between homogeneous spritesheets and heterogeneous spritesheets. While the first one does have a fixed grid (e.g. 32px times 32px
for each element, resulting in easy offset calculations), the latter one does not have a fixed grid. The illustration shows a heterogeneous
spritesheet. If we create a spritesheet for our homepage in order to increase performance by decreasing HTTP requests, we will usually
end up with a heterogeneous spritesheet.
For animations we should stick to homogeneous spritesheets in order to decrease the amount of information necessary for the
animation itself. The game uses the following piece of code to execute spritesheet animation:
this.frameID = id;
}
this.frameCount = 0;
this.currentFrame = 0;
https://www.codeproject.com/Articles/396959/Mario5 9/18
9/2/2021 Mario5 - CodeProject
this.frameTick = frames ? (1000 / fps / constants.interval) : 0;
this.frames = frames;
this.rewindFrames = rewind;
return false;
},
clearFrames: function() {
this.frameID = undefined;
this.frames = 0;
this.currentFrame = 0;
this.frameTick = 0;
},
playFrame: function() {
if(this.frameTick && this.view) {
this.frameCount++;
We included the spritesheet functionality in the most basic (game) class, since every more specialized class like figures or items will
inherit from this class. This guarantees the availability of spritesheet animation. Basically every object has a function to setup the
spritesheet animation called setupFrames(). The only parameters that need to be specified are the number of frames per second
(fps) and the number of frames in the spritesheet (frames). Usually animation is executed from left to right - therefore we included
the parameter rewind to change the animation to be from right to left.
One important thing about this method is the optional id parameter. Here we can assign a value that could identify the current
animation. This can then be used in order to distinguish if the animation that is about to be set up is already running. If this is the case
we will not reset the frameCount and other internal variables. How can this code be used within some class? Let's see with an
example of the Mario class itself:
this._super(vx, vy);
},
walkRight: function() {
if(this.state === size_states.small) {
if(!this.setupFrames(8, 2, true, 'WalkRightSmall'))
https://www.codeproject.com/Articles/396959/Mario5 10/18
9/2/2021 Mario5 - CodeProject
this.setImage(images.sprites, 0, 0);
} else {
if(!this.setupFrames(9, 2, true, 'WalkRightBig'))
this.setImage(images.sprites, 0, 243);
}
},
walkLeft: function() {
if(this.state === size_states.small) {
if(!this.setupFrames(8, 2, false, 'WalkLeftSmall'))
this.setImage(images.sprites, 81, 81);
} else {
if(!this.setupFrames(9, 2, false, 'WalkLeftBig'))
this.setImage(images.sprites, 81, 162);
}
},
/* ... */
});
Here we override the setVelocity() method. Depending on the current state we execute the corresponding function like
walkRight() or walkLeft(). The function then looks at the current state in order to decide which animation to apply. Here we
bring the optional id parameter into play. We change the current spritesheet location only then when we could apply a new animation.
Otherwise the current animation seems still to be valid, resulting in a valid spritesheet location as well.
https://www.codeproject.com/Articles/396959/Mario5 11/18
9/2/2021 Mario5 - CodeProject
The game has been structured to show relations and dependencies. One of the benefits for such a structure is the ability to extend the
game. We will investigate the extension process in the next section.
Inheritance is just one of the factors that writing object oriented JavaScript brings us. Another one is the ability to have something like
types (instances of our classes). Therefore we can for instance ask if an object is an instance of a certain class. Let's have a look at the
following code as an example:
if(fig.y === this.y + 32 && fig.x >= this.x - 16 && fig.x <= this.x + 16) {
if(fig instanceof ItemFigure)
fig.setVelocity(fig.vx, constants.bounce);
else if(fig instanceof Enemy)
fig.die();
}
}
https://www.codeproject.com/Articles/396959/Mario5 12/18
9/2/2021 Mario5 - CodeProject
},
})
The code snippet shows part of the Item class. This class contains a new method bounce(), which let's the box go up and down a
bit by setting the isBouncing property to true. Like in the original Mario game you can kill enemies which are unluckily standing
on the bouncing item. The other popular case is to give instances of ItemFigure (like the mushroom) additional momentum in y
direction (if above the bouncing item).
There are several extension points in the game itself. An obvious extension point is to build a new class and give it a proper reflection
name. Levels can then use this name, which results in the level creating an instance of this class. We start with an easy example of
implementing new kind of decoration: A left hanging bush!
This was pretty easy. We just have to inherit from the Decoration class and set another image over the setImage() method.
Since decorations are non-blocking we cannot specify a blocking level here (as with classes inheriting from Ground). We name this
new decoration class bush_left.
https://www.codeproject.com/Articles/396959/Mario5 13/18
9/2/2021 Mario5 - CodeProject
Now let's consider the case of extending the game with a new enemy: the ghost (not included in the source code)! This is a little bit
harder, but not from the principle. The problems just come with the rules that this specific type of enemy has to follow. The basic
construction is straight forward:
So the first thing to notice here is that we do not call the _super() method in the die() method. This results in the Ghost not
being able to die (since he or she is already dead). This is actually one of the rules. The other rules are:
While other routines just abuse the setVelocity() method it would be quite helpful to override the move() method in this
case. There are two reasons for that:
With this knowledge we can now include the rest of the ghost enemy, resulting in the following code:
https://www.codeproject.com/Articles/396959/Mario5 14/18
9/2/2021 Mario5 - CodeProject
if(mode)
this.setPosition(this.x + dx, this.y + dy);
} else
this.setMode(ghost_mode.sleep, this.direction);
},
hit: function(opponent) {
if(opponent instanceof Mario) {
opponent.hurt(this);
}
},
}, 'ghost');
Here all of our rules have been applied. The ghost will only do a move if Mario is within a certain range (800 pixels in this case). In order
to work coherent we introduce a new enumeration object, called ghost_mode:
var ghost_mode = {
sleep : 0,
awake : 1,
};
We also need to introduce some new sprites. In this case we've just added a new image that includes all the sprites. The path is saved
in images.ghost and leads to the following image:
Points of Interest
This article is named Super Mario for HTML5, however, there is not really much HTML5 in this demonstration. The two features that are
provided by HTML5 are the <canvas> element and the <audio> element. Both have not been used for this demonstration. While
the first one is interesting for the level editor (we will have a look at that in the next article), the latter is of course already interesting for
the game as well. I decided to exclude it in this demonstration in order to keep the size of the source as small as possible.
Even though JavaScript is a dynamic language we can still use flag enumeration like variables. For instance the blocking variables are
defined in a flag enumeration like way, e.g.:
var ground_blocking = {
none : 0,
left : 1,
top : 2,
right : 4,
bottom : 8,
all : 15,
};
Variables that contain values from ground_blocking by using var blocking = ground_blocking.left +
ground_blocking.top; (could be also achieved by a bit operation, however, due to the statement written in JavaScript we
would not have any benefits) or something similar can be read out easily. The process of reading out atomic values can be done by
using:
https://www.codeproject.com/Articles/396959/Mario5 15/18
9/2/2021 Mario5 - CodeProject
return false;
}
The original version (including sounds and code) can be found at http://www.florian-rappl.de/html5/projects/SuperMario/. This version
will be also online soon.
History
v1.0.0 | Initial release | 01.06.2012.
v1.1.0 | Fixed some typos, including YouTube video, extended the ghost code | 05.06.2012.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
Share
Florian lives in Munich, Germany. He started his programming career with Perl. After programming C/C++ for some years he
discovered his favorite programming language C#. He did work at Siemens as a programmer until he decided to study Physics.
During his studies he worked as an IT consultant for various companies. After graduating with a PhD in theoretical particle Physics
he is working...
show more
https://www.codeproject.com/Articles/396959/Mario5 16/18
9/2/2021 Mario5 - CodeProject
Search Comments
Source_Code
Damien612 13-May-19 4:10
beginners
Member 13397853 7-Sep-17 8:20
My vote of 5
Ehsan Sajjad 9-Sep-16 3:09
Re: My vote of 5
Florian Rappl 9-Sep-16 7:25
Re: How to keep Game in the same position in different screen resolutions
Florian Rappl 3-Sep-15 21:36
Re: How to keep Game in the same position in different screen resolutions
Qian (Chocolate) Lu 4-Sep-15 9:32
Refresh 1 2 3 4 5 6 7 8 9 Next ᐅ
General News Suggestion Question Bug Answer Joke Praise Rant Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.
https://www.codeproject.com/Articles/396959/Mario5 18/18