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
Actor
para representar a nave. -
Asteroid
Classe que estende
Actor
para representar um asteroide. -
Laser
Classe que estende
Actor
para 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
ApplyForce
para adicionar uma força à acceleração- Adicione ao atributo
mAcceleration
a forçaforce
, passada como parâmetro, dividida pela massamMass
do objeto.
- Adicione ao atributo
-
Implemente o método
Update
para calcular a nova posição do objeto utilizando o Método de Euler Semi-implícito-
Atualize a velocidade
mVelocity
e a posiçãoposition
do objeto utilizando o Método de Euler Semi-implícito. -
Utilize a função
Math::NearZero
para verificar se o comprimento do vetor velocidademVelocity
está próximo dezero
. Se estiver, use a funçãomVelocity.Set
para forçar velocidadezero
. Isso evita movimentos muito pequenos. -
Utilize a função
mAcceleration.Set
para reinicializar a aceleração emzero
. -
Some à rotação atual do objeto
rot
a velocidade angularmAngularSpeed
multiplicada pelodeltaTime
.
-
-
Implemente o método
ScreenWrap
para 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
Intersect
para 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.LengthSq
para 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
distSq
for 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
DrawPolygon
para 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érticesi
ei+1
. -
Utilize a função [SDL_RenderDrawLine] para desenhar uma linha entre o último e o primeiro vértices.
-
-
Implemente o método
DrawCircle
para 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
x
do novo vértice multiplicando o raio da circunferênciaradius
pelo cosseno do ângulo correnteangle
; -
Calcule a coordenada
y
da 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*PI
dividido pelo número de vérticesnumVertices
.
-
-
-
Implemente o método
Draw
para desenhar um objeto-
Utilize a função
Matrix3::CreateRotation
para criar uma matriz de rotação com o ângulo do dono desse componentemOwner->GetRotation
. -
Percorra os vértices desse componente
mVertices
multiplicando-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
DrawPolygon
para desenhar o conjunto de vértices transformados. -
Utilize a função
DrawCircle
para 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
Random
para gerar números aleatórios- Uma biblioteca
Random.h
foi incluída nesse projeto para a geração de números aleatórios. Utilize a funçãoRandom::Init
para inicializar o gerador de números aleatórios.
- Uma biblioteca
-
Instancie a nave e os asteroides
-
Instancie um objeto da classe
Ship
com20
pixels de altura e armazene seu ponteiro emmShip
. Em seguida, posicione a nave (mShip->SetPosition
) no meio da tela. Lembre-se que as variáveismWindowWidth
emWindowHeight
armazenam as dimensões da tela. -
Escreva um laço para instanciar
10
objetos da classeAsteroid
, cada um com80
pixels de raio.
-
-
Implemente os métodos
AddAsteroid
eRemoveAsteroid
para gerenciar a criação e remoção de asteroides-
No método
AddAsteroid
, adicione (emplace_back
) o asteroideast
ao vetor de asteroidesmAsteroids
. -
No método
RemoveAsteroid
, utilize a funçãostd::find
para procurar pelo asteroideast
no vetor de asteroidesmAsteroids
. Se o encontrar, remova-o do vetor de asteroides (mAsteroids.erase
).
-
-
-
Ship.cpp
-
Implemente o construtor de
Ship
criando 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 atributomHeight
como 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
,RigidBodyComponent
eCircleColliderComponent
. Armazene seus ponteiros emmDrawComponent
,mRigidBodyComponent
emCircleColliderComponent
respectivamente. 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
W
e, se estiver, aplique uma força para frente com magnitude dada pelo atributomForwardSpeed
. Utilize o métodoGetForward
para obter o vetor da frente e a funçãoApplyForce
do componentemRigidBodyComponent
para aplicar a força. -
Inicialize uma variável local chamada
angularSpeed
com0.0
e 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 angularangularSpeed
a velocidade de rotaçãomRotationForce
. -
Verifique se o jogador está pressionando a tecla
espaço
e 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.0
pixels 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
SetRotation
do laser eGetRotation
da 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
OnUpdate
para atualizar a nave a cada quadro-
Subtraia
deltaTime
do 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 velocidadevelocity
ec_r
é o coeficiente de resistênciamFrictionCoefficient
. Armazene a força calculada em um vetor chamadodragForce
. -
Aplique a força
drag
na nave com a funçãoApplyForce
domRigidBodyComponent
-
Percorra a lista de asteroides do jogo e verifique para cada asteroide se ele está colidindo com a nave. O método
GetGame()->GetAsteroids
retorna a lista de asteroides. Além disso, você já implementou o métodoIntersect
doCircleColliderComponent
. 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
Asteroid
gerando um círculo com ruídos para representar o asteroide visualmente e instanciando seus componentes-
Utilize a função
Random::GetVector
para 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çãoSetPosition
para alterar a posição inicial do asteroide com a posição gerada. -
Instancie os componentes
DrawComponent
,RigidBodyComponent
eCircleColliderComponent
. Armazene seus ponteiros emmDrawComponent
,mRigidBodyComponent
emCircleColliderComponent
respectivamente. 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 geradosaverageLength
como raio de colisão. -
Aplique a força aleatória gerada anteriormente
randStartingForce
para mover o asteroide. Utilize a funçãoApplyForce
do 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.5
e1.0
e multiplique-o pelo raio da circunferência (radius
). Armazene o resultado em uma variávelrandLength
; -
Calcule a coordenada
x
do novo vértice multiplicandorandLength
pelo cosseno do ângulo correnteangle
; -
Calcule a coordenada
y
da 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*PI
dividido pelo número de vérticesnumVertices
.
-
-
-
-
Laser.cpp
-
Implemente o construtor
Laser
gerando um segmento de reta para representar o asteroide visualmente e instanciando seus componentes-
Crie
2
vértices (Vector2
) considerando o centro da nave como origem e o atributomLength
como o comprimento do raio laser. Por exemplo:v1 = (-l/2, 0)
ev2 = (l/2, 0)
. -
Adicione esses
3
vértices em um contêinerstd::vector
. -
Instancie os componentes
DrawComponent
,RigidBodyComponent
eCircleColliderComponent
. Armazene seus ponteiros emmDrawComponent
,mRigidBodyComponent
emCircleColliderComponent
respectivamente. 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 lasesmLenght
como raio de colisão.
-
-
Implemente o método
OnUpdate
para 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
deltaTime
do cronômetro criado para essa contagem (mDeathTimer
). Verifique se esse cronômetro é menor ou igual a zero. Se for, destrua o laser alterando seu estadoSetState
paraActorState::Destroy
. Caso contrário, percorra a lista de asteroidsGetGame()->GetAsteroids
verificando 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%)