P3: Asteroids
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
Assim como o Pong (projeto anterior), o Asteroids (lançado pela Atari em 1979) também foi um dos jogos mais populares da era do fliperama. O asteroids é um jogo com uma temática espacial onde o jogador controla uma nave com o objetivo de atirar raios laser para destruir todos os asteroides que se movem no mapa sem colidir com nenhum deles. Quando o jogador destruir todos os asteroides do mapa, um número maior de asteroides do que o anterior será criado no mapa, aumentando a dificuldade do jogo. Se o jogador colidir com um asteroid, ele perderá uma vida. O jogo termina quando o jogador perder suas três vidas. O video a seguir mostra um gameplay do jogo original:
Objetivo
Nesse projeto, você irá desenvolver as mecânicas princiais de movimentação, colisão e tiro do Asteroids em C++ e SDL. Nessa versão, o jogador poderá mover-se e atirar com a nave como no jogo original, destruindo os asteroides caso um laser os acerte. O jogo será reiniciado quando a nave colidir com um asteroide. Essa versão não conterá a mecânica de gerar três novos asteroides menores quando um grande é destruído e não terá progressão de dificuldade quando o jogador destruir todos os asteroides.
Inicialmente você irá implementar os componentes RigidBodyComponent e CircleColliderComponent para movimentar e detectar as colisões dos objetos do jogo. Em seguida, você irá modificar o componente DrawCollider para desenhar os objetos simulando gráficos vetoriais. Por fim, você irá utilizar esses componentes para implementar uma nave que atira raios laser e gerar um dado número de asteroides com geometrias aleatórias.
Inicialização
Aceite o projeto p3-asteroids 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/p3-asteroids-<GITHUB_USERNAME>.git
Código Base
Abra o projeto p3-asteroids 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. O código base desse projeto foi construído a partir do código do projeto anterior [P2: Pong], portanto muitas das classes já foram introduzidas anteriormente. As novas classes desse projeto são:
-
RigidBodyComponent
Componente de movimentação de objetos rígidos.
-
CircleColliderComponent
Componente de detecção de colisão baseado em comparações entre círculos.
-
Ship
Classe que estende
Actorpara representar a nave. -
Asteroid
Classe que estende
Actorpara representar um asteroide. -
Laser
Classe que estende
Actorpara representar uma partícula de laser, que é atirada pela nave quando o jogador pressiona a tecla Espaço.
Instruções
Parte 1: Movimentação de Objetos Rígidos
Na primeira parte, você irá implementar os componentes RigidBodyComponent e CircleColliderComponent para movimentar e detectar as
colisões dos objetos do jogo.
-
RigidBodyComponent.cpp
-
Implemente o método
ApplyForcepara adicionar uma força à acceleração- Adicione ao atributo
mAccelerationa forçaforce, passada como parâmetro, dividida pela massamMassdo objeto.
- Adicione ao atributo
-
Implemente o método
Updatepara calcular a nova posição do objeto utilizando o Método de Euler Semi-implícito-
Atualize a velocidade
mVelocitye a posiçãopositiondo objeto utilizando o Método de Euler Semi-implícito. -
Utilize a função
Math::NearZeropara verificar se o comprimento do vetor velocidademVelocityestá próximo dezero. Se estiver, use a funçãomVelocity.Setpara forçar velocidadezero. Isso evita movimentos muito pequenos. -
Utilize a função
mAcceleration.Setpara reinicializar a aceleração emzero. -
Some à rotação atual do objeto
rota velocidade angularmAngularSpeedmultiplicada pelodeltaTime.
-
-
Implemente o método
ScreenWrappara teletransportar os objetos para o lado oposto quando eles saírem da tela-
Verifique se o objeto saiu pelo lado esquerdo da tela. Se tiver saído, altere sua posição horizontal para ser igual à largura da tela. Caso contrário, verifique se o objeto saiu pelo lado direito. Se tiver saído, altere sua posição horizontal para ser igual a zero.
-
Verifique se o objeto saiu por cima da tela. Se tiver saído, altere sua posição vertical para ser igual à altura da tela. Caso contrário, verifique se o objeto saiu por baixo. Se tiver saído, altere sua posição vertical para ser igual a zero.
-
-
-
CircleColliderComponent.cpp
-
Implemente o método
Intersectpara detectar a colisão de círculos com círculos-
Calcule o quadrado da distância entre o centro desse círculo (
GetCenter) e o do círculo c passado como parâmetro (c.GetCenter). Para isso, subtraia do centro desse círculo o centro do círculo c e armazene o resultado em um vetordiff. Depois use o métododiff.LengthSqpara calcular o quadrado da distância entre os centros e armazene o resultado em um escalardistSq. -
Calcule o quadrado da soma dos raios e armazene o resultado em um escalar
radiiSq. -
Retorne verdadeiro se
distSqfor menor ou igual aradiiSq. Caso contrário, retorne falso.
-
-
Parte 2: Desenhos Vetoriais
Na segunda parte, você irá modificar o componente DrawComponent para desenhar os objetos do jogo simulando gráficos vetoriais.
-
DrawComponent.cpp
-
Implemente o método
DrawPolygonpara desenhar um polígono formado por um conjunto de vértices-
Percorra do primeiro (
i = 0) até o penúltimo (i = vertices.size() - 1) vértice utilizando a função [SDL_RenderDrawLine] para desenhar as linhas entre os vérticesiei+1. -
Utilize a função [SDL_RenderDrawLine] para desenhar uma linha entre o último e o primeiro vértices.
-
-
Implemente o método
DrawCirclepara gerar e desenhar um conjunto de vértices em um círculo-
Inicialize uma variável
angle(float) comzero. Ela será utilizada para percorrer o arco de uma circunferência em intervalos angulares de tamanho fixo. -
Repita o seguinte procedimento para um dado número de vértices
numVertices:-
Calcule a coordenada
xdo novo vértice multiplicando o raio da circunferênciaradiuspelo cosseno do ângulo correnteangle; -
Calcule a coordenada
yda mesma forma, porém multiplicando pelo seno do ângulo corrente; -
Adicione o vetor
(x,y)ao conjunto de vérticesvertices; -
Incremente o ângulo corrente por
2*PIdividido pelo número de vérticesnumVertices.
-
-
-
Implemente o método
Drawpara desenhar um objeto-
Utilize a função
Matrix3::CreateRotationpara criar uma matriz de rotação com o ângulo do dono desse componentemOwner->GetRotation. -
Percorra os vértices desse componente
mVerticesmultiplicando-os pela matriz de rotação com a funçãoVector2::Transform. Adicione o vetor transformado a uma coleção (e.g.,std::vector) temporária de vertices. -
Utilize a função [SDL_SetRenderDrawColor] para alterar a cor de desenho para branco.
-
Chame a função
DrawPolygonpara desenhar o conjunto de vértices transformados. -
Utilize a função
DrawCirclepara desenhar o círculo de colisão desse objeto. Antes de desenhar, altere a cor para verde com a função [SDL_SetRenderDrawColor]. Esse trecho de código é útil para debugar a detecção de colisão.
-
-
Parte 3: Objetos do Asteroids
Na terceira parte, você irá utilizar os novos componentes para implementar uma nave que atira raios laser e gerar um dado número de asteroides com geometrias aleatórias.
-
Game.cpp
-
Inicialize a classe
Randompara gerar números aleatórios- Uma biblioteca
Random.hfoi incluída nesse projeto para a geração de números aleatórios. Utilize a funçãoRandom::Initpara inicializar o gerador de números aleatórios.
- Uma biblioteca
-
Instancie a nave e os asteroides
-
Instancie um objeto da classe
Shipcom20pixels de altura e armazene seu ponteiro emmShip. Em seguida, posicione a nave (mShip->SetPosition) no meio da tela. Lembre-se que as variáveismWindowWidthemWindowHeightarmazenam as dimensões da tela. -
Escreva um laço para instanciar
10objetos da classeAsteroid, cada um com80pixels de raio.
-
-
Implemente os métodos
AddAsteroideRemoveAsteroidpara gerenciar a criação e remoção de asteroides-
No método
AddAsteroid, adicione (emplace_back) o asteroideastao vetor de asteroidesmAsteroids. -
No método
RemoveAsteroid, utilize a funçãostd::findpara procurar pelo asteroideastno vetor de asteroidesmAsteroids. Se o encontrar, remova-o do vetor de asteroides (mAsteroids.erase).
-
-
-
Ship.cpp
-
Implemente o construtor de
Shipcriando um triângulo para representar a nave visualmente e instanciando seus componentes-
Crie 3 vértices (
Vector2) considerando o centro da nave como origem e o atributomHeightcomo altura do triângulo. Por exemplo:v1 = (-h, h/2),v2 = (h, 0)ev3 = (-h, -h/2) -
Adicione esses 3 vértices em um contêiner
std::vector. -
Instancie os componentes
DrawComponent,RigidBodyComponenteCircleColliderComponent. Armazene seus ponteiros emmDrawComponent,mRigidBodyComponentemCircleColliderComponentrespectivamente. O contêiner de vértices criado na etapa anterior será passado como parâmetro para oDrawComponente. E, para oCircleColliderComponent, passe a metade da altura da nave como raio de colisão.
-
-
Ler os eventos do teclado para controlar a nave
-
Verifique se o jogador está pressionando a tecla
We, se estiver, aplique uma força para frente com magnitude dada pelo atributomForwardSpeed. Utilize o métodoGetForwardpara obter o vetor da frente e a funçãoApplyForcedo componentemRigidBodyComponentpara aplicar a força. -
Inicialize uma variável local chamada
angularSpeedcom0.0e verifique se o jogador está pressionando a teclaA. Se estiver, some a essa variável a velocidade de rotaçãomRotationForce. -
Verifique se o jogador está pressionando a tecla
D. Se estiver, subtraia da velocidade angularangularSpeeda velocidade de rotaçãomRotationForce. -
Verifique se o jogador está pressionando a tecla
espaçoe se o tempo de resfriamento do laser já terminoumLaserCooldown <= 0f. Se ambas as condições forem verdadeiras:-
Instancie uma nova partícula de laser com
5.0pixels de comprimento; -
Posicione essa partícula na ponta da frente da nave (posição da nave
+vetor forward*altura do triângulo da nave); -
Inicialize a rotação dessa partícula com o ângulo da nave. Basta utilizar os métodos
SetRotationdo laser eGetRotationda nave; -
Aplique uma força para frente nessa partícula com magnitude
3000.0; -
Reinicialize o tempo de resfriamento do laser em um quarto de segundo (
0.25).
-
-
Altere a velocidade angular com o novo valor calculado
angularSpeed. Utilize a funçãoSetAngularSpeed.
-
-
Implemente o método
OnUpdatepara atualizar a nave a cada quadro-
Subtraia
deltaTimedo tempo de resfriamento do lasermLaserCooldown -
Calcule a força de resistência do meio para parar lentamente a nave. Lembre-se de que essa força é um vetor
f_r = -v.norm() * ||v||^2 * c_r, ondevé o vetor velocidadevelocityec_ré o coeficiente de resistênciamFrictionCoefficient. Armazene a força calculada em um vetor chamadodragForce. -
Aplique a força
dragna nave com a funçãoApplyForcedomRigidBodyComponent -
Percorra a lista de asteroides do jogo e verifique para cada asteroide se ele está colidindo com a nave. O método
GetGame()->GetAsteroidsretorna a lista de asteroides. Além disso, você já implementou o métodoIntersectdoCircleColliderComponent. Tanto a nave quanto os asteroides possuem esse componente, então basta utilizar essa função para verificar a colisão. Se houver colisão da nave com algum asteroide, termine o jogo (GetGame()->Quit).
-
-
-
Asteroid.cpp
-
Implemente o construtor
Asteroidgerando um círculo com ruídos para representar o asteroide visualmente e instanciando seus componentes-
Utilize a função
Random::GetVectorpara gerar uma posição aleatória inicial para o asteroide. Garanta que essa posição inicial não resultará em uma colisão com a configuração inicial da nave. Utilize a funçãoSetPositionpara alterar a posição inicial do asteroide com a posição gerada. -
Instancie os componentes
DrawComponent,RigidBodyComponenteCircleColliderComponent. Armazene seus ponteiros emmDrawComponent,mRigidBodyComponentemCircleColliderComponentrespectivamente. O contêiner de vértices criado na etapa anterior será passado como parâmetro para oDrawComponente. E, para oCircleColliderComponent, passe a média dos comprimentos dos vértices geradosaverageLengthcomo raio de colisão. -
Aplique a força aleatória gerada anteriormente
randStartingForcepara mover o asteroide. Utilize a funçãoApplyForcedo componentemRigidBodyComponent. -
Adicione (
game->AddAsteroid) esse asteroide,this, à lista de asteroides do jogo.
-
-
Implemente o destrutor
~Asteroid- Remova (
game->RemoveAsteroid) esse asteroide,this, da lista de asteroides do jogo.
- Remova (
-
Gere um conjunto de vértices em uma circunferência adicionando um pequeno ruído a cada um deles
-
Inicialize uma variável
angle(float) comzero. Ela será utilizada para percorrer o arco de uma circunferência em intervalos angulares de tamanho fixo. -
Repita o seguinte procedimento para um dado número de vértices
numVertices:-
Gere um número real entre
0.5e1.0e multiplique-o pelo raio da circunferência (radius). Armazene o resultado em uma variávelrandLength; -
Calcule a coordenada
xdo novo vértice multiplicandorandLengthpelo cosseno do ângulo correnteangle; -
Calcule a coordenada
yda mesma forma, porém multiplicando pelo seno do ângulo corrente; -
Adicione o vetor
(x,y)ao conjunto de vérticesvertices; -
Incremente o ângulo corrente por
2*PIdividido pelo número de vérticesnumVertices.
-
-
-
-
Laser.cpp
-
Implemente o construtor
Lasergerando um segmento de reta para representar o asteroide visualmente e instanciando seus componentes-
Crie
2vértices (Vector2) considerando o centro da nave como origem e o atributomLengthcomo o comprimento do raio laser. Por exemplo:v1 = (-l/2, 0)ev2 = (l/2, 0). -
Adicione esses
3vértices em um contêinerstd::vector. -
Instancie os componentes
DrawComponent,RigidBodyComponenteCircleColliderComponent. Armazene seus ponteiros emmDrawComponent,mRigidBodyComponentemCircleColliderComponentrespectivamente. O contêiner de vértices criado na etapa anterior será passado como parâmetro para oDrawComponente. Para oRigidBodyComponent, passe uma massa pequena (e.g.,0.1) como parâmetro. Para oCircleColliderComponent, passe o comprimento do raio lasesmLenghtcomo raio de colisão.
-
-
Implemente o método
OnUpdatepara atualizar o raio laser a cada quadro- O raio laser deve ser destruído depois de um tempo desde sua emissão ou quando houver colisão com um asteroide. Para contar quanto tempo percorreu desde a emissão do raio laser, subtraia
deltaTimedo cronômetro criado para essa contagem (mDeathTimer). Verifique se esse cronômetro é menor ou igual a zero. Se for, destrua o laser alterando seu estadoSetStateparaActorState::Destroy. Caso contrário, percorra a lista de asteroidsGetGame()->GetAsteroidsverificando se o laser colide com algum deles. Se houver colisão, destrua o laser e o asteroide alterando seus estados paraActorState::Destroy.
- O raio laser deve ser destruído depois de um tempo desde sua emissão ou quando houver colisão com um asteroide. Para contar quanto tempo percorreu desde a emissão do raio laser, subtraia
-
Parte 4: Customização
Na quarta, e última etapa, você irá ajustar as variáveis do jogo para criar uma versão única do Asteroids.
-
Escolha um novo tamanho de universo (janela);
-
Defina um novo esquema de cores;
-
Altere o tamanho da nave e dos asteroides;
-
Ajuste os parâmetros de movimentação (velocidade, massa, coeficientes de resistência etc.) da nave e dos asteroides.
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 P3'
git push
Barema
- Parte 1: Movimentação de Objetos Rígidos (30%)
- Parte 2: Desenhos Vetoriais (30%)
- Parte 3: Objetos do Asteroids (30%)
- Parte 4: Customização (10%)