TP3: Super Mario Bros 1-1
Entrega: 19/05/2025 às 23:59h
Introdução
O Super Mario Bros (SMB), lançado pela Nintendo em 1985, foi um dos jogos mais populares da era dos consoles 8 bits. SMB é um jogo de plataforma de rolagem lateral onde o objetivo do jogador é se mover para a direita para chegar a um mastro de bandeira no final de cada nível. O jogador controla o Mario, protagonista da série. O irmão de Mario, Luigi, é controlado pelo segundo jogador no modo multijogador e assume o mesmo papel e funcionalidade de Mario. Na narrativa do jogo, o mundo é chamado de Reino do Cogumelo e o Mario está atravessando-o para salvar a Princesa Peach do antagonista Bowser. O video a seguir mostra um gameplay do jogo original:
Objetivo
O objetivo desse projeto é praticar a implementação de integração com editores externos, rolagem de câmera, detecção de colisão com AABBs e animação 2D. Para isso, você irá implementar as mecânicas básicas de correr, pular, e acertar inimigos no primeiro nível do SMB. Primeiro, você irá implementar um parser para carregar objetos do jogo a partir de arquivos csv exportados do editor de mapas [Tiled]. Como parte dessa tarefa, você irá implementar o componente DrawSpriteComponent para desenhar sprites estáticos (i.e., não-animados) na tela. Segundo, você irá implementar a rolagem de câmera, garantindo que o personagem sempre fique a frente da câmera. Terceiro, você irá implementar o componente AABBCollideComponent para detectar colisões entre caixas delimitadoras alinhadas com os eixos (AABBs). Quarto, você irá implementar o componente DrawAnimatedComponent, de animações de sprites, utilizando sprite sheets gerados pela [FreeTexturePacker]. Por fim, você irá implementar os goombas, incluindo as mecânicas de matá-los quando o jogador pula em cima deles e de matar o jogador quando eles o acertam no chão. O vídeo a seguir mostra um gameplay da versão que você irá implementar:
Código-base
Aceite o projeto tp3-super-mario-bros-1-1 no GitHub classroom [nesse link]
Clone o seu novo repositório no seu computador:
# Substitua <GITHUB_USERNAME> pelo seu usuário do GitHub git clone https://github.com/ufmg-dcc192/tp3-super-mario-bros-1-1-<GITHUB_USERNAME>.gitAbra o projeto tp3-super-mario-bros-1-1 na CLion e, antes de começar a sua implementação, verique com cuidado as definições de métodos e atributos de cada classe:
DrawSpriteComponent
Componente para desenho de sprites estáticos (i.e., não animados).
DrawAnimatedComponent
Componente para desenho de sprites animados (estende
DrawSpriteComponent).AABBColliderComponent
Componente para detecção de colisão entre AABBs.
Block
Classe que estende
Actorpara representar um bloco do jogo.Goomba
Classe que estende
Actorpara representar o Goomba, inimigo que se movimenta horizontalmente de um lado para o outro.Mario
Classe que estende
Actorpara representar o Mário, que é controlado pelo jogador.Spawner
Classe que estende
Actorpara representar um “gatilho” (ou trigger) que cria um goomba quando o jogador está próximo.
Observação: o código base desse projeto foi construído a partir do código do projeto anterior [TP2: Asteroids], portanto muitas das classes já foram introduzidas anteriormente.
Instruções
Parte 0: Preparação dos Assets
Antes de começar a programar, você irá preparar os assets (recursos) que serão carregados no início do jogo. Esses assets incluem os sprite sheets dos personagens e dos blocos da primeira fase do Super Mario Bros., além de um arquivo .csv com o tilemap dessa fase.
Sprite Sheet
Utilize o [FreeTexturePacker] para gerar os sprite sheets do mario, goomba e blocos. Desabilite a opção
Allow trim, utilize o formato de dadosjson (Array)e o métodoBestAreaFitpara exportar os sprite sheets. Copie os sprite sheets (imagens e dados) para os seus respectivos locais dentro do diretórioAssetsdo projeto. Por exemplo, copie o sprite sheet do Mario para o localAssets/Sprites/Mario.Tilemap
Utilize o [Tiled] para abrir o arquivo
Levels/Level1-1/level1-1.tmxe exportar seu conteúdo para um arquivoLevels/Level1-1/Level1-1.csv. Inspecione o csv gerado para compreender sua estrutura de tiles.
Parte 1: Carregando uma fase
Na primeira parte, você irá ler um arquivo .csv exportado pelo Tiled para carregar a primeira fase do jogo. Para poder visualizar os blocos na tela, você também terá que implementar o componente DrawSpriteComponent para desenhar sprites estáticos.
DrawSpriteComponent.cpp
- Implemente o construtor da classe e o método
Drawpara desenhar sprites estáticos
- Implemente o construtor da classe e o método
Block.cpp
- Implemente o construtor da classe para adicionar um componente
DrawSpriteComponentno construtor do bloco
- Implemente o construtor da classe para adicionar um componente
Mario.cpp
- Implemente o construtor da classe para adicionar um componente
DrawSpriteComponentno construtor do personagem
- Implemente o construtor da classe para adicionar um componente
Game.cpp
Implemente o método
LoadTexturepara carregar uma textura com a SDL. Para testar seu código até aqui, crie uma instância deBlockna funçãoInitializeActorscom uma textura da sua escolha.auto block = new Block(this, "../Assets/Sprites/Blocks/BlockA.png"); block->SetPosition(Vector2(5 * TILE_SIZE, 5 * TILE_SIZE));Implemente o método
LoadLevelpara ler o arquivo csv e instanciar uma matriz de inteirosLEVEL_HEIGHT X LEVEL_WIDTHrepresentando os IDs dos tiles. Teste o seu código chamando essa função emInitializeActor, carregando a fase do arquivolevel1-1.csv. Imprima a matriz na saída padrão comSDL_Loge compare a saída com o conteúdo do arquivo: eles devem ser os mesmos.Implemente o método
BuildLevelpara percorrer a matriz de tiles carregada no item anterior e instanciar game objects para o mario, os canos e os blocos. Para saber qual ID de tile corresponde a qual textura, abra o arquivolevel1-1.tmxe inspecione os IDs de cada tile. Inicialize as posições dos objetos no mundo de acordo com as suas posições na matriz de tiles. Guarde a instância do mario no atributomMarioda classe game. Os blocos não precisam ser armazenados na classe.Adicione ao método
InitializeActorsa chamada para as funçãoLoadLeveleBuildLevel, nessa ordem, para que a fase seja carregada e instanciada no momento de inicialização do jogo.
Ao final dessa etapa, você deveria ver uma saída como a ilustrada na figura abaixo:

