Mario-7 wraps the game in a title screen, adds music and a scrolling background, and protects the physics from long frames. It’s also the full level, and it’s where your assignment begins.
Title Screen
main.js now registers a TitleScreenState and starts there:
game.addState(GameStateName.Play, PlayState);game.addState(GameStateName.TitleScreen, TitleScreenState);
game.services.stateMachine.change(GameStateName.TitleScreen);The title screen draws its image, starts the title music, and blinks PRESS ENTER once a second with a Timer it owns:
enter(props) { super.enter(props); this.game.sounds.restart(MusicName.TitleScreen);
this.isPromptVisible = true;
this.timer.every(1, () => { this.isPromptVisible = !this.isPromptVisible; });}
exit() { this.timer.cancelAll(); this.game.sounds.pause(MusicName.TitleScreen); super.exit();}Choosing a Character
Pressing Enter doesn’t start the game straight away. It switches the title screen into a character choice. Mario is on the left and Luigi is on the right. A/D or the arrow keys change the selection, Enter confirms, and Escape goes back to the prompt.
Both characters use the same sprite layout, so smallSpriteConfig cuts frames from either sheet. The selected character plays their walk animation and the other stands still at 40% opacity:
const frame = isSelected ? character.walk.getCurrentFrame() : character.idle;
context.globalAlpha = isSelected ? 1 : 0.4;The choice travels to PlayState as a transition prop, the same way Match-3 passed the level and score between states:
this.game.stateMachine.change(GameStateName.Play, { character: this.characters[this.selectedIndex].name,});PlayState is now a State<PlayStateProps>, and passes props.character on to the Player, which loads that character’s sprite sheet.
PlayState follows the same pattern for the overworld music. It calls restart() in enter() so every new level starts the song from the top, and pause() in exit().
Parallax Background
Parallax is the effect you see from a car window. Distant hills drift by slowly while nearby fence posts whip past. Drawing a background that scrolls slower than the camera makes it feel far away and gives a flat 2D level some depth.
PlayState keeps a list of background layers, each with its own speed relative to the camera:
this.parallaxLayers = [ { image: this.game.images.get(ImageName.Background), speedX: 0.04, speedY: 0.1, },];A speedX of 0.04 moves the background 4% as far as the camera. renderParallaxBackground() runs before the camera transform is applied, so it works in screen space. It offsets each layer by its fraction of the camera position, and repeats the image to cover the screen:
const parallaxX = -camera.x * layer.speedX;const drawX = (parallaxX % layer.image.width) + x * layer.image.width;The % wraps the offset into a single image width, so however far the camera travels, a couple of copies of the image always cover the view.
The Full Level
map.js now holds the complete level. It has the original Tiled layout plus the mountain, tunnel, and blocks added in earlier lessons. You’ll spot a few tiles that don’t do anything yet:
- floating platforms that are solid on every side
- a mushroom and a star floating near the start, which are solid scenery for now
TileMap only turns Tile.BLOCK tiles into Block objects. Making them work with items is part of your assignment!