Você está na página 1de 12

UNIVERSIDADE FEDERAL DO RIO GRANDE DO SUL 

INSTITUTO DE INFORMÁTICA 

INF01047 - FUNDAMENTOS DE COMPUTAÇÃO GRÁFICA 

 
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  ​+ t​v,​  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/ 

Você também pode gostar