Parte 2: Rolagem de Câmera
Na segunda parte, você irá implementar o movimento do jogador e da câmera, para que ele possa navegar horizontalmente sem colisão ao longo da fase.
Game.cpp
- Implemente o método
UpdateCamerapara fazer a câmera seguir o jogador
- Implemente o método
Mario.cpp
Adicione o componente
RigidBodyComponentno construtor do personagem para habilitar sua movimentação. Utilize a funçãoSetApplyGravitypara desabilitar a gravidade do personagem enquanto você estiver trabalhando na parte 2: isso irá facilitar o teste da câmera.Implemente o método
OnProcessInputpara mover o jogador horizontalmenteModifique o método
OnUpdatepara garantir que a posição horizontal do jogador esteja sempre à frente da câmera
Ao final dessa etapa, você deveria ver uma saída como no vídeo a seguir:
Parte 3: Detecção de Colisão com AABBs
Na terceira parte, você irá implementar o componente AABBColliderComponent para detectar colisões no jogo.
AABBColliderComponent.cpp
Implemente os métodos
GetMineGetMaxpara calcular os pontos de mínimo, máximo da AABB, respectivamenteImplemente método
Intersectpara verificar se duas AABBs têm interseçãoImplemente os método
GetMinVerticalOverlapeGetMinHorizontalOverlappara calcular as sobreposições verticais e horizontais da colisão, respectivamente.Implemente os métodos
DetectHorizontalCollisioneDetectVertialCollisionpara separar uma AABB após uma colisão horizontal e vertical, respectivamente.Implemente os métodos
ResolveHorizontalCollisionseResolveVerticalCollisionspara resolver as colisões horizontais e verticais, respectivamente, entre os objetos do jogo.
Block.cpp
- Adicione o componente
AABBColliderComponentao construtor dos blocos para habilitar colisões do jogador com os blocos do nível. Na criação, marque o bloco como estáticoisStatic = true.
- Adicione o componente
Mario.cpp
Habilite a gravidade no personagem, removendo a chamada para a função
SetApplyGravityque você adicionou na etapa anteriorAdicione o componente
AABBColliderComponentno construtor do personagem para habilitar colisões do jogador com os blocos do nível. Por padrão, o componente assume que o objeto não é estático, então não é necessário marcá-lo como não estático.Estenda o método
OnProcessInputpara implementar o pulo. O jogador só pode pular quando estiver no chãomIsOnGround = true.
Ao final dessa parte, você deveria ser capaz de navegar na primeira fase com as mecânicas de correr e pular, porém sem animação, como no vídeo a seguir:
Parte 4: Animações
Na quarta parte, você irá implementar o componente DrawAnimatedSprite para animar os objetos do jogo.
DrawAnimatedComponent.cpp
Todos os quadros de um objeto estão armazenados no vetor
mSpriteSheetData. Cada posição desse vetor é um ponteiro para umSDL_Rect*, representando as coordenadas de um sprite no sprite sheet. Além disso, todas as animações estão armazenadas no mapamAnimations. Uma animação é identificada por um nome (string) e definida por um vetor de índices de quadros (armazenados emmSpriteSheetData). A nome da animação corrente é armazenado na variável membromAnimName.Implemente o método
Updatepara atualizar o timer da animaçãoImplemente o método
Drawpara desenhar o sprite corrente da animaçãoImplemente o método
SetAnimationpara mudar a animação corrente
Mario.cpp
Modifique o construtor para que o personagem utilize o componente de desenho
DrawAnimatedComponentao invés deDrawSpriteComponent.Ao final desse item, você deveria ter o mesmo resultado da Parte 3, ou seja, o mário se movendo e colidindo mas sempre no estado idle.
Implemente a método
ManageAnimationspara selecionar a animação correta enquanto o jogador estiver vivo!mIsdead:- Se estiver no chão e correndo, a animação correta é
"run" - Se estiver no chão, mas não estiver correndo, a animação
"idle" - Se estiver não estiver no chão, a animação é
"jump"
- Se estiver no chão e correndo, a animação correta é
Ao final dessa parte, você deveria ver as animações de run, idle e jump tocando de acordo com o estado do jogador, como no vídeo a seguir:
Parte 5: Inimigos
Na quinta parte, você irá implementar os goombas e os spawners, que criam goombas quando o jogador está próximo.
Game.cpp
- Estenda o método
BuildLevelpara instanciar spawners de acordo com a matriz de tiles.
- Estenda o método
Goomba.cpp
Crie os componentes
RigidBodyComponent,AABBColliderComponent, eDrawAnimatedComponentno construtor, de forma similar ao MarioImplemente o método
Killpara tocar a animação de morte e desabilitar os componentesImplemente o método
OnUpdatepara destruir os goombas que já morreramImplemente o método
OnHorizontalCollisionpara alterar a direção do goomba quando ele colidir horizontalmente contra blocos ou outros goombas. Além disso, o Goomba deve matar o mario caso a colisão seja horizontal e contra o jogador.
Spawner.cpp
- Implemente o método
OnUpdatepara criar um goomba quando o jogador estiver próximo.
- Implemente o método
Mario.cpp
Modifique o método
OnUpdatepara detectar quando o jogador morreu.Implemente o método
Killpara tocar a animação de morte e finalizar o jogoImplemente o método
OnHorizontalCollisionpara matar o mario caso ele tenha colidido contra um goombaImplemente o método
OnVerticalCollisionpara matar o goomba caso o jogador tenha colidido contra um goomba
Essa etapa conclui as implementações básicas do jogo, que deveria ter um resultado como o mostrado no vídeo do início do roteiro.
Parte 6: Customização
Na sexta, e última etapa, você irá ajustar as variáveis do jogo para criar uma versão única do Super Mário Bros.
Altere os parâmetros de movimentação (velocidade, massa, coeficientes de atrito, etc.) do jogador para encontrar uma jogabilidade que mais lhe agrada.
Altere o nível dado ou crie um completamente novo.
Implemente a movimentação dos blocos de tijolo (tipo B) quando o mario os acerta por baixo. Nesse caso, o bloco deve se mover para cima e para baixo, retornando exatamente no ponto que estava originalmente.
Submissão
Para submeter o seu trabalho, basta fazer o commit e o push das suas alterações no repositório que foi criado para você no GitHub classroom.
git add .
git commit -m 'Submissão TP3'
git push
Barema
- Parte 1: Carregando uma fase (5%)
- Parte 2: Rolagem de Câmera (5%)
- Parte 3: Detecção de Colisão com AABBs (30%)
- Parte 4: Animações (30%)
- Parte 5: Inimigos (10%)
- Parte 6: Customização (20%)