P2: Pong
Política de Atraso
- A penalização será de 15% para cada dia de atraso.
- Cada atraso pode ser de no máximo 2 dias.
Introdução
Um dos primeiros e mais populares jogos da era do fliperama é o Pong, desenvolvido pela Atari em 1972. O pong simula um jogo de tênis de mesa, onde cada jogador controla verticalmente uma raquete posicionada em uma das extremidades da tela, com o objetivo de rebater uma bola de tal maneira que o oponente não consiga rebater de volta. Cada vez que um jogador não conseguir rebater a bola, o oponente receberá um ponto. O jogo termina quando um dos jogadores completar 11 pontos. Tanto as raquetes e a bola quanto as marcações de meio de campo e de pontuação são representados por retângulos brancos. O video a seguir mostra um gameplay do jogo original:
Objetivo
Nesse projeto, você irá desenvolver uma versão de 1 jogador do jogo Pong em C++ e SDL. Nessa versão, o jogador controla a raquete com o objetivo de rebater a bola contra a parede o maior número de vezes possível. Primeiro, você irá criar o laço principal do jogo (game loop) com uma taxa de quadros (framerate) dinâmica, que processa entradas do teclado, atualiza os objetos do jogo e renderiza os quadros. A modelagem de objetos terá uma arquitetura híbrida, com hierarquia de classes e componentes. Em seguida, você irá utilizar essa estrutura para definir os objetos de jogo do pong. O video a seguir mostra um gameplay da versão que você irá implementar:
Inicialização
Aceite o projeto p2-pong no GitHub classroom [nesse link] e clone o seu novo repositório no seu computador:
# Substitua <GITHUB_USERNAME> pelo seu usuário do GitHub
git clone https://github.com/ufv-inf216/p2-pong-<GITHUB_USERNAME>.git
Código Base
Abra o projeto p2-pong 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:
-
Game
Classe responsável por inicializar, gerenciar o laço principal e finalizar o jogo.
-
Actor
Classe base para todos os objetos do jogo, contendo atributos para transformação (translação, rotação e escala) e métodos para processamento de eventos de entrada, atualização e gerenciamento de componentes.
-
Component
Classe base para todos os componentes do jogo, contendo métodos para processamento de eventos de entrada e atualização.
-
DrawComponent
Componente de desenho de objetos com retângulos coloridos.
-
Ball
Classe que estende Actor para representar a bola do jogo Pong.
-
Paddle
Classe que estende Actor para representar a raquete do jogo Pong.
Instruções
Parte 1: Game Loop
Na primeira parte, você irá implementar o laço principal do jogo utilizando uma abordagem de taxa de quadros dinâmica.
-
Game.cpp
-
Estenda o método
Initializepara inicializar o contador de tempomTicksCountUtilize a função
SDL_GetTicks()para inicializar o atributomTicksCountde tal forma que ele represente o tempo (em milissegundos) decorrido desde a inicialização da SDL. -
Implemente o método
RunLooppara executar o laço principal do jogoEscreva um laço
whileque é executado enquanto o atributomIsRunningfor verdadeiro. Dentro do laço, execute os métodosProcessInput(),UpdateGame()eGenerateOutput()nessa ordem. -
Implemente o método
UpdateGamepara controlar a taxa de atualização de quadros-
Utilize a função
SDL_TICKS_PASSED()para garantir que pelo menos16milissegundos tenham se passado desde o último quadro (mTicksCount + 16); -
Utilize a função
SDL_GetTicks()para obter o tempo (em ms) decorrido até o quadro atual e subtraia pormTicksCount, obtendo o tempo (em ms) entre o quadro atual e o passado. Converta o resultado para segundos e armazene o resultado em uma variável do tipofloatchamadadeltaTime; -
Verifique se deltaTime é superior a
0.05segundos e, se for, limite-a para0.05segundos; -
Utilize a função
SDL_GetTicks()para atualizar o contador de tempomTicksCount; -
Chame a função
UpdateActors(deltaTime)para atualizar os objetos do jogo;
-
-
Parte 2: Modelo de Objetos
Na segunda parte, você irá implementar uma estrutura de objetos com hierarquia de classes e componentes.
-
Actor.cpp
-
Implemente o construtor
Actorpara adicionar o objeto ao jogoUtilize a função
AddActordo jogo (mGame) para adicionar o novo objeto (this) ao jogo. -
Implemente o destrutor
~Actorpara remover o objeto ao jogo-
Utilize a função
RemoveActordo jogo (mGame) para remover esse objeto (this) do jogo; -
Percorra o vetor de componentes
mComponentsdeletando (delete) cada um deles e, em seguida, limpe (clear) o vetor de componentes.
-
-
Implemente o método
Updatepara atualizar os componentesVerifique se o objeto está no estado (
mState) ativo (ActorState::Active). Se estiver, percorra o vetor de componentes, chamando a funçãoUpdate(deltaTime)para cada um deles e, em seguida, chame a funçãoOnUpdate(deltaTime). -
Implemente o método
ProcessInputpara processar a entradaDe forma similar ao método
Update, verifique se o objeto está no estado (mState) ativo (ActorState::Active). Se estiver, percorra o vetor de componentes, chamando a funçãoProcessInput(keyState)para cada um deles e, em seguida, chame a funçãoOnProcessInput(keyState).
-
-
DrawComponent.cpp
-
Implemente o construtor
DrawComponentpara adicionar o componente desenhável ao jogoUtilize a função
AddDrawabledo jogo (mOwner->GetGame()) para adicionar esse (this) componente ao vetor de objetos desenháveis do jogo. -
Implemente o destrutor
~DrawComponentpara remover o componente desenhável ao jogoUtilize a função
RemoveDrawabledo jogo (mOwner->GetGame()) para remover esse (this) componente ao vetor de objetos desenháveis do jogo. -
Implemente o método
Drawpara desenhar um quadrado-
Utilize a função
SDL_SetRenderDrawColor()para alterar a cor do renderer para branco; -
Crie um retângulo
SDL_Rectpara representar o objeto visualmente. A posição do retângulo deve ser o centro do objeto (não o canto esquerdo superior, como originalmente definido pela SDL). Isso facilitará os cálculos de colisão. Utilize a funçãomOwner->GetPosition()para obter a posição original do objeto (canto esquerdo superior) e os atributosmWidthemHeightpara obter a sua largura e altura respectivamente. Para deslocar a posição do objeto para o seu centro, subtraia da coordenadaxmetade da largura do objeto (mWidth/2) e da coordenadaymetade da altura (mHeight/2). Atribua o resultado dessas operações como posição final do retângulo criado. Altura e largura do objeto não precisam ser transformadas. -
Desenhe o retângulo criado com a função
SDL_RenderFillRect().
-
-
-
Game.cpp
-
Implemente o método
UpdateActorspara atualizar os objetos do jogo-
Atribua verdadeiro para
mUpdatingActorse, em seguida, escreva um laço para percorrer todos os elementos do vetor de objetos ativosmActorschamando a funçãoUpdate(deltaTime)para cada um deles. Ao final do laço, atribua falso paramUpdatingActors; -
Escreva um laço
forpara percorrer todos os elementos do vetor de objetos pendentesmPendingActorsadicionando-os ao final do vetor de objetos ativosmActors. Após o laço, remova todos os elementos do vetor de objetos pendentesmPendingActors; -
Crie um vetor chamado
deadActorspara armazenar ponteiros (std::vector<Actor*>) para os objetos a serem destruídos. Depois, escreva um laçoforpara percorrer todos os elementos do vetor de objetos ativosmActorsadicionando os que estiverem no estadoActorState::Destroyao final do vetor de objetos mortosdeadActors; -
Escreva um laço
forpara percorrer todos os elementos do vetordeadActorse removê-los um a um.
-
-
Implemente o método
AddActorpara adicionar objetos ao jogoVerifique se o jogo está atualizando objetos (
mUpdatingActors == true). Se estiver, adicione o novo objetoactorao final do vetor de objetos pendentesmPendingActors. Se não, ao final do vetor de objetos ativosmActors. -
Implemente o método
RemoveActorpara remover objetos do jogo-
Utilize a função
std::findpara procurar pelo objeto a ser removido no vetor de objetos pendentesmPendingActors. Se encontrar, utilize a funçãostd::iter_swappara trocar o objeto encontrado de posição com o último elemento demPendingActors. Em seguida, remova o último elemento demPendingActorscom a funçãopop_back. NÃO utilize odeletepara remover o elemento encontrado pois isso irá gerar um loop infinito; -
Utilize a função
std::findpara procurar pelo objeto a ser removido no vetor de objetos ativosmActors. Se encontrar, utilize a funçãostd::iter_swappara trocar o objeto encontrado de posição com o último elemento demActors. Em seguida, remova o último elemento demActorscom a funçãopop_back. NÃO utilize odeletepara remover o elemento encontrado pois isso irá gerar um loop infinito;
-
-
Implemente o método
AddDrawablepara adicionar um componente visual ao jogo-
Adicione o novo componente
drawableao final do vetor de componentes visuaismDrawables; -
Ordene (
std::sort) de forma crescente o vetor de componentes visuaismDrawablesde acordo com a prioridadeGetDrawOrder()estabelecida na criação do componente;
-
-
Implemente o método
RemoveDrawablepara remover um componente visual ao jogoProcure (
std::find) pelo componente dadodrawableno vetor de componentes visuaismDrawablese remova-o (erase) desse vetor. -
Estenda o método
ProcessInputpara passar os eventos de entrada aos objetos do jogo-
Utilize a função
SDL_GetKeyboardStatepara acessar o estado do jogo. Salve o estado em uma constanteUint8* state; -
Percorra o vetor de objetos
mActorschamando a funçãoProcessInput(state)para cada um deles.
-
-
Estenda o método
GenerateOutputpara desenhar os componentes visuaisPercorra o vetor de componentes visuais
mDrawablese chame a funçãoDraw(mRenderer)para cada um deles. -
Estenda o método
Shutdownpara deletar os objetos do jogoPercorra o vetor de objetos
mActorsenquanto (while) ele tiver elementos (!mActors.empty()) deletando (delete) o último elemento do vetor (mActors.back()). É necessário usar um laçowhilepois oforpercorre por índices e estes seriam desconfigurados no momento da remoção (delete).
-
Parte 3: Objetos do Pong
Na terceira, você irá utilizar a estrutura de objetos criada na parte anterior para criar os objetos do Pong: Ball e Paddle.
-
Paddle.cpp
-
Implemente o construtor
Paddlepara adicionar um componente de desenhoCrie um novo componente visual
DrawComponente atribua ao ponteiromDrawComponent. -
Implemente o método
OnProcessInputpara atualizar a direção do movimento da raquete-
Reinicialize a direção da raquete
mDirpara0; -
Verifique se tecla
westá sendo pressionada. Se estiver, altere a direçãomDirpara-1; -
Verifique se tecla
sestá sendo pressionada. Se estiver, altere a direçãomDirpara+1.
-
-
Implemente o método
OnUpdatepara atualizar a posição da raquete-
Some à coordenada
yda posição da raquetemPosition.ya velocidade da raquetemVerticalSpeedmultiplicada pela sua direçãomDire pelo tempo decorrido desde o último quadrodeltaTime; -
Limite a coordenada
yda raquete para que ela não ultrapasse os limites superior e inferior da tela. Utilize a funçãomGame->GetWindowHeight()para acessar a altura da tela. Lembre-se de que a posição da raquete se refere ao centro dela.
-
-
-
Ball.cpp
-
Implemente o construtor
Ballpara adicionar um componente de desenhoCrie um novo componente visual
DrawComponente atribua ao ponteiromDrawComponent. -
Implemente o método
OnUpdatepara atualizar a posição da bola-
Some à posição horizontal da bola (
mPosition.x) a sua velocidade horizontal (mVelocity.x) multiplicado pelo tempo decorrido desde o último quadro (deltaTime); -
Some à posição vertical da bola (
mPosition.y) a sua velocidade vertical (mVelocity.y) multiplicado pelo tempo decorrido desde o último quadro (deltaTime); -
Calcule as distâncias vertical e horizontal entra a bola e a raquete. Utilize a função
paddle->GetPosition()para acessar a posição da raquete. - Verifique se a bola colidiu com a raquete. Se houver colisão, inverta (multiplique por
-1) a velocidade horizontal da bola. Para que haja colisão, as seguintes condições devem ser satisfeitas:- A velocidade horizontal da bola (
mVelocity.x) deve ser negativa; - A distância vertical entra a bola e a raquete deve ser menor ou igual à metade da altura da raquete mais a metade tamanho da bola (
mSize/2). Utilize a função (paddle->GetHeight()) para acessar a altura da raquete; - A distância horizontal entre a bola e a raquete deve ser menor que a largura da raquete (
paddle->GetWidth());
- A velocidade horizontal da bola (
-
Verifique se a bola saiu pelo lado esquerdo da tela (zero). Se saiu, finalize o jogo chamando a função Quit do jogo
game->Quit(); -
Verifique se a bola colidiu com o lado direito da tela. Se houver colisão, inverta (multiplique por
-1) a velocidade horizontal da bola. Para que haja colisão, a velocidade horizontal da bolamVelocity.xdeve ser positiva e a posição horizontal da bolamPosition.xdeve ser maior ou igual à largura da tela menos a metade do tamanho da bolamSize/2. Utilize a funçãogame->GetWindowWidth()para acessar a largura da tela; -
Verifique se a bola colidiu com o limite superior tela. Se houver colisão, inverta (multiplique por
-1) a velocidade vertical da bola. Para que haja colisão, a velocidade vertical da bolamVelocity.ydeve ser negativa e a posição vertical da bolamPosition.ydeve ser menor ou igual ao limite superior da tela (zero) mais a metade do tamanho da bola (mSize/2). - Verifique se a bola colidiu com o limite inferior da tela. Se houver colisão, inverta (multiplique por
-1) a velocidade vertical da bola. Para que haja colisão, a velocidade vertical da bola (mVelocity.y) deve ser positiva e a posição vertical da bola (mPosition.y) deve ser maior ou igual ao limite inferior da tela (altura) menos a metade do tamanho da bola (mSize/2). Utilize a funçãogame->GetWindowHeight()para acessar a altura da tela.
-
-
-
Game.cpp
-
Implemente o método
InitializeActorspara inicializar a bola e a raquete-
Instancie a raquete
mPaddlee inicialize sua posição com o métodoSetPosition; -
Instancie a bola
mBalle inicialize sua posição e velocidade com os métodosSetPositioneSetVelocity, respectivamente;
-
-
Parte 4: Customização
Na quarta, e última etapa, você irá ajustar as variáveis do jogo para criar uma versão única do Pong.
-
Escolha um novo tamanho de quadra (janela);
-
Defina um novo esquema de cores que modifique as cores do fundo, da raquete e da bola;
-
Escolha uma nova posição horizontal e uma velocidade de movimentação para raquete;
-
Altere a altura da raquete e o tamanho da bola.
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 P2'
git push
Barema
- Parte 1: Game Loop (10%)
- Parte 2: Modelo de Objetos (50%)
- Parte 3: Objetos do Pong (30%)
- Parte 4: Customização (10%)