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
Initialize
para inicializar o contador de tempomTicksCount
Utilize a função
SDL_GetTicks()
para inicializar o atributomTicksCount
de tal forma que ele represente o tempo (em milissegundos) decorrido desde a inicialização da SDL. -
Implemente o método
RunLoop
para executar o laço principal do jogoEscreva um laço
while
que é executado enquanto o atributomIsRunning
for verdadeiro. Dentro do laço, execute os métodosProcessInput()
,UpdateGame()
eGenerateOutput()
nessa ordem. -
Implemente o método
UpdateGame
para controlar a taxa de atualização de quadros-
Utilize a função
SDL_TICKS_PASSED()
para garantir que pelo menos16
milissegundos 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 tipofloat
chamadadeltaTime
; -
Verifique se deltaTime é superior a
0.05
segundos e, se for, limite-a para0.05
segundos; -
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
Actor
para adicionar o objeto ao jogoUtilize a função
AddActor
do jogo (mGame
) para adicionar o novo objeto (this
) ao jogo. -
Implemente o destrutor
~Actor
para remover o objeto ao jogo-
Utilize a função
RemoveActor
do jogo (mGame
) para remover esse objeto (this
) do jogo; -
Percorra o vetor de componentes
mComponents
deletando (delete
) cada um deles e, em seguida, limpe (clear
) o vetor de componentes.
-
-
Implemente o método
Update
para 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
ProcessInput
para 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
DrawComponent
para adicionar o componente desenhável ao jogoUtilize a função
AddDrawable
do jogo (mOwner->GetGame()
) para adicionar esse (this
) componente ao vetor de objetos desenháveis do jogo. -
Implemente o destrutor
~DrawComponent
para remover o componente desenhável ao jogoUtilize a função
RemoveDrawable
do jogo (mOwner->GetGame()
) para remover esse (this
) componente ao vetor de objetos desenháveis do jogo. -
Implemente o método
Draw
para desenhar um quadrado-
Utilize a função
SDL_SetRenderDrawColor()
para alterar a cor do renderer para branco; -
Crie um retângulo
SDL_Rect
para 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 atributosmWidth
emHeight
para obter a sua largura e altura respectivamente. Para deslocar a posição do objeto para o seu centro, subtraia da coordenadax
metade da largura do objeto (mWidth/2
) e da coordenaday
metade 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
UpdateActors
para atualizar os objetos do jogo-
Atribua verdadeiro para
mUpdatingActors
e, em seguida, escreva um laço para percorrer todos os elementos do vetor de objetos ativosmActors
chamando a funçãoUpdate(deltaTime)
para cada um deles. Ao final do laço, atribua falso paramUpdatingActors
; -
Escreva um laço
for
para percorrer todos os elementos do vetor de objetos pendentesmPendingActors
adicionando-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
deadActors
para armazenar ponteiros (std::vector<Actor*>
) para os objetos a serem destruídos. Depois, escreva um laçofor
para percorrer todos os elementos do vetor de objetos ativosmActors
adicionando os que estiverem no estadoActorState::Destroy
ao final do vetor de objetos mortosdeadActors
; -
Escreva um laço
for
para percorrer todos os elementos do vetordeadActors
e removê-los um a um.
-
-
Implemente o método
AddActor
para adicionar objetos ao jogoVerifique se o jogo está atualizando objetos (
mUpdatingActors == true
). Se estiver, adicione o novo objetoactor
ao final do vetor de objetos pendentesmPendingActors
. Se não, ao final do vetor de objetos ativosmActors
. -
Implemente o método
RemoveActor
para remover objetos do jogo-
Utilize a função
std::find
para procurar pelo objeto a ser removido no vetor de objetos pendentesmPendingActors
. Se encontrar, utilize a funçãostd::iter_swap
para trocar o objeto encontrado de posição com o último elemento demPendingActors
. Em seguida, remova o último elemento demPendingActors
com a funçãopop_back
. NÃO utilize odelete
para remover o elemento encontrado pois isso irá gerar um loop infinito; -
Utilize a função
std::find
para procurar pelo objeto a ser removido no vetor de objetos ativosmActors
. Se encontrar, utilize a funçãostd::iter_swap
para trocar o objeto encontrado de posição com o último elemento demActors
. Em seguida, remova o último elemento demActors
com a funçãopop_back
. NÃO utilize odelete
para remover o elemento encontrado pois isso irá gerar um loop infinito;
-
-
Implemente o método
AddDrawable
para adicionar um componente visual ao jogo-
Adicione o novo componente
drawable
ao final do vetor de componentes visuaismDrawables
; -
Ordene (
std::sort
) de forma crescente o vetor de componentes visuaismDrawables
de acordo com a prioridadeGetDrawOrder()
estabelecida na criação do componente;
-
-
Implemente o método
RemoveDrawable
para remover um componente visual ao jogoProcure (
std::find
) pelo componente dadodrawable
no vetor de componentes visuaismDrawables
e remova-o (erase
) desse vetor. -
Estenda o método
ProcessInput
para passar os eventos de entrada aos objetos do jogo-
Utilize a função
SDL_GetKeyboardState
para acessar o estado do jogo. Salve o estado em uma constanteUint8* state
; -
Percorra o vetor de objetos
mActors
chamando a funçãoProcessInput(state)
para cada um deles.
-
-
Estenda o método
GenerateOutput
para desenhar os componentes visuaisPercorra o vetor de componentes visuais
mDrawables
e chame a funçãoDraw(mRenderer)
para cada um deles. -
Estenda o método
Shutdown
para deletar os objetos do jogoPercorra o vetor de objetos
mActors
enquanto (while
) ele tiver elementos (!mActors.empty()
) deletando (delete
) o último elemento do vetor (mActors.back()
). É necessário usar um laçowhile
pois ofor
percorre 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
Paddle
para adicionar um componente de desenhoCrie um novo componente visual
DrawComponent
e atribua ao ponteiromDrawComponent
. -
Implemente o método
OnProcessInput
para atualizar a direção do movimento da raquete-
Reinicialize a direção da raquete
mDir
para0
; -
Verifique se tecla
w
está sendo pressionada. Se estiver, altere a direçãomDir
para-1
; -
Verifique se tecla
s
está sendo pressionada. Se estiver, altere a direçãomDir
para+1
.
-
-
Implemente o método
OnUpdate
para atualizar a posição da raquete-
Some à coordenada
y
da posição da raquetemPosition.y
a velocidade da raquetemVerticalSpeed
multiplicada pela sua direçãomDir
e pelo tempo decorrido desde o último quadrodeltaTime
; -
Limite a coordenada
y
da 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
Ball
para adicionar um componente de desenhoCrie um novo componente visual
DrawComponent
e atribua ao ponteiromDrawComponent
. -
Implemente o método
OnUpdate
para 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.x
deve ser positiva e a posição horizontal da bolamPosition.x
deve 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.y
deve ser negativa e a posição vertical da bolamPosition.y
deve 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
InitializeActors
para inicializar a bola e a raquete-
Instancie a raquete
mPaddle
e inicialize sua posição com o métodoSetPosition
; -
Instancie a bola
mBall
e inicialize sua posição e velocidade com os métodosSetPosition
eSetVelocity
, 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%)