Super Mario Bros. è un videogioco di genere platform in 2D, sviluppato da Nintendo e rilasciato su cartuccia nel 1985 per NES. Nato dalla mente di Shigeru Miyamoto, è considerato uno dei capolavori della storia dei videogiochi. Con più di 380 milioni di copie vendute, ha rivoluzionato il mercato videoludico.
Il gioco consiste in livelli di crescente difficoltà, in cui Mario deve
affrontare nemici e ostacoli, sfruttando i potenziamenti scovati lungo
il percorso. Il gameplay si fonda sull’idea semplice e geniale del salto
combinato al movimento orizzontale. Questa meccanica, facile e
coinvolgente, viene amplificata dalle abilità date dai power up. Il
gioco, pur essendo basato su regole di movimento elementari, risulta
sempre stimolante e divertente, motivo per il quale è considerato un
capolavoro senza tempo.
Il gioco, sebbene presenti una trama quasi inesistente, prevede che
Mario salvi la principessa del Regno dei Funghi, rapita dell’antagonista
Bowser. Questo cliché è diventato il punto di partenza di gran parte dei
giochi della saga.
I nemici, aumentati in numero e quantità nel corso degli ani, sono
caratterizzati da comportamenti diversi a seconda della specie. Tra i
più famosi, anche nella cultura popolare, spiccano sicuramente i Goomba
e i Koopa, che appaiono già nel primo livello del gioco.
Il remake di Super Mario Bros. è stato sviluppato utilizzando la
libreria grafica Processing 4, basata su Java, prendendo ispirazione dal
gioco originale rilasciato nel 1985. Tuttavia, presenta notevoli
differenze rispetto ad esso, sia dal punto di vista tecnico che da
quello del design dei livelli.
Tra le variazioni tecniche più evidenti, spiccano la dimensione della
finestra di gioco, aggiornata in risoluzione Full HD (1920x1080), e il
frame rate impostato a 60 Hz. Anche l’aspetto sonoro è stato rivisto,
con alcune musiche ed effetti sostituiti da versioni rimasterizzate, di
qualità superiore rispetto agli originali. Gli asset grafici sono stati
aggiornati, mantenendo un’ottima fedeltà agli elementi originali.
Attualmente in uno stato alpha, il remake presenta ancora alcune lacune rispetto al gioco originale per NES, come la mancanza di alcuni nemici, power up, scenari e meccaniche. Tuttavia, risulta giocabile e fruibile senza evidenti problemi. Nonostante le mancanze, le principali funzioni di gioco sono accessibili e i livelli proposti, sebbene inediti rispetto all’originale, mantengono le medesime meccaniche di gameplay.
In questa sezione verrano analizzati gli elementi di gioco implementati.
-
Ground
Il blocco Ground rappresenta un blocco indistruttibile all’interno del gioco. Se un’entità entra in contatto con questo blocco, non può oltrepassarlo. Viene utilizzato come base dei livelli.
-
Block
Il blocco Block rappresenta un blocco distruttibile se colpito con un valore di forza maggiore o uguale a 3. Se un’entità entra in contatto con questo blocco, non può oltrepassarlo.
-
Brick
Il blocco Brick rappresenta un blocco distruttibile se colpito con un valore di forza maggiore o uguale a 2. Se un’entità entra in contatto con questo blocco, non può oltrepassarlo.
-
Question Block
Il blocco Question Block rappresenta un blocco distruttibile se colpito con un valore di forza maggiore o uguale a 2. Se un’entità entra in contatto con questo blocco, non può oltrepassarlo. Se viene colpito con un valore di forza maggiore o uguale a 1, cambia stato, rilasciando il power up equipaggiato.
-
Goomba
Il Goomba rappresenta un nemico che, muovendosi orizzontalmente, può attaccare il giocatore se riesce a colpirlo con il suo lato destro, sinistro o inferiore. Può subire danno se colpito dall’alto o con dei power up.
-
Green Koopa Troopa
Il Green Koopa Troopa rappresenta un nemico che, muovendosi orizzontalmente, può attaccare il giocatore se riesce a colpirlo con il suo lato destro, sinistro o inferiore. Può subire danno se colpito con dei power up. Se colpito sul lato superiore entra nello stato guscio. Dopo alcuni secondi in questo stato, se non viene colpito, torna a muoversi normalmente. Se colpito nello stato di guscio, inizia a muoversi a velocità maggiore con una forza orizzontale di 2. Se colpito mentre in movimento nello stato guscio torna a rimanere statico.
-
Coin
Il Coin rappresenta un power up statico. Se colpito dal giocatore aggiunge una moneta all’inventario. Con 100 monete il giocatore guadagna una vita.
-
Super Mushroom
Il Super Mushroom rappresenta un power up che si muove nel livello orizzontalmente. Se colpito dal giocatore lo potenzia a Super Mario.
-
One Up Mushroom
Il One Up Mushroom rappresenta un power up che si muove nel livello orizzontalmente. Se colpito dal giocatore aumenta di uno il numero delle vite.
-
Fire Flower
Il Fire Flower rappresenta un power up statico. Se colpito dal giocatore lo potenzia a Fire Mario.
-
Mario
Il livello di abilità Mario rappresenta il livello di potenziamento minimo del giocatore. In questo stato, Mario è più piccolo della sua versione Super. Se colpito da un nemico muore immediatamente. Il suo livello di forza superiore è pari a 1, mentre la forza degli altri lati è 0.
-
Super Mario
Il livello di abilità Super Mario rappresenta il primo livello di potenziamento del giocatore. In questo stato, Mario è più grande della sua versione base. Se colpito da un nemico torna allo stato Mario. Il suo livello di forza superiore è pari a 2, mentre la forza degli altri lati è 0.
-
Fire Mario
Il livello di abilità Fire Mario rappresenta il secondo livello di potenziamento del giocatore. In questo stato, Mario è della stessa grandezza della sua versione Super. Se colpito da un nemico torna allo stato Super Mario. Il suo livello di forza superiore è pari a 2, mentre la forza degli altri lati è 0. Può lanciare Fire Balls ai nemici, che applicano un danno quando li interccettano.
Il remake, così come il gioco originale, basa i suoi controlli sulle meccaniche tipiche dei platform: il salto e la corsa. Di seguito verranno analizzati tutti i comandi presenti nel gioco.
-
Movimento laterale
Il giocatore può muoversi a sinistra premendo e a destra premendo . Il movimento, meccanica fondamentale nei platform, permette al giocatore di esplorare il livello ed interagire con esso.
-
Corsa
Il giocatore può incrementare la propria velocità di movimento premendo in combinazione con o . La corsa permette al giocatore di superare nemici, burroni e ostacoli.
-
Salto
Il giocatore può saltare premendo . Premendo il tasto per poco tempo si effettuerà un salto normale, mentre tenendolo premuto più a lungo sarà possibile fare un super salto. Combinato con il movimento, il salto risulta una meccanica fondamentale all’interno di un platform.
-
Lancio delle palle di fuoco
Il giocatore può lanciare palle di fuoco premendo quando è Fire Mario. Premendo il tasto, Mario lancierà delle palle di fuoco che attaccheranno i nemici.
-
Pausa e uscita dal gioco
È possibile mettere in pausa il gioco premendo all’interno di un livello. Si aprirà quindi un menù di pausa dal quale sarà possibile riprendere la partita premendo nuovamente o uscire premendo .
I livelli ricreati all’interno del remake sono frutto di fantasia, sebbene siano ispirati a quelli del gioco originale. Di seguito verranno analizzati e descritti brevemente.
-
Livello iniziale
Il livello iniziale funge da menù principale del remake. È possibile accedervi all’avvio del gioco o al completamento dell’ultimo livello. È l’unico livello a nascondere l’HUD e a riprodurre un suono differente rispetto a quello di default quando si colpisce la bandiera di fine livello. Mostra a schermo il logo del remake e i comandi di gioco.
-
Level 1
Level 1 rappresenta il primo livello del gioco. È diviso in microsezioni separate da burroni. All’interno del livello è possibile ottenere un Super Mushroom e un One Up Mushroom. Prende ispirazione dai livelli del gioco originale ambientati nell’overworld.
-
Level 2
Level 2 rappresenta il secondo livello del gioco. È incentrato sul parkour e richiede una buona abilità nell’attaccare e saper utilizzare a proprio vantaggio i nemici. All’interno del livello è possibile ottenere un Fire Flower. Prende ispirazione dai livelli più moderni della saga.
-
Level 3
Level 3 rappresenta il terzo e ultimo livello del gioco. Essendo il livello finale, presenta alcune peculiarità, come una versione remixata della classica OST del gioco. Il livello contiene, inoltre, molti nemici Goomba, sui quali il giocatore potrà provare l’abilità del Fire Flower. Con il quantitativo di monete presenti nel livello, e morendo un numero di volte sufficienti, sarà possibile raggiungere le 100 unità, guadagnando una vita extra. Al raggiungimento della bandiera il giocatore verrà riportato al livello iniziale.
Oltre ai livelli precedentemente analizzati, il gioco presenta alcune schermate di gioco che forniscono informazioni di vario genere al giocatore.
-
Schermata di caricamento del livello
In questa schermata vengono mostrati, in ordine, il nome del livello successivo (o di quello corrente nel caso in cui lo si stia ricominciando dopo essere morti), il numero di vite e il numero di monete raccolte. Questa schermata appare nel caso in cui il giocatore raggiunga la bandiera di fine livello o nel caso in cui muoia, ma abbia ancora delle vite.
-
Schermata di Game Over
Questa schermata appare nel caso in cui il giocatore muoia e non abbia più altre vite a disposizione.
-
Schermata di pausa
Questa schermata appare nel caso in cui venga premuto il tasto e rappresenta il menù di pausa. Il testo mostrato a schermo indica come riprendere la partita o uscire dal gioco.
-
HUD delle vite
Questo HUD fornisce informazioni riguardo al numero di vite a disposizione del giocatore. È posto in alto a sinistra ed è presente in tutti i livelli ad esclusione di quello di inizio.
-
HUD delle monete
Questo HUD fornisce informazioni riguardo al numero di monete raccolte dal giocatore. È posto in alto a sinistra, a fianco dell’HUD delle vite, ed è presente in tutti i livelli ad esclusione di quello di inizio.
Lo sviluppo del remake è stato realizzato utilizzando Processing 4. Il linguaggio, basato su Java, possiede un IDE proprietario che permette, oltre allo sviluppo, la possibilità di esportare il gioco compilato o di installare librerie. Tuttavia nello sviluppo del codice è stato utilizzato l’editor di testo Visual Studio Code con l’ausilio del plugin Processing VSCode. Questo editor, infatti, presenta funzioni di aiuto allo sviluppo ben più avanzate rispetto all’IDE ufficiale.
Nella realizzazione del remake, è stato utilizzato il sitema di
controllo delle versioni GIT, in combinazione con il servizio online
GitHub. Il codice e gli assets, insieme alle commit effettuate e alle
release, sono accessibili gratuitamente attraverso la repository GitHub
accessibile
qui.
Per la manipolazione degli assets grafici è stato utilizzato il software
Adobe Photoshop. Molti template utilizzati per lo sviluppo, infatti,
sono accessibili in formato PSD.
Nello sviluppo del remake sono state create molte classi e frammenti di codice che andremo ad analizzare di seguito.
La classe Main, sebbene non sia una vera classe, costituisce il cuore del gioco. Contiene i metodi richiamati all’avvio dell’applicazione e alcune variabili globali. Svolge vari compiti:
-
La fase di inizializzazione prepara il gioco, istanziando livelli, giocatori, suoni, timer e altri elementi necessari prima dell’avvio.
-
Gestisce le schermate del gioco, come il caricamento dei livelli, il game over e la pausa.
-
Si occupa dell’aggiornamento e del disegno dei livelli, garantendo che gli elementi di gioco siano aggiornati e visualizzati correttamente sullo schermo.
-
Gestisce gli input dell’utente, inclusi i tasti premuti e rilasciati, per consentire l’interazione con il gioco.
Il codice è accessibile qui, ma di seguito è disponibile quello del metodo draw(). Il metodo draw risulta particolarmente importante, in quanto si occupa di gestire le varie scene mostrate a schermo con l’ausilio di timer e controlli.
void draw() {
if (!player.isDead) { // When the player is not dead
if (level.isFinished) {
if (loadLevelScreenTimeDuration.tick()) {
if (level.id + 1 < levels.size()) {
level = levels.get(level.id + 1);
player.reset();
} else {
level = levels.get(0);
for (Level level : levels) {
level.reset();
}
player.resetGameOver();
}
player.immunity = false;
level.startLevel();
newLevelAnimationLevelTimeDuration.reset();
loadLevelScreenTimeDuration.reset();
drawLevel();
} else {
newLevelAnimationLevelTimeDuration.update();
if (newLevelAnimationLevelTimeDuration.tick()) {
loadLevelScreenTimeDuration.update();
loadLevelDraw();
} else {
player.stopX();
player.jump = false;
player.immunity = true;
drawLevel();
}
}
} else {
if (pause) {
if (pauseKeyTimeDuration.tick()) {
pauseKeyFalse();
if (getKeyStatus("e_key") || getKeyStatus("E_key")) {
exit();
}
} else {
pauseKeyTimeDuration.update();
}
pauseDraw();
if (!pause) {
level.music.play();
} else {
level.music.pause();
}
} else {
if (pauseKeyTimeDuration.tick()) {
pauseKeyTrue();
} else {
pauseKeyTimeDuration.update();
}
drawLevel();
}
}
} else if (player.lives <= 0) // When the player runs out of lives
{
if (gameOverScreenTimeDuration.tick()) {
player.immunity = false;
level = levels.get(0);
for (Level level : levels) {
level.reset();
}
level.startLevel();
levelNameString.replace(0, levelNameString.length(), level.name);
player.resetGameOver();
gameOverScreenTimeDuration.reset();
deathAnimationLevelTimeDuration.reset();
} else {
deathAnimationLevelTimeDuration.update();
if (deathAnimationLevelTimeDuration.tick()) {
if (gameOverScreenTimeDuration.elapsed == 0) {
game_over_effect.play();
}
gameOverScreenTimeDuration.update();
gameOverDraw();
} else {
player.immunity = true;
drawLevel();
}
}
}
else //When the player dies
{
if (loadLevelScreenTimeDuration.tick()) {
player.immunity = false;
level.startLevel();
player.resetDead();
loadLevelScreenTimeDuration.reset();
deathAnimationLevelTimeDuration.reset();
} else {
deathAnimationLevelTimeDuration.update();
if (deathAnimationLevelTimeDuration.tick()) {
loadLevelScreenTimeDuration.update();
loadLevelDraw();
} else {
player.immunity = true;
drawLevel();
} } } }La classe Sprite gestisce gli sprite all’interno del gioco. Contiene
variabili per la texture, la larghezza e l’altezza dello sprite, la sua
posizione e velocità. La classe include anche metodi per rilevare
collisioni con altri sprite, per aggiornare le animazioni e per
disegnare lo sprite sullo schermo. Inoltre, fornisce funzioni per
controllare e fermare il movimento sia orizzontale che verticale dello
sprite, nonché per calcolare le posizioni centrali orizzontali e
verticali dello sprite. Da questa classe eridatano la maggior parte
delle altre classi del gioco.
Il codice è accessibile
qui,
ma in questa sezione andremo ad analizzare alcuni metodi degni di
nota.
Per gestire le animazioni dello sprite è possibile utilizzare il metodo
animation. Questo metodo accetta come parametri il set di immagini
dell’animazione e la frequenza di cambiamento dei frame.
void animation(ArrayList<PImage> currentFrameSet, int frameFrequency) {
this.currentFrameSet = currentFrameSet;
this.frameFrequency = frameFrequency;
texture = currentFrameSet.get(0);
frameCount = 0;
currentFrame = 0;
}Una volta impostata una animazione, questa viene gestita dal metodo update. La texture viene aggiornata in base al set di frame e alla frequenza di aggiornamento.
void update() {
position.y += speed.y;
position.x += speed.x;
if (frameFrequency > 0) {
if (frameCount >= frameFrequency) {
currentFrame = (currentFrame + 1) % currentFrameSet.size();
frameCount = 0;
texture = currentFrameSet.get(currentFrame);
}
frameCount += 1;
}
}Vale la pena analizzare anche il sistema di rilevamento delle collisioni. Le collisioni tra due sprite vengono analizzate su tutti e quattro i lati separatamente, in modo da poter essere gestite in maniera differente. Utilizzano un sistema di tolleranza che permette di rilevare la collisione solo entro un certo numero di pixel. Di seguito il metodo per gestire la collisione verso il basso.
boolean collideDown(Sprite sprite) {
boolean downCollisionTemp = false;
if (sprite.position.y - (position.y + height) <= 0 &&
sprite.position.y - (position.y + height) > -collisionFraction &&
sprite.position.x - (position.x + width) < 0 &&
(sprite.position.x + sprite.width) - position.x > 0)
{
downCollisionY = sprite.position.y - height;
downCollisionTemp = true;
}
return downCollisionTemp;
}La classe Entity estende la classe Sprite e gestisce le entità nel
gioco. Gestisce le collisioni, evitando che avvengano compenetrazioni e
quindi limitando il movimento quando necessario. La classe si occupa di
gestire la meccanica di salto, la gravità e il movimento automatico
delle entità autonome.
Il codice è accessibile
qui,
ma in questa sezione andremo ad analizzare il metodo update, che
contiene la logica di salto. Il salto viene gestito basandosi sulle
collisioni correnti, l’input di salto e i timeout di salto. Grazie a
questi controlli è possibile gestire sia il salto normale che il super
salto.
void update(ArrayList<Platform> platforms, ArrayList<PowerUp> powerUps) {
super.update();
if (exceedsLowerBound()) {
this.takeDamage(-1);
} else {
checkCollisions(platforms, powerUps); if (!downCollision && jumpStatus == 0 ) {
applyGravity();
} else if ((jump || jumpStatus > 0) && jumpTimeout == 0) {
if (jumpStatus<jumpValue && !upCollision) {
jumpStatus += 1;
jump = false;
applyInvertedGravity();
} else if (jump && jumpValue == smallJumpValue && jumpStatus > 3) {
jumpValue = superJumpValue;
} else {
jumpValue = smallJumpValue;
jumpStatus = 0;
jump = false;
jumpTimeout = JUMP_TIMEOUT_VALUE;
}
} else {
stopY();
}
if (jumpTimeout > 0 && downCollision) {
jump = false;
jumpTimeout -= 1;
}
}
}La classe Player si occupa di gestire il personaggio del giocatore nel
gioco. Estende la classe Entity e contiene variabili per gestire vite,
monete, potenziamenti e altro ancora. La classe include metodi per
gestire il movimento del giocatore, la raccolta delle monete, la
gestione dei danni, la morte del giocatore e l’aggiornamento dell’HUD.
Inoltre, gestisce la logica per i potenziamenti del giocatore e la
capacità di lanciare le palle di fuoco.
Il codice è accessibile
qui,
ma in questa sezione verranno analizzati alcuni metodi particolarmente
importanti.
Il metodo takeDamage si occupa di gestire la situazione in cui il
giocatore viene attaccato. Ha come parametro una variabile int che
rappresenta il valore del danno. Si occupa di gestire la morte del
player o il suo cambiamento di abilità a seconda del danno subito.
@Override
void takeDamage(int damage) {
super.takeDamage(damage);
if (!immunity) {
if (damage == -1) {
die();
} else {
if (damageTimeout == DAMAGE_TIMEOUT_VALUE) {
if (powerLevel - damage == 0 ) {
die();
} else {
powerLevel -= damage;
pipe_effect.play();
if (powerLevel == 1) {
basePower();
} else if (powerLevel == 2) {
superMushroomPower();
}
}
damageTimeout = 0;
}
}
}
}Il metodo getLife, simile al metodo coin, gestisce il guadagno di una vita da parte del giocatore e richiama il metodo updateLifeHud per aggiornare l’HUD.
void getLife() {
if (lives < 99) {
lives += 1;
one_up_effect.play();
updateLifeHud();
}
}Il metodo updateLifeHud si occupa di aggiornare la stringBuilder che verrà poi mostrata come HUD. Per farlo controlla se il numero di vite sia minore di 10, in modo da mostrare uno 0 davanti alla cifra nel caso in cui il valore sia a cifra singola.
void updateLifeHud() {
if (lives < 10) {
lifeHudString.replace(lifeHudString.length()-2, lifeHudString.length(), "0" + lives);
} else {
lifeHudString.replace(lifeHudString.length()-2, lifeHudString.length(), "" + lives);
}
}La classe FireBall gestisce le palle di fuoco lanciate da Mario ed
eredita della classe Entity. Si occupa di gestire il movimento della
palla di fuoco e il suo rimbalzo. Inoltre uccide l’entità dopo un numero
massimo di rimbalzi o dopo una collisione.
Il codice è accessibile
qui.
La classe Enemy estende la classe Entity e gestisce i nemici nel
gioco. Include variabili per la salute e il danno inflitto. Gestisce i
danni subiti dal giocatore e l’eventuale morte. Inoltre gestisce le
collisioni con altri nemici e con le palle di fuoco.
Il codice è accessibile
qui.
La classe Goomba estende la classe Enemy e gestisce i nemici di tipo
Goomba nel gioco. Utilizza il movimento automatico in orizzontale per i
suoi spostamenti, partendo dalla direzione specificata nel costruttore.
Si occupa di infliggere e subire danno all’interno dell’update.
Il codice è accessibile
qui.
La classe Koopa estende la classe Enemy e gestisce i nemici di tipo
Koopa nel gioco. Utilizza il movimento automatico in orizzontale per gli
spostamenti dell’entità attiva e sotto forma di guscio, partendo dalla
direzione specificata nel costruttore. Si occupa di infliggere e subire
danno all’interno dell’update. Gestisce, inoltre, i cambiamenti di stato
della tartaruga da attiva, a guscio fermo, a guscio in movimento.
Il codice è accessibile
qui.
La classe GreenKoopaTroopa estende la classe Koopa e gestisce i
nemici di tipo Green Koopa Troopa nel gioco. Sfrutta la classe da cui
eredita per effettuare tutte le sue operazioni, essendo il tipo più
semplice di Koopa.
Il codice è accessibile
qui.
La classe PowerUp estende la classe Entity e gestisce i
potenziamenti nel gioco. Funge da classe base per la creazione delle
sottoclassi power up che la estendono.
Il codice è accessibile
qui.
La classe Coin estende la classe PowerUp e gestisce le monete nel
gioco. Si occupa di gestire il guadagno di una moneta da parte del
giocatore quando viene colpita come entità. Non possiede gravità, quindi
può fluttuare nel mondo di gioco.
Il codice è accessibile
qui.
La classe SuperMushroom estende la classe PowerUp e gestisce i Super
Mushroom nel gioco. Si occupa di gestire l’attivazione dell’abilità
Super Mario quando viene colpita come entità. Gestisce automaticamente
il movimento in orizzontale.
Il codice è accessibile
qui.
La classe OneUpMushroom estende la classe PowerUp e gestisce gli One
Up Mushroom nel gioco. Si occupa di gestire il guadagno di una vita da
parte del giocatore quando viene colpita come entità. Gestisce
automaticamente il movimento in orizzontale.
Il codice è accessibile
qui.
La classe FireFlower estende la classe PowerUp e gestisce i Fire
Flower nel gioco. Si occupa di gestire l’attivazione dell’abilità Fire
Mario quando viene colpita come entità. È un’entità statica per quanto
riguarda il movimento orizzontale, ma possiede un valore di gravità.
Il codice è accessibile
qui.
La classe Flag estende la classe PowerUp e gestisce la bandiera di
fine livello nel gioco. Si occupa di gestire la fine del livello quando
viene colpita come entità. È un’entità statica e possiede uno sprite
aggiunto che non subisce le collisioni. Quando viene colpita nella parte
superiore fa guadagnare una vita al giocatore.
Il codice è accessibile
qui.
La classe Platform estende la classe Sprite e si occupa della
gestione delle piattaforme. Oltre alle proprietà che eredita, aggiunge i
valori di distruttibilità per lato, che rappresentano i valori di forza
necessari per rompere la piattaforma.
Il codice è accessibile
qui.
La classe QuestionBlock estende la classe Platform e si occupa della
gestione dei Question Block. Gestisce il rilascio dei power up quando
viene colpito, gestendo parti delle collisioni dentro l’update.
Il codice è accessibile
qui,
ma di seguito verrà analizzato il metodo hitQuestionBlock. Questo
metodo si occupa dell’effettivo rilascio del power up, calcolando la
posizione in cui farlo apparire in base alla propria.
void hitQuestionBlock() {
this.isActive = false;
this.emptyBlock();
if (this.powerUp == SUPER_MUSHROOM) {
level.powerUps.add(new SuperMushroom(this.position.x, GAME_HEIGHT - this.position.y + 90, this.isRight));
powerup_appears_effect.play();
} else if (this.powerUp == ONE_UP_MUSHROOM) {
level.powerUps.add(new OneUpMushroom(this.position.x, GAME_HEIGHT - this.position.y + 90, this.isRight));
powerup_appears_effect.play();
} else if (this.powerUp == FIRE_FLOWER) {
level.powerUps.add(new FireFlower(this.position.x + 15, GAME_HEIGHT - this.position.y + 90));
powerup_appears_effect.play();
}
}La classe Level si occupa di gestire i livelli all’interno del gioco.
Gestisce le piattaforme, i nemici e i power up presenti nel livello,
aggiornandoli, rimuovendoli se necessario e disegnandoli a schermo. Si
occupa anche della transizione della telecamera e dell’impostazione dei
limiti del livello. Disegna a schermo gli HUD e attiva la musica del
livello al suo avvio.
Il codice è accessibile
qui.
La classe Level0 estende la classe Level e si occupa di gestire il
livello iniziale del gioco. Questo livello, che funge da menù
principale, possiede alcune peculiarità rispetto ai livelli di default.
In questo livello, infatti, non sono presenti gli HUD, è presente un
testo a schermo con i comandi e il suono riprodotto al raggiungimento
della bandiera di fine livello è differente.
Il codice è accessibile
qui,
ma di seguito verranno analizzato alcuni metodi responsabili delle
modifiche specifiche di questo livello. Il primo metodo che subisce
delle modifiche è drawHud, al quale viene fatto l’override senza
effettuare, però, alcuna operazione. Ciò impedisce all’HUD di essere
mostrato a schermo.
@Override
void drawHud() {
}Il secondo metodo che differisce dal modello di default è draw. Infatti in questo metodo vengono disegnati il logo e la lista dei comandi.
@Override
void draw() {
super.draw();
logo.draw();
controls.draw();
}Il terzo metodo responsabile delle caratteristiche peculiari del livello iniziale è finished, che riproduce un suono differente, rispetto a quello di default, alla fine del livello.
@Override
void finished() {
isFinished = true;
music.stop();
lets_a_go_effect.play();
newLevelAnimationLevelTimeDuration.elapsed = 350;
updateNextLevelName();
}La classe Level1 estende la classe Level e si occupa di gestire il
primo livello del gioco. Si limita ad aggiungere le varie componenti del
livello, ereditando la logica di aggiornamento dalla classe Level.
Il codice è accessibile
qui.
La classe Level2 estende la classe Level e si occupa di gestire il
primo livello del gioco. Si limita ad aggiungere le varie componenti del
livello, ereditando la logica di aggiornamento dalla classe Level.
Il codice è accessibile
qui.
La classe Level3 estende la classe Level e si occupa di gestire il
primo livello del gioco. Si limita ad aggiungere le varie componenti del
livello, ereditando la logica di aggiornamento dalla classe Level.
Il codice è accessibile
qui.
La classe Timer si occupa di gestire la misurazione degli intervalli
intervalli di tempo nel gioco. È possibile controllare il tempo passato
dall’avvio del timer, il suo stato e gestire la sua reimpostazione.
Il codice è accessibile
qui.
La classe Text si occupa di gestire il testo nel gioco. Include
parametri per la posizione, il colore del font, la dimensione del testo,
il tipo di carattere e l’allineamento. Il testo da mostrare a schermo
viene gestito come StringBuilder, oggetto che permette il passaggio
per riferimento.
Il codice è accessibile
qui.
La classe Sound si occupa di gestire i suoni nel gioco. Sfrutta la
libreria per la gestione del suono fornita da Processing, e mette a
disposizione metodi per l’avvio, il loop, la pausa e lo stop. Permette,
inoltre, di regolare il volume e di controllare lo stato di
riproduzione.
Il codice è accessibile
qui.
La classe Key si occupa della gestione dei tasti della tastiera.
Gestisce la pressione e il rilascio del tasto, identificandolo
attraverso un keyCode.
Il codice è accessibile
qui.
Il file Platforms contiene alcuni metodi globali utilizzati per
semplificare la creazione e l’aggiunta delle piattaforme all’interno dei
livelli. Questi metodi rappresentano un’interfaccia ridotta e pratica
per istanziare quei blocchi presenti in quantità notevoli all’interno
del gioco.
Il codice è accessibile
qui.
Il file Global contiene la maggior parte delle costanti globali
presenti all’interno del gioco. Esse contengono infromazioni riguardanti
alcuni attributi generici del remake, il percorso degli assets e alcuni
parametri utilizzati nella fisica del gioco.
Il codice è accessibile
qui.
La creazione di questo remake è stato un progetto al quale mi sono
dedicato con entusiasmo ed impegno, e posso considerarmi soddisfatto del
risultato ottenuto fino ad ora. Lo sviluppo mi ha messo davanti a
moltissime difficoltà come la gestione delle collisioni con le
tolleranze o la gestione della fisica di gioco, ma aver risolto queste
sfide affinando le soluzioni con tentativi e ragionamento è stato più
che mai stimolante e formativo. Sento di aver maturato, grazie alla
creazione di questo progetto, un nuovo livello di competenze nella
programmazione ad oggetti e nello sviluppo di videogiochi.
Ci sono molti aspetti e meccaniche del gioco che non sono riuscito ad
aggiungere, ma sono motivato a continuare questo percorso, evolvendo
sempre di più quello che ho creato. Tra gli aspetti che mi piacerebbe
implementare nel prossimo futuro, rientrano sicuramente i tubi di
teletrasporto ai livelli sotterranei, l’aggiunta di più nemici e la
meccanica del nuoto. Mi piacerebbe anche continuare ad affinare la mia
abilità nel level design, che sicuramente era ed è uno dei punti di
forza della saga di Super Mario.
Ringrazio il prof. Fabio Barosi per avermi introdotto a questo nuovo
linguaggio di programmazione e avermi supportato nel corso del progetto.
Senza di lui o senza Miyamoto questa idea non avrebbe mai potuto
prendere vita.
-
Google (motore di ricerca)
-
Chat GPT
-
Processing
-
Nintendo
-
GitHub
-
Stack Overflow
-
Wikipedia
-
The Mushroom Kingdom
-
VideoGameSprites.net
-
MarioUniverse.com
-
Super Mario Bros. - Overworld (Main Theme) [Remix] by Qumu
-
Super Mario Bros. OST Remastered 80s Style by Aqua MIDI


