Super Mario RPG Mario Punching Animation in Javascript

javascript super mario rpg punch animationThis is an attempt to replicate a battle animation in Super Mario RPG wherein Mario punches an enemy. Our setting here is the Sunken Ship, and he’s facing an adversary not normally present in the game — Bandana Green. As you know, Bandana sharks can be repalettize to change their colors, which makes up the Bandana Red and the Bandana Blue. However, the green palette can also be applied on the Bandana, and thus, we came up with Bandana Green.

Note that this is simply a fan project. I don’t make money out of it. Super Mario RPG was develop by Square (now Square Enix) and is licensed by Nintendo for the Super Nintendo Entertainment System (SNES). I am not affiliated to Nintendo nor Square. The sprites here have been downloaded from Spriters-Resource.com: Super Mario RPG, the punching sound from MFGG.net, and the background music from Khinsider.com.

The animation is complete with a Super Mario RPG battle theme song that automatically plays on page load. Mario’s punching animation is triggered by clicking on the “Play animation” button. Bandana Green also starts moving upon page load. It is also worth noting that we are using audio control functions here that allows us to start playing from the 2 sec. position time line.

You can download the package here. It’s compose of an HTML file, audio files, images, and the Javascript for the animation.

Talk Code

This code will be kept here for future references. I will also note only of the new Javascript functions that were used in this animation as well as explain some tweaks that were made to make it cross-browser compatible. Note however that this may not work on old browsers such as IE8.

HTML File:

<html>
<head>
	<title>Mario Punching Animation</title>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
	<style>
		.sunken_ship{
			background:url('sunken_ship.gif') no-repeat;
		}
	</style>
</head>
<body>

<!-- for Google Chrome, preventing autoplay -->
<iframe src="5-seconds-of-silence.mp3" allow="autoplay" style="display:none"></iframe>

<audio id="controlbgm" src="fight_against_monsters.ogg" type="audio/ogg"></audio>
<audio preload="auto" id="bgm">
	<source src="fight_against_monsters.ogg" type="audio/ogg">
</audio>
<audio id="punch">
	<source src="smrpg_battle_punch.wav" type="audio/wav">
</audio>
<script>
	window.onload = function(){
		document.getElementById("controlbgm").play();
		setTimeout(function bgmMusic(){
			document.getElementById("controlbgm").pause();
			thisBGM = document.getElementById("bgm");
			if(thisBGM.currentTime = 2.176){
				thisBGM.currentTime = 2.176;
			}
			thisBGM.play();
			setTimeout(bgmMusic,57714);
		},2176);
	}
</script>
<div class="sunken_ship">
	<canvas width="500" height="200"></canvas>
	<script type="application/javascript" src="animate.js"></script>
</div>
<br><button onclick="init()">Play Animation</button>
</body>
</html>

Points to note:

Line 15 – This is a hack that forces Google Chrome to play our BGM on page load. The latest version of Google Chrome seems to prevent auto playing. I’ve tested this on Version 72.0.3626.121. The initial auto-play will be block by Chrome’s security feature, but not the second. Using this knowledge, we can bait Chrome to stop a dummy music, which will be loaded via an iframe instead. Chrome will go after this music. Then we launch a second one, which is our real music so we can auto play it on page load.

Line 25 – window.onload — this javascript event executes a function on page load.

Line 30 and 31 – Audio.currentTime properties — once an audio file is loaded into the browser, the playback time is calculated by the computer. This DOM property allows us to get the playback time as well as set the starting playback time for the audio. Property currentTime on line 31 is use to make our audio file play from point 2.176 secs.

Line 39 – canvas is an HTML element used to draw graphics on a web page. This will be manipulated by our Javascript to draw the animations.

Javascript File:

let sprite = new Image();
let enemy = new Image();
sprite.src = 'mario_punching.png';
enemy.src = 'bandana_green.png';
sprite.onload = function(){
	animd();
	enemy_init();
};

let canvas = document.querySelector('canvas');
let context = canvas.getContext('2d');

function enemy_init(){
	anime();
	setTimeout(del, 100);
	setTimeout(animf, 100);
	setTimeout(del, 200);
	setTimeout(animg, 200);
	setTimeout(del, 300);
	setTimeout(enemy_init,300);
}

function init(){
	document.getElementById('punch').play();
	setTimeout(clear, 50);
	setTimeout(anim2, 50);
	setTimeout(clear, 100);
	setTimeout(anim3, 100);
	setTimeout(clear, 150);
	setTimeout(anim4, 150);
	setTimeout(clear, 200);
	setTimeout(anim5, 200);
	setTimeout(clear, 250);
	setTimeout(anim6, 250);
	setTimeout(clear, 300);
	setTimeout(anim7, 300);
	setTimeout(clear, 350);
	setTimeout(anim8, 350);
	setTimeout(clear, 400);
	setTimeout(anim9, 400);
	setTimeout(clear, 450);
	setTimeout(anima, 450);
	setTimeout(clear, 500);
	setTimeout(animb, 500);
	setTimeout(clear, 550);
	setTimeout(animc, 550);
	setTimeout(clear, 600);
	setTimeout(animd, 600);
}

function anim1(){
	context.drawImage(sprite, 0, 0, 42, 40, 80, 110, 42, 40);
}

function anim2(){
	context.drawImage(sprite, 38, 0, 45, 42, 80, 110, 45, 42);
}

