Introduction
Super Mario Phaser is a modern web-based recreation of the classic Super Mario Bros game, built using the Phaser framework. This project is designed to bring the beloved nostalgic experience of the original game to today’s web browsers, allowing players to enjoy it directly online.
One of the standout features of Super Mario Phaser is its random level generation. This means that every time you play, the levels are different, providing a fresh and challenging experience with each playthrough. This dynamic element ensures that the game remains exciting and unpredictable, offering new adventures and obstacles for players to overcome every time they start a new game.
Additionally, we will provide options to customize the control keys, allowing players to configure their preferred inputs. We will also include settings to adjust or mute the sound, as well as control the volume of both music and sound effects, enhancing the overall gaming experience.
For more information about the original project, you can visit its GitHub repository.
Level generation
In this unit, we will create a simplified version of the original Super Mario Phaser project. Our goal is to enable easy customization of level generation, even for those without extensive programming knowledge.
To achieve this, we will use a matrix template where each cell specifies a type of block. By implementing a nested loop, we can efficiently render all the cells in the matrix, generating the level layout. This approach allows for straightforward modification and understanding of how different blocks are placed within the game environment.
For example, if we want to build a platform consisting of several blocks with some coins, we can use a matrix to define the layout. Each cell will represent a specific element (‘B’ stands for ‘block’ and ‘C’ stands for ‘coin’). Here is an example of how the matrix might look:
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', 'C', 'C', 'C', 'C', 'C', 'C', ' ', ' '], [' ', ' ', 'B', 'B', 'B', 'B', 'B', 'B', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
In this matrix, we have a design where coins are positioned directly above a row of blocks. Specifically, the fifth row of the matrix places coins in the middle columns, while the sixth row directly beneath it contains a continuous platform of blocks. This layout allows for a straightforward and clear way to design and visualize the level structure. The empty spaces around the blocks and coins indicate that these elements are floating in the air, creating a typical platforming challenge for the player.
The assets
Loading the assets (js/main/preload.js)
The provided code inside the “preload.js” file includes functions for preloading assets, initializing sounds, and creating animations.
The preload
function is responsible for loading all necessary game assets before the game starts. It sets up a visual progress bar and percentage text to inform the user about the loading progress. Various assets such as images, spritesheets, fonts, and sound files are loaded. The function also includes logic to determine the style of the level (overworld or underground) randomly and loads the appropriate assets accordingly. Additionally, it loads several plugins required for the game.
The initSounds
function initializes the game’s audio elements. It iterates over a list of sound assets, creates audio objects for each sound, and assigns them to the game instance. This function ensures that all sounds are ready to be played during the game, with background music starting to loop once the sounds are initialized.
The createAnimations
function defines various animations for the game’s characters and objects. It creates both single-frame and multi-frame animations using the loaded spritesheets. Single-frame animations are static poses, while multi-frame animations include movements like running, walking, and item interactions. These animations are essential for bringing the game characters and elements to life, providing visual feedback and enhancing the gaming experience.
Overall, the following code prepares the game environment by loading all assets, initializing audio, and setting up animations:
function preload() { // Setup progress box, bar, and percent text const progressBox = this.add.graphics().fillStyle(0x222222, 1) .fillRoundedRect(screenWidth / 2.48, screenHeight / 2 * 1.05, screenWidth / 5.3, screenHeight / 20.7, 10); const progressBar = this.add.graphics(); const percentText = this.make.text({ x: this.cameras.main.width / 2, y: this.cameras.main.height / 2 * 1.25, text: '0%', style: { font: `${screenWidth / 96}px pixel_nums`, fill: '#ffffff' } }).setOrigin(0.5, 0.5); // Update progress bar and text on progress this.load.on('progress', (value) => { percentText.setText(value * 99 >= 99 ? 'Generating world...' : `Loading... ${parseInt(value * 99)}%`); progressBar.clear().fillStyle(0xffffff, 1) .fillRoundedRect(screenWidth / 2.45, screenHeight / 2 * 1.07, screenWidth / 5.6 * value, screenHeight / 34.5, 5); }); // Clean up on load complete this.load.on('complete', () => { [progressBar, progressBox, percentText].forEach(item => item.destroy()); loadingGif.forEach(gif => gif.style.display = 'none'); }); // Load Fonts and Plugins this.load.bitmapFont('carrier_command', 'assets/fonts/carrier_command.png', 'assets/fonts/carrier_command.xml'); const plugins = ['rexvirtualjoystickplugin', 'rexcheckboxplugin', 'rexsliderplugin', 'rexkawaseblurpipelineplugin']; plugins.forEach(plugin => { this.load.plugin(plugin, `https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/${plugin}.min.js`, true); }); // Determine level style isLevelOverworld = Phaser.Math.Between(0, 100) <= 50; const levelStyle = isLevelOverworld ? 'overworld' : 'underground'; // Utility functions for loading assets const loadSpriteSheet = (path, frameWidth, frameHeight) => { this.load.spritesheet(path.split('/').pop(), `assets/${path}.png`, { frameWidth, frameHeight }); }; const loadImage = (path) => { this.load.image(path.split('/').pop(), `assets/${path}.png`); }; // Load entity sprites [{ path: 'mario', frameWidth: 18, frameHeight: 16 }, { path: 'mario-grown', frameWidth: 18, frameHeight: 32 }, { path: 'mario-fire', frameWidth: 18, frameHeight: 32 }, { path: `${levelStyle}/goomba`, frameWidth: 16, frameHeight: 16 }, { path: 'koopa', frameWidth: 16, frameHeight: 24 }, { path: 'shell', frameWidth: 16, frameHeight: 15 }, { path: 'fireball', frameWidth: 8, frameHeight: 8 }, { path: 'fireball-explosion', frameWidth: 16, frameHeight: 16 }] .forEach(item => loadSpriteSheet(`entities/${item.path}`, item.frameWidth, item.frameHeight)); // Load collectibles [{ path: 'coin', frameWidth: 16, frameHeight: 16 }, { path: 'underground/ground-coin', frameWidth: 10, frameHeight: 14 }, { path: `${levelStyle}/fire-flower`, frameWidth: 16, frameHeight: 16 }] .forEach(item => loadSpriteSheet(`collectibles/${item.path}`, item.frameWidth, item.frameHeight)); // Load animated blocks [{ path: `${levelStyle}/brick-debris`, frameWidth: 8, frameHeight: 8 }, { path: `${levelStyle}/mistery-block`, frameWidth: 16, frameHeight: 16 }, { path: 'overworld/custom-block', frameWidth: 16, frameHeight: 16 }] .forEach(item => loadSpriteSheet(`blocks/${item.path}`, item.frameWidth, item.frameHeight)); // Load animated hud [{ path: 'npc', frameWidth: 16, frameHeight: 24 }] .forEach(hud => loadSpriteSheet(`hud/${hud.path}`, hud.frameWidth, hud.frameHeight)); // Load mushrooms ['live', 'super'] .forEach(item => loadImage(`collectibles/${item}-mushroom`)); // Load normal scenery ['castle', 'flag-mast', 'final-flag', 'sign'] .forEach(item => loadImage(`scenery/${item}`)); // Load overworld scenery ['cloud1', 'cloud2', 'mountain1', 'mountain2', 'fence', 'bush1', 'bush2'] .forEach(item => loadImage(`scenery/overworld/${item}`)); // Load specific scenery ['floor-bricks'] .forEach(item => loadImage(`scenery/${levelStyle}/${item}`)); ['start-floor-bricks'] .forEach(item => loadImage(`scenery/overworld/${item}`)); // Load tubes ['horizontal', 'horizontal-final', 'vertical-small', 'vertical-medium', 'vertical-large', 'vertical-extra-large'] .forEach(item => loadImage(`scenery/${item}-tube`)); // Load HUD ['gear', 'settings-bubble'] .forEach(item => loadImage(`hud/${item}`)); // Load static blocks ['block', 'empty-block', 'immovable-block'] .forEach(item => loadImage(`blocks/${levelStyle}/${item}`)); ['construction-block'] .forEach(item => loadImage(`blocks/underground/${item}`)); // Load sounds (music and effects) this.sounds = [ { path: 'music/overworld/overworld.mp3', volume: 0.15 }, { path: 'music/underground/underground.mp3', volume: 0.15 }, { path: `music/${levelStyle}/hurry-up.mp3`, volume: 0.15 }, { path: 'music/game-over.mp3', volume: 0.3 }, { path: 'music/win.wav', volume: 0.3 }, { path: 'effects/jump.mp3', volume: 0.1 }, { path: 'effects/coin.mp3', volume: 0.2 }, { path: 'effects/power-up-appears.mp3', volume: 0.2 }, { path: 'effects/consume-power-up.mp3', volume: 0.2 }, { path: 'effects/power-down.mp3', volume: 0.3 }, { path: 'effects/goomba-stomp.wav', volume: 1 }, { path: 'effects/flagpole.mp3', volume: 0.3 }, { path: 'effects/fireball.mp3', volume: 0.3 }, { path: 'effects/kick.mp3', volume: 0.3 }, { path: 'effects/time-warning.mp3', volume: 0.2 }, { path: 'effects/pause.wav', volume: 0.17 }, { path: 'effects/block-bump.wav', volume: 0.3 }, { path: 'effects/break-block.wav', volume: 0.5 }, { path: Phaser.Math.Between(0, 100) < 98 ? 'effects/here-we-go.mp3' : 'effects/cursed/here-we-go.mp3', volume: 0.17 } ]; const generateKey = path => path.split('/').pop().split('.')[0].replace(/-./g, match => match.charAt(1).toUpperCase()) + 'Sound'; this.sounds.forEach(sound => { sound.key = generateKey(sound.path); this.load.audio(sound.key, `assets/sound/${sound.path}`); }); } function initSounds() { this.sounds.forEach(sound => { this[sound.key] = this.sound.add(sound.key, { volume: sound.volume }); }); this.overworldSound.play({ loop: -1 }); } function createAnimations() { const singleFrameAnimations = [ { key: 'idle', target: 'mario', frame: 0 }, { key: 'hurt', target: 'mario', frame: 4 }, { key: 'jump', target: 'mario', frame: 5 }, { key: 'grown-mario-idle', target: 'mario-grown', frame: 0 }, { key: 'grown-mario-crouch', target: 'mario-grown', frame: 4 }, { key: 'grown-mario-jump', target: 'mario-grown', frame: 5 }, { key: 'fire-mario-idle', target: 'mario-fire', frame: 0 }, { key: 'fire-mario-crouch', target: 'mario-fire', frame: 4 }, { key: 'fire-mario-jump', target: 'mario-fire', frame: 5 }, { key: 'fire-mario-throw', target: 'mario-fire', frame: 6 }, { key: 'goomba-idle', target: 'goomba', frame: 1 }, { key: 'goomba-hurt', target: 'goomba', frame: 2 }, { key: 'koopa-idle', target: 'koopa', frame: 1 }, { key: 'koopa-hurt', target: 'koopa', frame: 0 }, { key: 'koopa-shell', target: 'koopa', frame: 1 }, { key: 'fireball-left-down', target: 'fireball', frame: 0 }, { key: 'fireball-left-up', target: 'fireball', frame: 1 }, { key: 'fireball-right-down', target: 'fireball', frame: 2 }, { key: 'fireball-right-up', target: 'fireball', frame: 3 }, { key: 'fireball-explosion-1', target: 'fireball-explosion', frame: 0 }, { key: 'fireball-explosion-2', target: 'fireball-explosion', frame: 1 }, { key: 'fireball-explosion-3', target: 'fireball-explosion', frame: 2 }, ]; const multiFrameAnimations = [ { key: 'run', target: 'mario', start: 3, end: 1, frameRate: 12, repeat: -1 }, { key: 'grown-mario-run', target: 'mario-grown', start: 3, end: 1, frameRate: 12, repeat: -1 }, { key: 'fire-mario-run', target: 'mario-fire', start: 3, end: 1, frameRate: 12, repeat: -1 }, { key: 'goomba-walk', target: 'goomba', start: 0, end: 1, frameRate: 8, repeat: -1 }, { key: 'koopa-walk', target: 'koopa', start: 0, end: 1, frameRate: 8, repeat: -1 }, { key: 'coin-default', target: 'coin', start: 0, end: 3, frameRate: 10, repeat: -1 }, { key: 'ground-coin-default', target: 'ground-coin', start: 2, end: 0, frameRate: 5, repeat: -1, repeatDelay: 5 }, { key: 'mistery-block-default', target: 'mistery-block', start: 2, end: 0, frameRate: 5, repeat: -1, repeatDelay: 5 }, { key: 'custom-block-default', target: 'custom-block', start: 2, end: 0, frameRate: 5, repeat: -1, repeatDelay: 5 }, { key: 'brick-debris-default', target: 'brick-debris', start: 0, end: 3, frameRate: 4, repeat: -1 }, { key: 'fire-flower-default', target: 'fire-flower', start: 0, end: 3, frameRate: 10, repeat: -1 }, { key: 'npc-default', target: 'npc', start: 0, end: 1, frameRate: 2, repeat: -1, repeatDelay: 10 }, ]; singleFrameAnimations.forEach(({ key, target, frame }) => { this.anims.create({ key, frames: [{ key: target, frame }] }); }); multiFrameAnimations.forEach(({ key, target, start, end, frameRate, repeat, repeatDelay = 0 }) => { this.anims.create({ key, frames: this.anims.generateFrameNumbers(target, { start, end }), frameRate, repeat, repeatDelay }); }); }
Drawing the blocks and coins (js/game/structures.js)
The function generateStructure
is designed to create and display different elements (blocks, coins, etc.) on the screen based on a predefined matrix layout. The key steps and components are as follows:
- Element addition functions: Several helper functions (
addBlock
,addConstructionBlock
,addImmovableBlock
,addMisteryBlock
,addCoin
) are defined to add different types of game elements (blocks, mystery blocks, immovable blocks, coins) to their respective groups and scale them appropriately. - Level layouts: The
levels
object contains predefined matrices for different types of levels (overworld
andunderworld
). Each matrix defines the placement of blocks (‘B’), mystery blocks (‘M’), immovable blocks (‘I’), coins (‘C’), and construction blocks (‘X’). - Random level selection: A random matrix is selected from the
levels
object based on whether the level is overworld or underworld. - Drawing the matrix: The
drawMatrix
function iterates over the selected matrix, calculates the position for each element, and uses the helper functions to add the elements to the screen.
In the following source code, each matrix is used to define the layout of game elements in a clear and structured way, and the function dynamically generates the game level based on these definitions:
function generateStructure(pieceStart) { const scale = screenHeight / 345; const addBlock = (x, y) => this.blocksGroup.add(this.add.image(x, y, 'block').setScale(scale)); const addConstructionBlock = (x, y) => this.constructionBlocksGroup.add(this.add.image(x, y, 'construction-block').setScale(scale)); const addImmovableBlock = (x, y) => this.immovableBlocksGroup.add(this.add.image(x, y, 'immovable-block').setScale(scale)); const addMisteryBlock = (x, y) => this.misteryBlocksGroup.add(this.add.sprite(x, y, 'mistery-block').setScale(scale)); const addCoin = (x, y) => this.groundCoinsGroup.add(this.physics.add.sprite(x, y, 'ground-coin').setScale(scale)); const levels = { overworld: [ [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', 'B', 'M', 'B', ' ', ' ', ' ', ' ', ' ', 'B', 'M', 'B', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', 'B', 'B', 'M', 'B', 'B', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', 'B', 'M', 'B', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', 'B', 'M', 'B', ' ', ' ', ' ', 'B', 'M', 'B', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', 'M', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', 'B', 'M', 'B', 'M', 'B', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', 'B', 'M', 'M', 'B', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', 'B', 'B', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', 'M', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', 'M', ' ', ' ', 'M', ' ', ' ', 'M', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', 'B', 'B', 'M', 'B', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], ], underworld: [ [ [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', 'M', 'M', 'M', 'M', 'M', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' '], ], [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', 'I', ' ', ' ', ' ', 'M'], [' ', ' ', ' ', ' ', 'I', ' ', 'I', ' ', 'I', ' ', ' '], [' ', ' ', 'I', ' ', 'I', ' ', 'I', ' ', 'I', ' ', ' '], ['I', ' ', 'I', ' ', 'I', ' ', 'I', ' ', 'I', ' ', ' '], ], [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', 'C', 'C', 'C', 'C', ' ', ' ', ' '], [' ', ' ', ' ', 'X', 'X', 'X', 'X', ' ', 'X', ' '], [' ', ' ', 'C', 'X', ' ', ' ', 'X', 'C', 'X', ' '], [' ', 'X', 'X', 'X', ' ', ' ', 'X', 'X', 'X', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], [ [' ', ' ', 'M', 'M', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', 'M', 'M', ' ', ' ', 'M', 'M', 'M', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', 'C', 'C', 'C', 'C', ' ', ' ', ' '], [' ', ' ', ' ', 'B', 'B', 'B', 'B', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', ' '], [' ', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], ] }; const random = Phaser.Math.Between(0, 5); const getMatrix = () => isLevelOverworld ? levels.overworld[random] : levels.underworld[random]; const drawMatrix = (matrix) => { const blockSize = 16 * scale; matrix.forEach((row, rowIndex) => { const y = (screenHeight - platformHeight) + (rowIndex * blockSize) - (8.5 * blockSize); row.forEach((cell, colIndex) => { const x = pieceStart + (colIndex * blockSize) - (4.5 * blockSize); if (cell === 'B') addBlock(x, y); if (cell === 'M') addMisteryBlock(x, y); if (cell === 'I') addImmovableBlock(x, y); if (cell === 'C') addCoin(x, y); if (cell === 'X') addConstructionBlock(x, y); }); }); }; const matrix = getMatrix(); drawMatrix(matrix); return isLevelOverworld ? Phaser.Math.Between(1, 3) : 1; }
Download the source code and the assets
Inside this ZIP file, you will find all the files containing the source code and all the necessary assets to run the game on your own. The structure of the ZIP file is organized as follows:
index.html
: This is the main HTML file that serves as the entry point for the game.css
: This folder includes the CSS file used for styling the HTML elements of the game interface.js
: This folder contains all the JavaScript files that comprise the game’s source code, including:main/preload.js
: Handles the loading of all game assets, such as images and sprite sheets.game/structures.js
: Defines the structure and layout of the game levels.- Other JavaScript files that implement the game logic and mechanics.
assets
: This folder contains all the graphical and audio assets needed for the game, such as images, sprite sheets, and sound files.
This organization ensures that you have a clear structure for all components of the game, making it easy to understand, modify, and run the game on your own system.
Proposed exercise: Customizing the Mario character
You may easily customize the Mario character by simply overwriting the images and defining the new sprite sizes (if changed). You need to update the relevant parts of the
preload.js
file (under thejs/main
folder). The source code remains mostly the same unless you need to adjust frame-specific size and animations, which might require changes based on your custom sprite sheet. Here are the specific steps you need to follow:
- Update the images: Change the following images. You may replace them by a new ones, or you can update the ones provided using Pixelorama:
- /assets/entities/mario.png
- /assets/entities/mario-grown.png
- /assets/entities/mario-fire.png
- Update frame dimensions inside
preload.js
file: Replace thewidth
andheight
of the Mario sprites with the new frame dimensions (if changed):// Load entity sprites [{ path: 'mario', frameWidth: 18, frameHeight: 16 }, { path: 'mario-grown', frameWidth: 18, frameHeight: 32 }, { path: 'mario-fire', frameWidth: 18, frameHeight: 32 }, ...
- Update animations inside
preload.js
file: Ensure that the animation definitions use the new sprites correctly. You may change thestart
andend
frames, and also theframeRate
:const multiFrameAnimations = [ { key: 'run', target: 'mario', start: 3, end: 1, frameRate: 12, repeat: -1 }, { key: 'grown-mario-run', target: 'mario-grown', start: 3, end: 1, frameRate: 12, repeat: -1 }, { key: 'fire-mario-run', target: 'mario-fire', start: 3, end: 1, frameRate: 12, repeat: -1 }, ...By updating these lines, you can ensure that your custom Mario sprite is loaded with the new dimensions and integrated into the game’s existing animations.
Proposed exercise: Defining your own levels
To define your own levels, you need to update the
structures.js
file (under thejs/game
folder) to include your custom level designs. Here are the steps and the specific lines of code to add new levels or update the existing ones:
- Define new levels: Add new level arrays to the
levels.overworld
andlevels.underworld
arrays.- Adjust block types: Use ‘B’ for normal blocks, ‘M’ for mystery blocks, ‘I’ for immovable blocks, ‘C’ for coins, and ‘X’ for construction blocks.
- Update random selector: If you add more than six levels, adjust the
Phaser.Math.Between(0, 5)
to match the number of levels you have.This is the code you should modify inside the
structures.js
file:function generateStructure(pieceStart) { // Existing code... const levels = { overworld: [ // Existing level definitions [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', 'C', 'M', 'C', ' ', ' ', ' ', ' ', ' ', 'B', 'M', 'B', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', 'B', 'B', 'M', 'B', 'B', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], // Add more custom levels as needed... ], underworld: [ // Existing level definitions [ [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', 'C', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', 'M', 'B', 'M', ' ', 'M', 'B', 'M', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ], // Add more custom levels as needed... ] }; // Existing code to select and draw the level const random = Phaser.Math.Between(0, 5); ... }By following these instructions, you can easily define and customize your own levels in the Mario game.
Enjoy the game!
You can enjoy playing this game online here.