Escolar Documentos
Profissional Documentos
Cultura Documentos
INSTITUTO DE INFORMÁTICA
RELATÓRIO - TRABALHO PRÁTICO
MINIONS HAVE SPAWNED
Mario Guaraci Figueiró Zemor - 0
0242319
Rafael Baldasso Audibert - 00287695
JUNHO 2019
1. Preenchimento de requisitos
Abaixo, teremos uma breve descrição de como cada um dos requisitos
para o trabalho final foram desenvolvidos no âmbito da aplicação final,
explicando detalhes de sua implementação.
Quando formos definir uma função em específica do código, ela estará
definida, em sua forma mais geral, como
{caminho_relativo/arquivo.extensao:classe#metodo}, já que a maioria das
funções da aplicação são na verdade métodos de uma classe.
a. Objetos virtuais representados por uma malha poligonal complexa:
Praticamente todos os objetos da aplicação, que são muitos,
- a maioria deles mais complexos do que cow.obj - são carregados
a partir de arquivos .obj, carregando seus vértices (e arestas), junto
com suas respectivas normais e coordenadas de textura. Para
alguns arquivos .obj mais simples, onde não possuímos informação
de normal, foi utilizado o algoritmo de Gouraud, calculando as
normais para cada vértice fazendo uma média entre elas.
b. Transformação geométrica de objetos:
A aplicação possui 3 interações (sendo uma delas múltipla)
com objetos, ocasionando transformações geométricas no mesmo.
Juntamente com a câmera, enquanto em modo de câmera
look-around, temos um objeto {./src/objects/hand.obj} que
acompanha a visualização, rotacionando no eixo y juntamente
com a câmera (porem deslocado para a frente e para baixo,
portanto precisamos calcular rotações e translações), mesmo se o
jogador pular ou se agachar.
Ao tentarmos colocar uma torre em jogo, podemos
controlar, “apontando” com a câmera onde queremos que a torre
seja colocada, controlando portanto o posicionamento da torre
com o mouse. Ao caminharmos a torre sempre fica voltada para o
usuário, portanto controlamos a rotação da torre com o nosso
teclado.
Por fim, todas as barras de vidas presente no jogo ficam
sempre viradas para a câmera, não importando onde estejamos.
c. Controle de câmeras virtuais:
O jogo possui 1 câmera virtual implementada em
{include/engine/camera.hpp:Camera}, sendo que ela pode ser
alterada entre o modo look at e o modo look around (default),
como implementado em
{src/engine/camera.cpp:Camera#switch_camera_type}.
No modo look around podemos caminhar livremente pelo
mapa, fazendo rotações nos eixos x e y (este, limitado em [-89.9º,
89.9º]. Ao tentarmos alterar para o modo look at, a camera passa a
ficar focada no inimigo que está mais próximo de atingir o nexus
aliado, caso ele exista. Caso esse inimigo morra, ou atinja o nexus
aliado, a câmera volta para o modo l ook around.
A diferença de implementação entre os 2 tipos de câmeras
se dá principalmente pelo jeito com o qual calculamos o vetor
front da camera (utilizado para obter a matriz view). Enquanto em
modo look around temos o pitch e o yaw desse vetor atualizado
com a movimentação do mouse nos eixos x e y da tela, no modo
look at temos o vetor front como o vetor que parte da posição
atual da câmera em direção do objeto que queremos focar.
d. Objetos virtuais com mais de uma instância:
Todos os objetos (assim como as texturas e shaders) são
administrados por uma classe estática localizada em
{include/engine/resource_manager.hpp:ResourceManager} que
armazena todos os dados que precisam ser armazenados de
arquivos já lidos do disco.
Para os objetos em específico, após lermos um objeto pela
primeira vez, armazenamos o VAO (e outras informações, sobre
como devemos renderizar esse objeto) com as informações de
vértices, cores, normais, texturas, etc. em uma lista. Dessa forma,
toda vez que quisermos criar um novo objeto daquele mesmo tipo,
não precisaremos acessar novamente o disco, e podemos somente
utilizar o mesmo VAO já criado para aquele objeto anteriormente.
Dessa forma, todos as instâncias de um mesmo tipo de objeto, na
verdade são o mesmo conjunto de vértices que já estão
armazenados na GPU, acelerando o carregamento dos objetos e a
instanciação de novos.
e. Testes de Intersecção:
Temos 3 tipos básicos de testes de intersecção, definidos
abaixo:
1. Teste Cubo-Cubo:
Para os testes de colisão entre objetos (câmera colide
com objetos colisíveis, e os objetos colidem com a câmera -
mas não entre si), foi utilizado um teste de intersecção
utilizado AABB bounding boxes. Todos os objetos que
possuem esse tipo de colisão, herdam de
{include/model/collisive.hpp:Collisive}, e implementam um
teste de colisão básico entre cubos, levando em
consideração transformações de translação e escalamento,
mas não rotação.
Isso acontece, pois não foi implementado oriented
bounding boxes, porém isso não interfere no funcionamento
das colisões, já que os objetos, são em sua maioria ou em
formatos cubóides ou em formatos de torre, fazendo com
que a rotação seja imperceptível, graças a um truque na
bounding box da câmera, que é consideravelmente maior
do que as dimensões reais de uma pessoa no no lugar da
câmera, por exemplo.
2. Teste Raio-Plano:
Para a função de adicionar uma torre ao jogo, é
necessário utilizar algum algoritmo para identificar em que
posição do plano base estamos mirando, para adicionar a
torre.
Para isso, dado um raio v definido por um ponto p
(posição da câmera) e um vetor f (vetor front da câmera) de
tal maneira que v = p + tv, para todo t no intervalo [0, +inf), e
um plano definido através de um ponto o que pertença ao
plano, e um vetor n ortonormal ao plano, podemos resolver
uma equação linear para encontrar para qual valor de t,
temos que o raio e o plano se intersectam.
Na aplicação, em específico, queremos que esse t
esteja limitado a um certo valor k, definido em
{include/engine/constants.hpp/Constants#SPAWNING_TUR
RET_MAX_DISTANCE. Caso o valor calculado de t seja menor,
que essa constante k, conseguimos posicionar a torre no
ponto obtido após substituir esse t nas equações acima.
Caso esse valor seja maior, cortamos o vetor v nessa máxima
distância, e projetamos o vetor no plano, para obter o ponto
em que iremos inserir a torre.
3. Teste Ponto-Esfera:
Esse, que é o teste de intersecção mais simples, é
utilizado para sabermos se uma torre pode ou não atirar em
um dado inimigo. Para cada torre procurando um alvo,
percorremos pela lista de inimigos, vendo se esse inimigo
está dentro de uma esfera de raio X, definido, por exemplo,
em
{include/model/turret/siege_chaos.hpp/SiegeChaos#AGGRO
_RADIUS}. Para isso, calculamos o tamanho do vetor que vai
da posição da torre até a posição do minion, e vemos se ele é
menor que esse dado raio X. Adicionalmente, o inimigo mais
próximo da torre, ou seja, aquele que gerou o menor raio
menor que X, é escolhido como alvo.
f. Iluminação dos modelos geométricos:
Por padrão, todos os modelos utilizam iluminação difusa
com modelo de Phong, descrita nos shaders
{src/shaders/default_lighting.fs} e { src/shaders/default_lighting.vs}.
Para algumas aplicações específicas, como as bolas de
canhão {include/model/ammo/cannon_ball.hpp/CannonBall}
lançadas por algumas torres, cujo shaders estão definidos em
{src/shaders/cannon_ball.fs} e {src/shaders/canon_ball.vs} por
exemplo, dada a sua baixa resolução e a não necessidade de
realismo, foi utilizado modelo de iluminação de Gouraud, onde o
modelo de iluminação é calculado em cada vértice da esfera, e não
em cada ponto. Nas bolas de canhão também é utilizado modelo
de iluminação de Blinn-Phong, também usado para renderizar os
nexus {include/model/nexus.hpp:Nexus}
g. Mapeamento de texturas:
Assim como no caso da criação de malhas complexas de
triangulos, praticamente todo o mapeamento de textura foi feito a
partir da leitura dos arquivos .obj mapeando para uma imagem
.jpg, obtida através da extração de arquivos de um jogo popular.
Há exceções, porém, como no caso da bola de canhão, onde
utilizamos coordenadas de texturas esféricas para o mapeamento
de texturas, conforme realizado no laboratório 5.
h. Curvas de Bézier:
Foi utilizada uma curva de bézier de grau 2 para simular o
movimento parabólico da bola de canhão. O primeiro ponto é
colocado na “boca” do canhão e o segundo ponto é colocado no
ponto alvo, ou seja, em cima do objeto.
Para calcularmos o ponto de controle - que fica no meio dos
dois pontos acima - foi utilizada a estratégia de pegar um ponto na
mesma altura do que a boca do canhão para o eixo y - que está
acima do ponto final - e interpolar os valores para os eixos x e z,
pegando os valores exatamente no meio. Isso foi adotado para que
a curva ficasse parecendo real, não importando a distância entre o
canhão e o inimigo, já que caso colocássemos os eixos x e z
exatamente acima do inimigo, fariamos com que, caso a torre
atirasse de muito longe, a bola percorresse o espaço horizontal
muito mais rápido do que o espaço vertical, desacelerando
abruptamente no final, o que não se parecia muito com a
realidade.
i. Animação baseada com o tempo:
Toda e qualquer atualização de estado ou movimento
acontece a partir de uma função update, que recebe como
parâmetro um float delta_time indicando quanto tempo passou
(em média) desde que essa função foi chamada pela última vez,
multiplicando toda e qualquer ação de movimento por essa
variável, dessa forma o jogo irá rodar em todos os computadores,
não importando a taxa de quadros por segundo, na mesma
velocidade.
Por conta desse fato, porém, as colisões podem não
funcionar corretamente em computadores muito lentos, já que a
movimentação ocorrerá em passos muito grandes, fazendo com
que a câmera consiga atravessar objetos muito finos, já que estará
fazendo “saltos” de posição muito grandes.
2. Funcionamento da aplicação
O jogo foi baseado em um modelo de jogo de estratégia popularmente
conhecido como tower defender[1], onde o objetivo é, através da instalação de
torres no mapa, conseguir impedir que as tropas inimigas atinjam um
objetivo seu, o qual você tenta defender.
Nosso jogo, intitulado de Minions Have
Spawned, foi baseado no jogo de estilo MOBA
League of Legends[2], de onde a maioria dos objetos
e texturas foram retirados, através da extração de
arquivos do jogo, utilizando ferramenta de código
aberto disponibilizado sob licença LGPL 3.0,
disponível em
https://github.com/CommunityDragon/CDTB.
O objetivo do jogo é, como mencionado
anteriormente, impedir que as tropas inimigas cheguem
até o seu nexus, a estrutura a qual você está tentando
defender.
Os inimigos vem em ondas, onde você tem um
tempo para pensar entre cada uma delas, para melhor
poder posicionar as suas torres, a fim de impedir que eles cheguem até o seu
nexus. Para posicionar as torres, você seleciona elas clicando de 1 até 7, e clica
com o botão esquerdo do mouse para posicioná-la. Se clicar com o botão
esquerdo ou no 0, a torre será desselecionada.
Existem diferentes tipos de torres, com diferentes efeitos especiais e
características, representadas na imagem abaixo, que são definidas por:
I. Bilgerwater Chaos: Atira uma bola de canhão, causando dano
moderado, e atingindo inimigos em área ao redor do inimigo principal.
II. Bilgerwater Order: Atira uma bola de canhão incendiada, causando
dano moderado, porém fazendo com que o inimigo perca um pouco de
vida adicional ao longo de um curto período de tempo.
III. Howling Chaos: Atira um raio de gelo, causando dano moderado,
fazendo com que o inimigo caminhe um pouco mais devagar por um
período de tempo. Caso exista mais de uma torre desse tipo, a
diminuição de velocidade se acumula multiplicativamente.
IV. Howling Order: Atira um raio de gelo congelante, causando dano
moderado, e fazendo com que o inimigo permaneça parado por um
curto intervalo de tempo.
V. Siege Chaos: Torre com o maior dano de ataque, porém com um tempo
de recarga entre os tiros elevado, atinge um único inimigo por vez com
um raio laser.
VI. Summoners Chaos: Torre padrão, com dano de ataque moderado,
porém velocidade de ataque elevada.
VII. Summoners Order: Torre padrão, com dano de ataque alto, porém
velocidade de ataque moderada.
Também existem 4 tipos diferentes de inimigos, que não possuem
poderes especiais, apenas possuem quantidade de vida diferente, sendo eles
definidos e representados abaixo, em ordem crescente de vida por: Melee,
Ranged, Siege e Super.
Você ganha se sobreviver por todas as ondas de inimigos. Você perde
caso os inimigos destruírem o seu nexus antes de todas as ondas chegarem.
Nas imagens abaixo podemos ver uma instância do jogo acontecendo,
com o nexus inimigo (em vermelho), de onde os inimigos surgem, e o nexus
aliado (em azul), ao qual temos que defender, com inimigos percorrendo o
caminho e torres os atingindo.
3. Utilização do jogo
Ao entrar no jogo, o jogo inicia automaticamente, sem nenhuma
onda atualmente por vir. Os comandos estão definidos abaixo
W - Caminhar para a frente
A - Caminhar para a direita
S - Caminhar para trás
D - Caminhar para a direita
Espaço - Pular
Shift - Agachar
Tab - Alternar o modo de câmera
1 até 7 - Selecionar torre para adicionar ao jogo
Botão Esquerdo - Colocar torre atualmente selecionada
0 ou Botão Direito - Deselecionar torre atualmente em mão
Enter - Liberar próxima onda de tropas (se disponível)
ESC - Sair do jogo
4. Compilação e Execução
Para compilar o programa, basta abrir o arquivo
{MinionsHaveSpawned.cbp} no programa Code::Blocks, e clicar na tecla
F9 ou selecionar o botão Build and Run. Caso apareça algum aviso
relacionado à static library basta apenas clicar em OK. Por padrão, você
irá compilar a aplicação no modo Debug. Para compilar com
otimizações de velocidade e outras flags adicionais para o compilador,
basta alterar o Build Target de Debug para Release. A compilação será
mais demorada, porém o programa carregará cerca de 20% mais rápido
os objetos, e não possuirá a interface de console com prints de debug
disponíveis para visualização, somente a GUI. A compilação não foi
testada no Linux, apenas no Windows.
Já existe um arquivo executável, compilado em modo Release
disponível em {bin/Release/MinionsHaveSpawned.exe} em modo
Release para Windows.
5. Referências
[1] https://en.wikipedia.org/wiki/Tower_defense
[2] https://br.leagueoflegends.com/pt/