function anim3(){
	context.drawImage(sprite, 75, 0, 30, 40, 90, 110, 30, 40);
}

function anim4(){
	context.drawImage(sprite, 100, 0, 28, 40, 92, 110, 28, 40);
}

function anim5(){
	context.drawImage(sprite, 125, 0, 32, 40, 92, 110, 32, 40);
}

function anim6(){
	context.drawImage(sprite, 155, 0, 30, 40, 93, 110, 30, 40);
}

function anim7(){
	context.drawImage(sprite, 185, 0, 42, 42, 95, 110, 42, 42);
}

function anim8(){
	context.drawImage(sprite, 0, 40, 40, 42, 80, 110, 40, 42);
}

function anim9(){
	context.drawImage(sprite, 36, 40, 25, 42, 93, 110, 25, 42);
}

function anima(){
	context.drawImage(sprite, 61, 40, 29, 42, 93, 110, 29, 42);
}

function animb(){
	context.drawImage(sprite, 89, 40, 27, 42, 94, 110, 27, 42);
}

function animc(){
	context.drawImage(sprite, 113, 40, 27, 42, 94, 110, 27, 42);
}

function animd(){
	context.drawImage(sprite, 134, 40, 31, 42, 92, 110, 31, 42);
}

function clear(){
	context.clearRect(80, 110, 45, 45);
}

function anime(){
	context.drawImage(enemy, 0, 0, 35, 40, 125, 85, 35, 40);
}

function animf(){
	context.drawImage(enemy, 31, 0, 34, 40, 128, 85, 34, 40);
}

function animg(){
	context.drawImage(enemy, 61, 0, 34, 40, 128, 85, 34, 40);
}

function del(){
	context.clearRect(125, 85, 45, 45);
}

//function mario_punching(){
//	context.drawImage(sprite, 0, 0, 42, 40, 0, 0, 42, 40);
//	context.drawImage(sprite, 38, 0, 45, 42, 49, 0, 45, 42);
//	context.drawImage(sprite, 75, 0, 30, 40, 98, 0, 30, 40);
//	context.drawImage(sprite, 100, 0, 28, 40, 147, 0, 28, 40);
//	context.drawImage(sprite, 125, 0, 32, 40, 196, 0, 32, 40);
//	context.drawImage(sprite, 155, 0, 30, 40, 245, 0, 30, 40);
//	context.drawImage(sprite, 185, 0, 42, 42, 294, 0, 42, 42);
//	context.drawImage(sprite, 0, 40, 40, 42, 0, 50, 40, 42);
//	context.drawImage(sprite, 36, 40, 25, 42, 59, 50, 25, 42);
//	context.drawImage(sprite, 61, 40, 29, 42, 102, 50, 29, 42);
//	context.drawImage(sprite, 89, 40, 27, 42, 148, 50, 27, 42);
//	context.drawImage(sprite, 113, 40, 27, 42, 198, 50, 27, 42);
//	context.drawImage(sprite, 134, 40, 31, 42, 245, 50, 31, 42);
//}

Points to note:

Line 1 and 2 – use of let — let provides Block Scope variables in Javascript. In computer science, a “let” expression associates a function definition with a restricted scope. let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.

Also note of Image(). It is a constructor that creates a new HTMLImageElement instance. It is functionally equivalent to document.createElement(‘img’).

Line 3 and 4 – sprite.src is a property for let sprite that calls for the source image. The same with enemy.src that is reference to let enemy.

Line 5 – sprite.onload calls a function once let sprite loads.

Line 10 – document.querySelector() is a Document method that returns the first Element within the document that matches the specified selector, or group of selectors. If no matches are found, null is returned.

Line 11 – The HTMLCanvasElement.getContext() method returns a drawing context on the canvas, or null if the context identifier is not supported. For our image, we are using 2D or 2-Dimensional Rendering.

Line 52 – The drawImage() is method that draws an image, canvas, or video onto the canvas. The drawImage() method can also draw parts of an image, and/or increase/reduce the image size.

The parameter format is drawImage(img, sx, sy, swidth, sheight, dx, dy, dwidth, dheight).

img – stands for our var that contains new Image().
sx – source’s x coordinate where we start clipping our image. The coordinate moves horizontally from left to right. Imagine this as a box that views the image’s position. This makes that box move left or right.
sx – source’s y coordinate where we start clipping our image. The coordinate moves vertically from top to bottom. Also imagine this as a box that views the image’s position. This makes that box move up or down.
swidth – The width of the clipped image. Technically, this is our crop box’s width.
sheight – The height of the clipped image. Technically, this is our crop box’s height.
dx – destination’s x coordinate where we place the image on the canvas. Moves our image from left to right.
dy – destination’s y coordinate where we place the image on the canvas. Moves our image from top to bottom.
dwidth – The width of the image to use (stretch or reduce the image) on the canvas.
dheight – The height of the image to use (stretch or reduce the image) on the canvas.

Line 103 and 119 – clearRect — The clearRect() method clears the specified pixels within a given rectangle. It is the exact opposite of the crop box. While our crop box crops images, this deletes the drawing within the box.

The parameter format is clearRect(dx, dy, dwidth, dheight).

Line 13 and 23 – So what we did in our functions is simply switch the pictures by using setTimeout to make our pictures appear then remove it so we can replace it with another one. The timing when to remove a picture and when another one should start appearing should be the same to make the animation look like it moves frame by frame.

The outcome is this:

Follow me at:

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.