Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
CDD 0001.642
005.133
323.43
07/13
Sumrio
Captulo 1 - Introduo1
Sobre as plataformas 1
Sobre o cdigo-fonte 2
Ferramentas grficas 17
Imagens 17
Transparncia e canal alpha 18
Unidades de medida 20
Arquivos de imagem 21
Ferramenta para Sketch 22
Ferramentas para desenho Raster 23
Ferramentas para desenho vetorial 24
Comparao entre renderizao raster e vetorial 25
Game engines 28
HTML 5 + Javascript 28
Nativos 28
Usar a plataforma nativa 30
Ambientes multiplataforma 30
Prototipadores 31
Bibliotecas auxiliares 31
Usando o Codea 34
Um tutorial rpido em Codea 35
Criando o prottipo do AsteroidNuts no Codea 39
Sumrio VII
Usando o Processing 49
Um tutorial rpido no Processing 51
Criando o prottipo do AsteroidNuts com o Processing 54
Crie vrios prottipos 64
Os primrdios 66
Conceitos bsicos 66
Acelerao e movimento 68
Coliso 70
Deteo de coliso 72
Engines de fsica 76
Bullet Physics Library 76
Chipmunk physics engine 77
Box2D 77
Fsica com Box2D 78
Preparando o laboratrio 79
O laboratrio Java 80
Normalizao e ajuste para renderizao 85
Fundamentos do Box2D 87
Fora e atrito 93
Corpos circulares 97
Rotao dos corpos 98
Desenhando corpos como polgonos 100
Desenhando corpos como imagens 102
Torque e impulso 107
Deteo de coliso 110
Juntas ou junes 113
Usando o Box2D em plataformas mveis 123
Box2D no Android 123
Box2D no iOS 132
VIII
Sumrio IX
Limitaes 299
Licena de uso do cdigo-fonte 299
A concepo 300
Jogos casuais 301
Jogabilidade 301
Implementao Bsica 302
I18N e L10N 302
Alteraes no modelo do game 303
Alteraes na carga do nvel corrente 307
Alteraes no Game Loop 309
Colises 311
Registro de tempos 315
Gesto de memria no iOS 323
Uso do GLKBaseEffect 324
Procure Memory Leaks 327
Verifique se est realmente liberando a memria 328
Concluso 332
Possveis melhorias no game 333
Captulo 1
Introduo
Quando eu comecei a desenvolver games, li muitos livros sobre o assunto e
fiz muitas pesquisas no Google. Muitos dos problemas que eu passei no incio
tive que batalhar muito para resolver sozinho. Dessa forma, reuni um conjunto
de tcnicas ao longo do meu aprendizado, que so teis at hoje. Durante esse
aprendizado, notei que muito difcil encontrar guias prticos de uso, com
exemplos simples, o que nos fora a ler muito e escrever muitas provas de
conceito, perdendo tempo neste processo.
Ento, resolvi colocar no papel tudo o que eu havia aprendido, e que poderia ser til para outras pessoas. Assim, nasceu a ideia deste livro, que um
guia prtico com solues para os problemas comuns no desenvolvimento de
games mveis.
Eu queira algo mais que um recipe book (livro de receitas), porm menos que uma referncia completa. Um livro que voc consegue ler rapidamente e consultar sempre que precisar, com inmeros exemplos em cdigo-fonte,
alm de um game completo.
Neste livro, eu assumo que voc, leitor, leitora, um desenvolvedor experiente e que deseja criar games para plataformas mveis, especialmente:
Android e iOS. Apesar de j haver escrito livros introdutrios sobre o assunto,
este um livro para profissionais que desejem entrar no lucrativo negcio de
desenvolvimento de games, tornando-se um Indie Game Developer (desenvolvedor independente de games).
Ao longo do livro, voc construir um framework simples, porm abrangente, e que pode ser utilizado para criar games mveis biplataforma (Android
e iOS) rapidamente. O game exemplo do livro Bola no Quintal, foi feito em
apenas 1 semana para as duas plataformas!
A melhor maneira de aproveitar todo o contedo ler com a mo na massa, rodando todos os exemplos de cdigo.
Sobre as plataformas
Este livro apresenta exemplos em Android e iOS, logo, voc dever ter
instalados os dois kits de desenvolvimento. Nada impede que voc opte por
uma das duas plataformas, pois o livro no obriga voc a conhecer ambas. A
plataforma Android utilizada a ltima verso disponvel (4.2, API = 17), porm, os exemplos podem ser executados em qualquer dispositivo com verso
Gingerbread (maior ou igual a 2.3). A plataforma iOS utilizada tambm a
ltima verso disponvel (6.1), com Xcode 4.5.
Se optar por utilizar as duas plataformas, ser necessrio utilizar um computador Mac / OS X, com verso mnima Mountain Lion, podendo desenvolver para Android e iOS.
Sobre o cdigo-fonte
Todo o cdigo-fonte do livro est disponvel para download, em formato
zip. Voc poder encontr-lo nos seguintes locais:
Site da editora: http://www.lcm.com.br, procure pelo ttulo do livro;
Site especfico deste livro: http://www.indiegamerbrasil.com;
Site The Code Bakers: http://www.thecodebakers.com.
Se tiver alguma dvida ou dificuldade para instalar os projetos ou sobre o
livro, por favor me contate:
cleuton.sampaio@gmail.com
Captulo 2
Reviso de conceitos
Games... Que coisa apaixonante! So capazes de despertar emoes, tanto
negativas como positivas e so obras de arte vivas, que deixamos para a
posteridade. Se voc nunca criou um game, no sabe quo boa a sensao de
saber que as pessoas passam horas se divertindo com a sua criao.
Se algum me diz que criou um compilador, ou um sistema operacional,
certamente vai me impressionar. Agora, se algum me diz que criou um Game,
certamente vai chamar minha ateno imediatamente. Para mim, o ultimate
fight de todo programador criar um Game. Isto o que diferencia os bons
dos comuns.
Para mim, desenvolver um Game a maior prova de competncia para
um programador. realmente um desafio muito grande, pois existem muitos
aspectos a serem abordados. Um game um projeto de software complexo,
com vrios recursos, de reas diferentes envolvidas.
Ilustrao 2: Projeo 3D
Se for um Game 3D, isto mais crtico ainda. A traduo do modelo espacial para o modelo 2D da tela pode ser feito de vrias maneiras:
Projeo Ortogrfica (ou ortogonal): a iluso de 3D criada ao
criarmos projees ortogonais ao plano, ou seja, no h diferena de
tamanho entre objetos prximos e objetos distantes. Desta forma,
mostramos os elementos em um ngulo diferente do que veramos
apenas de cima, ou de lado. Evita os clculos complexos de projeo
3D e mantm uma boa iluso para o jogador.
Viso Isomtrica: no uma projeo, mas uma forma de mostrarmos os objetos. Neste tipo de viso, rotacionamos um pouco os
eixos para mostrar mais detalhes dos objetos, criando a iluso 3D.
Neste caso, o ngulo entre os eixos (x, y, z) no plano igual, ou seja:
120 graus. Normalmente, a viso isomtrica utilizada com projeo ortogrfica. Era muito comum nos anos 80 e 90, embora alguns
jogos ainda a utilizem. Alguns exemplos famosos que usam viso
isomtrica: Q*Bert (Gottlieb), Civilization II (MicroProse), Diablo
(Blizzard);
Projeo Perspectiva: vem do desenho tradicional, no qual as
partes mais prximas parecem maiores do que as mais distantes.
Tambm conhecida como perspectiva cnica. Do maior iluso de
realidade aos games, porm exigem maior complexidade de programao e poder de processamento. Os jogos mais modernos, como:
Battlefield, Assassins Creed, Halo e outros apresentam viso em
perspectiva;
vidas, viso Isomtrica etc). Exemplos de arcades clssicos: Space invaders e Pac-Man. Os jogos arcade evoluram e hoje esto
disponveis em vrias plataformas, incluindo mobile;
Jogos de Ao: desafiam o jogador fisicamente, seja por coordenao motora, viso de movimento ou tempo de reao. Possuem
vrias subcategorias, como: plataforma, tiro e luta. Exemplos clssicos: River raid (Atari), Sonic (SEGA), Super Mario (Nintendo) e outros mais modernos, como: Angry Birds (Rovio), Jetpack joyride (Halfbrick). Na verdade, arcades so jogos de ao, s
que com regras e visual mais simples;
Aventura (adventure): jogos que desafiam a inteligncia do jogador, colocando-o em determinado mundo, com uma misso a cumprir. Nos primrdios da computao pessoal, os jogos de aventura
eram simples, muitas vezes textuais, ou com poucos grficos. Eu
gosto de citar os exemplos do Renato Degiovani, como: Serra Pelada e Angra-I. Hoje em dia, muitos jogos de aventura tambm
misturam caractersticas de jogos de ao, como os Shooters;
Role Playing Game (RPG): so derivados dos famosos jogos de
RPG textuais, como Dungeons & Dragons. Na verdade, so jogos
de aventura, nos quais o jogador assume um papel e uma vida paralela em um universo virtual. A principal diferena para os jogos de
aventura tradicionais a durao do jogo, que pode ser indeterminada. Hoje em dia, os jogos RPG ganharam verses multiusurio
online, como os MMORPG Massively Multiuser On-line RPG
(RPGs on-line massivamente multiusurios). Exemplos: World of
Warcraft (Blizzard) e Ragnark (Gravity Corp);
Tile based games: podem ser jogos de ao ou RPG, nos quais os
personagens se movem em um tabuleiro, de casa em casa. Existem vrios padres e ferramentas para criar Tile Based Games,
como o padro TMX (https://github.com/bjorn/tiled/wiki/TMX-Map-Format) e TiledEditor (http://www.mapeditor.org/). Alguns
RPGs e jogos de aventura so tambm Tile Based;
Menu games: so jogos de simulao e RPG, com interface baseada
em escolha. Parecem muito com os jogos de cartas, como: Magic.
Voc tem uma srie de atributos e vai desafiando os outros jogadores, acumulando vitrias ou amargando derrotas. Um bom exemplo
o Mafia Wars (Zynga);
Jogos de ao
O objetivo principal deste trabalho so jogos de ao. Em primeiro lugar,
porque todos os elementos clssicos (Player Object, NPC, Game loop) so encontrados neles e, em segundo lugar, porque so uma porta de entrada para
o desenvolvimento de games.
Os jogos de ao podem ser divididos tambm em subcategorias, como:
Plataforma: so jogos 2D com iluso 3D, nos quais o jogador se
move entre plataformas, de diversas altitudes, pulando, correndo e
saltando. Ele pode ter que enfrentar desafios fsicos, como abismos,
ou mesmo adversrios. Exemplos so: Sonic (SEGA), Super Mario (Nintendo) e, mais modernamente, Super Meat Boy (Team
Meat);
Tiro: seu objetivo matar inimigos atirando diretamente neles ou
derrubando-os. Podem ser simples como o Angry Birds (Rovio),
ou complexos, como o Doom (Id Software / Nerve Software). Entre os jogos de tiro, existem os First Person Shooters, nos quais a
viso a do jogador (em primeira pessoa), ou Third Person Shooters, nos quais a viso externa ao jogador;
Luta: so jogos nos quais o jogador desafiado a derrotar lutadores, que podem ser totalmente virtuais, ou avatares controlados por
outras pessoas. Alguns exemplos so: Street Fighter (Capcom) e
Mortal Kombat (Midway games);
Jogos de ao so baseados em tempo, ou seja, a cada fatia de tempo a
posio do jogador (e das balas) muda, sendo atualizada constantemente.
Quando o processamento de uma fatia de tempo demora demais, acontece o
efeito de Lagging, que quando o jogador percebe a lentido do jogo em
determinados momentos. O Lag pode ter diversas causas, como a latncia
da rede, em caso de jogos multiusurio, ou mesmo a lentido da renderizao
de frames. O Lag pode causar grande insatisfao no usurio, compromentendo o sucesso do Game.
Devido a esta caracterstica de sensibilidade ao tempo, jogos de ao exigem muito do programador. necessrio racionalizar os grficos, utilizando
ao mximo o poder de processamento de GPU (Graphics Processing Unit), e
tambm empregar recursos de multiprogramao, de modo a aproveitar o paralelismo, afinal, muitos dispositivos mveis so multicore (dotados de mais
de uma CPU ou ncleo).
Inventrio
Responsvel pelos objetos e propriedades do Game. Os elementos ativos
do jogo (personagens ou Player Objects e NPCs) e suas propriedades (quantidade de vidas etc).
Pontuao
Responsvel por acompanhar a pontuao do usurio, suas conquistas e o
compartilhamento destas informaes.
Estratgia
Quando o jogo tem caracteres prprios, no manipulados pelo usurio
(Non-Player Character ou NPC), preciso dar alguma inteligncia a eles,
caso contrrio, o jogo ser fcil demais. Alguns tipos de jogos dispensam a
estratgia, baseando-se apenas em clculos aleatrios, como: labirintos, por
exemplo. Jogos do tipo Shooting (2D, 3D, TPS, FPS), quando no esto em
modo multiusurio, necessitam de estratgias avanadas, para dar a sensao
de realidade ao usurio.
Configurao
Responsvel pela persistncia e recuperao de todas as informaes de
configurao do jogo, como: nveis oferecidos, nvel do jogador, conquistas,
opes etc.
Game loop
o conjunto de comandos repetidos a cada fatia de tempo, responsveis
por movimentar os objetos, atualizando o modelo de dados. Ele tambm pode,
ao final do processamento, invalidar a apresentao, ordenando o redesenho
da tela. Em jogos de ao, geralmente existe um Game loop baseado em temporizador, que atualiza a tela em determinadas fatias de tempo (time frame).
Alguns tipos de jogos dispensam este recurso.
Animao
Os caracteres do jogo e at mesmo o cenrio podem exibir animaes. O
personagem pode sorrir, chorar, gritar e o cenrio pode mudar. At mesmo a
movimentao dos personagens tratada por esta funo. A animao tambm
envolve a tcnica utilizada para desenhar os objetos na tela a cada fatia de
tempo passada.
Fsica
A macrofuno de fsica muito importante em jogos de ao. Ela responsvel por dar realismo ao movimento dos caracteres. Coisas como: impulso, gravidade, movimento e coliso so tratadas por esta funo.
10
Interao
Controla a maneira pela qual o usurio interfere no jogo. responsvel por
capturar o input do usurio e converter em aes no jogo.
Comunicao
responsvel por comunicar ao usurio o status do jogo, e tambm o
resultado de suas aes. Pode manter energy bars, contadores, emitir sons,
acionar exploses etc.
Algumas destas macrofunes podem ser implementadas em parte por bibliotecas de terceiros, como: Box2D e OpenGL ES. Porm, mesmo assim,
necessrio um esforo do desenvolvedor para configurar e interagir com elas.
Uma funo muito importante a de Inventrio, que tambm abrange o
modelo de dados do Game.
12
Jogabilidade
Jogabilidade, ou Gameplay, uma caracterstica ligada usabilidade do
jogo, ou seja, o quo o jogo nos diverte. Eu diria que o somatrio das experincias do usurio durante o jogo, que envolve: facilidade de controle, boa
visualizao de informaes, nvel crescente e ajustado de desafios etc.
Outros aspectos que influenciam a jogabilidade so:
Satisfao proporcionada ao jogador;
Facilidade de aprendizado do jogador;
Eficincia de envolvimento. O quo eficientemente o jogo envolve o
jogador. Jogos eficientes envolvem o jogador rapidamente;
Motivao para evoluo, ou se o jogo motiva o usurio a cumprir
atividades;
Cativao, se o jogo faz com que o usurio volte a jogar novamente
ou se interesse em adquirir novas fases, armas etc;
Um dos fatores importantes na jogabilidade pode ser a socializao, ou a
facilidade que o game d para divulgar suas conquistas.
Muita gente confunde jogabilidade com facilidade de jogar, que um
dos aspectos do conceito. Embora seja muito importante, a usabilidade (facilidade de jogar) sozinha no garante o sucesso de um game.
Talvez seja melhor citar alguns exemplos de games com boa jogabilidade:
Angry Birds: simples, fcil e rpido de aprender;
World of Goo: fantstico! Embora seja um jogo de desafios, sua
jogabilidade tima;
Fruit Ninja: igualmente simples e fcil, com alta eficincia de
envolvimento.
14
silncio e uma tela de LED enorme). A jogabilidade para jogos mveis deve
ser estudada levando-se em conta estas caractersticas.
Um erro muito comum dos desenvolvedores de games mveis criar as
mesmas verses para smartphones e tablets. No so a mesma coisa, embora,
com o Samsung Galaxy S III e o iPhone 5, os smartphones estejam crescendo, ainda so bem diferentes de um tablet de 8 ou 10 polegadas. necessrio
adaptar a jogabilidade ao tamanho do dispositivo.
A originalidade tambm tem um papel importante no sucesso de um Game,
especialmente se for do tipo casual. Games muito parecidos com outros, de
maior sucesso, no so bem vistos e a tendncia do pblico considerar como
uma imitao, mesmo que seja apenas uma coincidncia. Quanto mais original seu game for, mais poder chamar a ateno deste pblico.
Neste livro, no vou falar sobre este assunto, mas a forma de monetizao tambm pode ter um impacto positivo ou negativo no Game. Em meu
livro anterior Mobile Game Jam (www.mobilegamejam.com), focamos muito
nestes aspectos, porm, no podemos deixar de mencionar alguns problemas.
Para comear, nem todos os usurios so fs de jogos Ads based e podem
se irritar com aquela faixa de tela utilizada para veicular anncios. Eu tenho
games publicados que so deste tipo, e a rentabilidade muito baixa. Outra
forma que deve ser evitada cobrar licena de uso. Se voc j cobra pelo uso
sem deixar o usurio experimentar, receita para o fracasso, a no ser que seu
game seja realmente sensacional. O ideal dar ao jogador uma maneira de
experimentar o game antes de compr-lo. A entra o processo de in-app purchase (compra por dentro da aplicao), e os bens virtuais. uma maneira
de fazer o usurio se div ertir e recuperar gradativamente o seu investimento.
Captulo 3
Kit de ferramentas para
criao de games
Quando eu era garoto, passava aquele seriado antigo do Batman (DC
Comics), no qual o Morcego tinha um cinto de utilidades, e eu achava aquele cinto sensacional. Com Games, no muito diferente, pois necessitamos
ter a mo (e saber usar) algumas ferramentas importantes, que nos pouparo
muito tempo na criao de um Game.
Dependendo do tipo de Game que voc vai fazer, algumas ferramentas se
tornam mais importantes e outras, desnecessrias, mas sempre existe um conjunto bsico que voc deve ter. Vamos mostrar algumas delas aqui.
Ferramentas grficas
Em Games, as imagens so fundamentais e ns j discutimos isso, agora,
o momento de entrarmos mais um pouco a fundo no assunto, focando nas
principais ferramentas para criao de imagens.
Imagens
Vamos comear a estudar imagens em seu formato virtual e, depois, vamos
ver como ela renderizada (apresentada) na tela.
Toda imagem na memria um conjunto de pontos. Estes pontos so armazenados na memria e, posteriormente, so exibidos para o usurio. Cada
ponto tem as propriedades: coordenadas e cor.
As coordenadas de um ponto indicam onde ele est em nosso mapa
virtual (Bitmap no confundir com o formato BMP). Dependendo do tipo
de grfico que estamos trabalhando, pode ser em um mapa tridimensional ou
bidimensional. Como estamos falando de imagens, vamos supor que seja apenas bidimensional.
J a cor, normalmente expressa em quantidade de vermelho (red), verde
(green) e azul (blue), podendo tambm trazer a quantidade de transparncia
(Alfa). Vamos ver alguns exemplos simples de representaes.
18
20
Na imagem anterior, usamos o LibreOffice Draw para desenhar. Os quadrados, que possuem cor de fundo preta, esto com o canal alfa variando de
0 at 1, ou, na linguagem do LibreOffice, variando de 100% at 0% de
transparncia.
Toda imagem armazenada da mesma maneira. Porm, h alguns detalhes
sobre imagens que precisamos conhecer.
Unidades de medida
Quando falamos em imagens, muito comum ouvirmos pontos e pixels, conceitos que muitas vezes nos confundem. Um ponto (point) uma
medida de tipografia e equivale a 1/72 de polegada. Um pixel uma unidade
grfica de um dispositivo. a menor unidade utilizvel de um dispositivo grfico, como uma tela de LCD. Temos que deixar claro que estamos estudando
grficos exibidos em telas, e no impressos, pois, caso contrrio tudo mudaria.
Resoluo e densidade
H muita confuso sobre estas duas mtricas. O senso comum diz que resoluo a quantidade de pixels, tanto na horizontal quanto na vertical, que
um dispositivo pode exibir. Tambm serve para determinar o tamanho de
uma imagem. Por exemplo, uma imagem de 5 Megapixels tem 5 milhes de
pixels, o que no est relacionado ao tamanho fsico. Uma tela WXGA, com
1280 x 800 pixels, tem 1,024 Megapixels.
J o tamanho que uma imagem ou tela tero depende muito da densidade,
que a quantidade de pixels por polegada (PPI) que a tela pode exibir (ou que
a imagem espera ser reproduzida). Isto determina qual tamanho a imagem
ter na tela real. Cada dispositivo tem sua densidade especfica, por exemplo
(fonte: gsmarena.com) :
Samsung Galaxy S III: 306 PPI;
Apple iPhone 5: 326 PPI;
Apple iPad 4: 264 PPI;
Apple iPad 2: 132 PPI;
Motorola Xoom 2 Media Edition: 184 PPI.
Profundidade de cor
A profundidade de cor (Color depth) o nmero de bits necessrios para
representar cada cor de um pixel. Temos dois grandes sistemas para representar a cor: indexado e direto. O sistema indexado utilizado quando temos
baixa profundidade de cor, ou seja, temos poucos bits para representar cada
cor. Ento, criada uma palheta (tabela) com as cores utilizadas e o cdigo
, na verdade, o ndice nesta tabela. Sistemas com 8 ou 12 bits por cor geralmente utilizam palhetas de cor. O sistema direto quanto no usamos palheta, mas representamos diretamente o valor de cada cor. Podemos ter vrias
profundidades neste sistema, como 8 bits, 16 bits ou true color, que so 24
bits de cor (8 para cada cor RGB). Com este sistema, podemos representar
mais de 16 milhes de cores. claro que o sistema direto requer mais memria (RAM ou arquivo) para representar as imagens, porm, fica mais fiel.
Hoje em dia, o sistema indexado utilizado para armazenamento de imagem, de modo a economizar espao. Entre os tipos de arquivo que usam palheta temos: BMP, GIF e PNG.
Arquivos de imagem
Existem vrios padres para armazenamento de imagem, cada um melhor para determinada aplicao. Temos formatos que usam palheta, formatos
que usam Color deph e formatos que podem utilizar os dois sistemas. Tambm
podemos armazenar uma imagem de maneiras diferentes, como: Raster e
Vetorial.
Imagens em formato Raster so as mais comuns. So mais simples em
termos de processamento, pois basicamente armazenam os pontos e suas cores.
S isso. necessrio apenas decodificar (ou descomprimir, caso necessrio)
o arquivo e acionar os pontos na tela com a cor necessria. Porm, o formato
Raster tem a desvantagem de ser grande. claro que podemos comprimir a
imagem, perdendo ou no informao, mas, ainda assim, o arquivo grande.
Existem formatos de arquivo Raster que comprimem a imagem, com
perda ou sem perda de informao. O JPEG, por exemplo, utiliza compresso
com perda, que pode ser regulada atravs da qualidade do arquivo. Arquivos
como PNG comprimem sem perda de informaes, porm geram arquivos
maiores.
J as imagens em formato vetorial no representam a imagem na forma de
pixels, mas como vetores. Elas contm as instrues para desenhar a imagem,
permitindo mais suavidade na renderizao. Os arquivos em formato vetorial
so menores do que os em formato raster, porm, sua renderizao exige mais
recursos de processamento e pode gerar distores quando utilizamos dispositivos diferentes.
22
24
26
28
Game engines
Um Game engine uma ferramenta para facilitar o desenvolvimento de games. Eles so compostos por editores, bibliotecas e interface de programao,
que podemos utilizar para criar nossos prprios games. Existem game engines
para vrias plataformas, seja 2D ou 3D, com nveis diferentes de facilidade
de uso.
Adotar um Game engine pode aumentar sua produtividade, permitindo que
voc se concentre mais no Game design (incluindo a jogabilidade), ao invs
de se preocupar com detalhes de animao, game loop etc.
HTML 5 + Javascript
Existem vrios Game engines para HTML 5 + Javascript. J que a Adobe
discontinuou o desenvolvimento do Flash player para dispositivos mveis, a
galera que fazia jogos em Flash est migrando para HTML 5 e Javascript.
Em meu ltimo livro, Mobile Game Jam, eu mostrei como desenvolver
games mveis usando HTML 5 e Javascript, e mostrei um engine multiplataforma: o PhoneGap (Cordova). Mas no mostrei nenhum Game engine desta
plataforma.
O ImpactJS (impactjs.com) uma boa opo. Ele permite criar games
em HTML 5 + Javascript para Desktops e dispositivos mveis. Vem com um
editor de nveis (o Weltmeister), que facilita muito a criao de cenrios para
diferentes nveis do jogo. Ele vem com ferramentas de depurao e empacotadores, que permitem colocar sua aplicao diretamente na AppStore iOS. Seu
custo relativamente barato (US$ 99,00).
Existem vrios outros game engines para HTML 5 + Javascript, porm o
ImpactJS, na minha opinio, o mais profissional.
Nativos
Um Game engine nativo aquele que possui bibliotecas nativas e executa
fora do ambiente do Browser. Geralmente, apresentam melhor performance
do que os Game engines baseados em HTML 5 + Javascript.
O Cocos2D for iPhone (http://www.cocos2d-iphone.org/) um port do
Game engine original Cocos2D, para Python. bastante popular, com vrios games famosos no mercado. Ele vem com um ambiente de desenvolvimento (CocosBuilder), a biblioteca do Game engine em si (cocos2D), e um
engine de fsica (Chipmunk). Existem verses para HTML 5 (http://cocos2d-x.
Se voc vai criar um game 3D, melhor considerar uma ferramenta de desenho apropriada. Eu recomendo o AutoDesk Maya (http://usa.autodesk.com/
maya/), apropriado para criao de personagens 3D com animao. Agora, se
30
preferir um software gratuito, o Blender (http://www.blender.org/) uma excelente opo. E os modelos 3D criados com o Blender podem ser importados
em game engines 3D, como o Unity.
Ambientes multiplataforma
Fora os Game engines, existem alguns ambientes multiplataforma para
criao de aplicaes mveis, alguns deles com recursos para Games. Para
comear, vou vender o meu peixe. No nosso portal The Code Bakers (www.
thecodebakers.org), criamos um framework de aplicaes mveis multiplataforma, o AAMO (www.aamoframework.org). Ele baseado em Lua e pode gerar aplicaes em iOS e Android. Futuramente, haver verses para Desktop e
tambm um game engine embutido: o AAMO Xtreme. O AAMO totalmente
gratuito e Open Source, mas ainda no contempla os recursos para criao de
Games.
Outro ambiente multiplataforma interessante o CoronaSDK (http://www.
coronalabs.com/products/corona-sdk/). Ele tambm baseado na linguagem Lua
e possui recursos muito interessantes para criao de games. Ele foi escolhido
pela EA (famosa por vrios games) para criar seu jogo mvel: World Smack
(http://www.coronalabs.com/blog/2012/11/08/corona-spotlight-ea-chooses-corona-for-word-smack/). O corona no gratuito, mas tem licena de baixo custo, s
necessria quando voc for publicar o game.
Prototipadores
Um grande recurso, geralmente ignorado pelos desenvolvedores de game,
a prototipao. Este recurso nos permite capturar, de maneira simples e clara, os requisitos de uma aplicao. Como games possuem requisitos muito
subjetivos (jogabilidade um deles), a prototipao pode ser um excelente
instrumento para acelerar o desenvolvimento, evitando retrabalho.
Seja utilizando um Game engine ou no, o comprometimento do projeto
de um game cresce rpida e exponencialmente. Em poucas semanas, j temos
tanto cdigo escrito que criamos um forte comprometimento com ele, deixando de ver coisas bvias. Com um prottipo isto evitado, pois podemos jogar
tudo fora e comear novamente.
Felizmente, existem alguns softwares excelentes para prototipao de games e aplicaes em geral. Eu uso dois deles, que considero excelentes.
Para comear, gostaria de falar do Codea (http://twolivesleft.com/Codea/),
uma ferramenta que roda diretamente (e apenas) no iPad. Com ela, podemos
programar diretamente no iPad, sem necessidade de um laptop ou desktop.
Infelizmente, s existe verso para iPad. Podemos at baixar o Codea Runtime (https://github.com/TwoLivesLeft/Codea-Runtime) e distribuir a aplicao na AppStore. A licena do Codea custa US$ 9,99 e acredite: ela se paga
rapidamente!
Vou mostrar o prottipo de um game que foi feito originalmente no Codea
em menos de 4 horas! Eu aprendi a usar o Codea, aprendi a linguagem Lua e
criei o Game enquanto estava hospitalizado, aguardando uma cirurgia renal.
Quando fui para a sala de cirurgia, o Game estava pronto. claro que era um
prottipo, mas estava funcionando completamente.
Eu sou f do Codea, porm, fiquei completamente atnito e pasmo quando
conheci a oitava maravilha do mundo: O Processing (http://processing.org/)!
aquele tipo de coisa bombstica que nos faz gritar: Para tudo! O Processing
um ambiente de simulao e prototipao, que j vem com tudo em cima
para criar Games. Ele usa uma linguagem tipo java, mas tambm permite
exportar seu projeto para HTML 5 + Javascript e at mesmo Android, ou seja:
lava, passa, cozinha e, ainda por cima: gratuito!
Bibliotecas auxiliares
Como j discutimos, um Game um projeto muito complexo, com vrias
macrofunes diferentes. Certamente, podemos utilizar Game engines para
nos auxiliar, porm, ainda existem alguns motivos importantes para estudarmos as bibliotecas em separado:
32
Captulo 4
Prototipao do game
Bem, vamos comear realmente o nosso game. Imaginemos uma ideia
simples e vamos tentar criar um prottipo com ela, de modo a avaliar se est
boa para virar um Game.
Tudo comea com uma ideia. Que tal criarmos um shooting game? Afinal de contas, shooting games so fceis de criar e, geralmente agradam a
todos. Podemos comear com um rascunho (Sketch) do game. Eu pensei
em um game de escapar de asteroides. Voc est em uma nave e tem que
manobrar em um campo de asteroides, evitando colidir com eles. Voc ganha
pontos de acordo com a distncia percorrida, em anos-luz.
Uma dica: se voc comear a complicar, pensando coisas do tipo: j tem
muita coisa assim, preciso criar algo diferente... seu projeto no vai andar.
Voc vai criar um prottipo do jogo, logo, no o momento de pensar nisso.
Depois, voc pode fazer as otimizaes que desejar.
Um rascunho tudo o que precisamos
Eu peguei meu iPad e, usando o Sketchbook, criei vrios desenhos. Eu
fui desenhando o que me vinha mente, sem me preocupar muito com esttica
ou funcionalidade. O desenho que mais gostei foi o da prxima figura.
A nave se move para cima e para baixo, e os asteroides vm em sua direo, pois ela est se movendo para a frente. Existem asteroides bons (os
brilhantes), que repem a energia da nave, e os asteroides ruins (opacos), que
podem destruir a nave. Caso ela colida com um asteroide ruim, sua energia
drenada, pois ela usa os escudos para impedir sua coliso.
34
Se o seu jogo tiver mais telas ou nveis, voc pode ir criando rascunhos
para cada um deles. O importante colocar a idea no papel (digital), antes que
voc se esquea.
Usando o Codea
O Codea um ambiente de programao sensacional. simples e prtico,
permitindo criar rapidamente jogos e simulaes grficas. Ele uma aplicao
para iPad, que est disponvel na AppStore por cerca de US$ 10,00.
36
O ambiente de execuo bem simples. Ele tem um painel de propriedades, um de output (para sada de comandos print), e uma barra de controle.
A barra de controle est sempre no canto inferior esquerdo, e tem alguns botes:
Seta para a esquerda: termina o programa;
Pausa / Play: para ou continua a execuo do programa;
Seta curva: reseta o programa, executando do incio;
Mquina fotogrfica: tira uma foto instantnea do programa;
Filmadora: filma a execuo do programa;
Agora, vamos fazer uma coisa mais legal. Vamos criar um foguete que se
move. Altere o arquivo Main para acrescentar as linhas em negrito abaixo:
-- bola
-- Use this function to perform your initial setup
function setup()
print(Hello World!)
x = 0
y = HEIGHT / 2
supportedOrientations(PORTRAIT_ANY)
displayMode(FULLSCREEN)
end
38
end
Vamos ver o que mudamos. Para comear, criamos duas variveis, x para
posio horizontal e y para vertical. Note que o valor de y a metade da
tela (HEIGHT / 2) fixamos a orientao em portrait, pois queremos que
40
42
else
end
end
sprite(Tyrian Remastered:Eggstroid,
self.position.x,
self.position.y,
self.position.x+self.dimension.x,
self.position.y+self.dimension.y)
function ship:draw()
-- Codea does not automatically call this
spriteMode(CORNERS)
sprite(SpaceCute:Rocketship,self.position.x,
self.position.y,
self.position.x+self.dimension.x,
self.position.y+self.dimension.y)
end
44
Veja na linha em negrito como eu crio uma instncia de uma classe, neste
caso, a nave. O mtodo init da classe ship ser invocado e vai devolver
uma instncia j inicializada, que pode ser referida atravs da varivel nave.
A funo touched ser invocada sempre que o jogador tocar na tela. O
parmetro que ela recebe (touch) contm, entre outras coisas, as coordenadas de onde o jogador tocou na tela. Eu repasso isso para o mtodo touch
da nave.
A funo draw um pouco extensa (e poderia ser refatorada), mas eu
tenho que comentar algumas coisas sobre ela, afinal o Game loop.
-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)
-- This sets the line thickness
strokeWidth(5)
-- Do your drawing here
font(MarkerFelt-Wide)
46
else
spriteMode(CORNERS)
sprite(Tyrian Remastered:Bullet Fire A,
v.position.x,v.position.y,v:finalx
(),v:finaly())
if v.type == 2 then
sound(SOUND_JUMP, 16037)
energy = energy + 20
table.remove(asters,i)
currentasters = currentasters - 1
else
sound(SOUND_EXPLODE, 35632)
energy = energy - 2
end
if energy <= 0 then
fim()
end
end
end
end
end
end
end
end
end
48
...
currentasters = currentasters 1
O parmetro nome indica o nome do som a ser tocado, e a raiz o modifica aleatoriamente. Voc deve experimentar com este segundo parmetro at
chegar ao som que deseja mostrar.
O jogo chega ao fim sempre que a energia acaba.
Durante o jogo, eu exibo algumas mensagens na tela. Eu uso a sequncia
de comandos abaixo:
font(nome da fonte): troca a famlia tipogrfica (a fonte) dos
caracteres;
fill(red, green, blue, alfa): estabelece a cor para a escrita;
fontSize(tamanho da letra): estabelece o tamanho da letra, em pontos (1/72 de polegada);
textMode(origem da posio do texto): especifica o tipo de coordenadas que vamos fornecer. CENTER significa que o centro do
texto e CORNER que o canto inferior esquerdo do texto;
text(texto a ser escrito, x, y): os parmetros x e y so as coordenadas do texto (veja textMode);
Com este prottipo, eu posso brincar com o game, repensando a jogabilidade, as regras etc. A codificao em Lua de baixa verbosidade e a API do
Codea facilita tudo. Eu tenho um jogo quase completo que foi desenvolvido
rapidamente.
Infelizmente, o Codea s existe para iPad... Esta uma limitao muito sria. Para comear, temos que ter um iPad para desenvolver o prottipo.
No podemos sequer rodar em um iPhone, quanto mais em um dispositivo
Android.
Neste caso, temos outra ferramenta excelente para prototipao: Processing.
Usando o Processing
Como definir o Processing? Bem, para comear, podemos dizer que ele
um ambiente de programao, voltado para criao de grficos e simulaes.
Na verdade, ele mais do que isso, pois tambm um game engine e um simulador de grficos 3D.
A primeira reao de quem v os demos do processing pela primeira vez
surpresa. Sim, voc tambm vai ficar de boca aberta, babando, ao ver como
possvel criar simulaes fantticas, com to pouco cdigo. Vamos fazer
isso agora. Para comear, baixe o Processing do seu site processing.org, e
descompacte para a pasta que julgar conveniente.
Abra a pasta onde instalou o Processing e execute o programa (no Windows
Processing.exe). Voc ver a tela do PDE (Processing Development
Editor), cuja simplicidade se assemelha muito ao Codea. Na verdade, o Codea
foi inspirado pelo Processing.
50
Caro leitor, cara leitora, no impressionante? Se analisarmos o cdigo-fonte da aplicao (que tem dois arquivos), veremos que so poucos comandos para gerar um efeito sensacional.
O Processing foi criado para ser uma ferramenta de prototipao de aplicaes grficas, como games e simulaes. S que, hoje em dia, utilizado em
aplicaes profissionais mesmo. Com ele, podemos criar efeitos e animaes
com grficos 2D e 3D, utilizando OpenGL.
Ele permite desenvolvermos aplicaes rapidamente, utilizando uma linguagem parecida com Java, da Oracle. Na verdade, o Processing converte
o cdigo digitado em Java e o executa utilizando a JVM.
O Processing possui alguns modos de execuo, que permitem migrar
o executvel da sua aplicao para as plataformas que ele suporta: Java,
Javascript e Android. Para mudar de plataforma, basta clicar no boto
JAVA que est na barra superior e selecionar a nova plataforma. Porm,
antes que voc fique muito animado com o Android, quero esclarecer que
esta caracterstica ainda no funciona corretamente.
Eu tentei muito tempo rodar uma aplicao Processing no Android e at
consegui, porm, foi to complicado que resolvi no recomendar no livro.
Apesar do Emulador Android suportar OpenGL ES 2.0 (a partir da API 15),
no consegui fazer funcionar. Somente com o dispositivo real conectado e,
mesmo assim, tive diversos problemas.
Mas estes problemas no invalidam o uso do Processing como ferramenta de prototipao de games, pois s rodar no modo Java e tudo vai funcionar, inclusive o OpenGL.
52
Agora, dentro da pasta do seu sketch, crie uma pasta chamada data e
copie o arquivo da nave, que fica em: ...\Codigo\AsteroidNuts_Processing\
Main\data\nave.png. Para saber como baixar os arquivos-fonte do livro, veja
na Introduo. A imagem deve ficar em uma subpasta data, dentro da pasta onde voc salvou o seu sketch, certo?
Agora, vamos digitar alguma coisa no PDE. Uma aplicao Processing
tem estrutura bastante simples. Podemos digitar comandos imediatos (semelhante ao Javascript) ou podemos criar funes. Existem duas funes especiais, que so callbacks, ou seja, o runtime do Processing as invoca
quando necessrio: setup() e draw(). A funo setup() chamada no
incio do programa e apenas uma nica vez. Nela, colocamos todo o cdigo
de inicializao. A funo draw() invocada quando necessrio criar um
novo frame.
O Processing serve para criarmos simulaes framed-based, ou seja,
aquelas semelhantes a desenhos animados. A cada intervalo de tempo, atualizamos o modelo, apagamos e redesenhamos a tela. Este intervalo o frame rate, que medido em FPS Frames por Segundo. Podemos ajustar o
intervalo com a funo frameRate(). O FPS default 60, ou seja, a funo
draw() ser invocada 60 vezes por segundo.
Vamos criar duas variveis: uma para armazenar a imagem da nave e a outra para armazenar a posio dela. Antes de mais nada, o Processing utiliza
o sistema cartesiano com a origem no canto superior esquerdo, ao contrrio do
Codea. Eis o cdigo inicial:
PImage nave;
PVector posicao;
nossa varivel nave o sprite da nave, que est na subpasta data. Finalmente, iniciamos o vetor posicao indicando x = 10 e y = 80.
Agora, vamos fazer com que a nave ande at o final da tela, um pixel a
cada 1/60 de segundo. Para isto, temos que apagar a tela, desenhar a nave e
incrementar a posio no eixo das abscissas. Se o valor for maior que a largura da tela (width) ento temos que voltar posio inicial. Eis o cdigo da
funo draw():
void draw() {
background(40, 40, 50);
imageMode(CORNERS);
image(nave, posicao.x, posicao.y,
posicao.x + 100,
posicao.y + 50);
posicao.x++;
if (posicao.x > width) {
posicao.x = 10;
}
}
54
direito da tela, em uma altura varivel. Haver uma faixa de valores no eixo
das ordenadas (Y), onde os asteroides podero surgir. Eu no quero usar a tela
toda, pelo menos neste prottipo. Ento, faz sentido passarmos em passar os
limites para o construtor da classe, de modo que este possa gerar um asteroide
dentro da faixa esperada.
class Asteroid {
PVector position;
PVector dimension;
PImage imagem;
int type;
Asteroid(int upper, int lower) {
int tipo = int(random(1,101));
position = new PVector(100,50);
dimension = new PVector(50,50);
position.x = width - 100;
float rPos = random(upper, lower);
position.y = int(rPos);
if (tipo > 80) {
// asteroide bom
type = 2;
imagem = loadImage(bom.png);
}
else {
type = 1;
imagem = loadImage(ruim.png);
}
}
56
(propriedade dimension). O primeiro parmetro o nome da figura, o segundo e o terceiro, as coordenadas do canto superior esquerdo, e o quarto e o
quinto, as coordenadas do canto inferior direito.
Finalmente, temos dois mtodos finalx e finaly, que retornam as coordenadas do canto superior direito do asteroide, para facilitar seu uso.
Agora, precisamos representar a nave (arquivo ship.pde). Vamos criar
uma classe da mesma maneira. Em seu construtor, ns criamos as mesmas
propriedades do asteroide, exceto o type. Mas colocamos o lower e o
upper como propriedades da nave. Veja o mtodo init:
class ship {
PVector position;
PVector dimension;
PImage imagem;
int upper;
int lower;
ship() {
position = new PVector(10,80);
dimension = new PVector(100,50);
position.y = (height - dimension.y)/2;
upper = int(position.y - 4 * dimension.y);
lower = int(position.y + 5 * dimension.y);
imagem = loadImage(nave.png);
}
void draw() {
imageMode(CORNERS);
image(imagem, position.x, position.y,
position.x + dimension.x,
position.y + dimension.y);
}
58
position.y = y;
Aqui valem algumas notas importantes sobre arquivos de recursos (imagens e fontes). Todos os recursos em uma aplicao Processing devem ficar
dentro da subpasta data, dentro da pasta onde esto os sketches que voc
vai rodar. Logo, todas as imagens devem estar dentro dela. Esta estrutura j
est desta forma no zip do cdigo-fonte do livro.
Outra coisa importante a fonte. Se voc for escrever alguma coisa, deve
criar um arquivo de fonte no formato vlw, que o Processing usa. Existe
um arquivo vlw para cada combinao de tipo e tamanho de letra. Para criar
um arquivo vlw:
1. Abra o menu tools / create font;
2. Selecione o tipo de letra e o tamanho;
3. Clique em Ok;
4. Copie o arquivo vlw gerado (fica na pasta do prprio Sketch
para a subpasta data).
A funo mouseClicked ser invocada sempre que o jogador clicar na
tela. Quando isto acontece, eu repasso as coordenadas do clique para o mtodo
clicked da nave.
A funo draw um pouco extensa (e poderia ser refatorada), mas eu
tenho que comentar algumas coisas sobre ela, afinal o Game loop.
void draw() {
background(40, 40, 50);
strokeWeight(5);
textFont(letraTitulo);
fill(121, 249, 16, 255);
60
62
mit);
}
else {
lastaster = lastaster + 1;
}
64
Captulo 5
Fsica de games
Fsica uma macro funo muito importante em games de ao, pois serve
para aumentar a jogabilidade, aumentando a percepo de realidade e o envolvimento do jogador. A fsica de um game deve lidar com problemas como:
fora, acelerao, movimento e coliso, tentando fazer com que os Game Objects se comportem aproximadamente como os modelos reais.
Os exemplos so incrementais
Eu vou mostrar vrios exemplos neste captulo e todo o cdigo-fonte est
disponvel para voc, logo, no necessitar digitar coisa alguma.
Porm, eu quero fazer uma observao sobre os exemplos: o objetivo
mostrar como aplicar clculos de fsica aos games. Neste momento, eu no
estou preocupado com a preciso do Game Loop ou com quaisquer outros
detalhes. Logo, antes de usar os exemplos como base para o seu game, tenha
em mente que existem mais aspectos a serem analisados, ento, eu sugiro que
voc leia at o final ANTES de sair criando seu jogo definitivo.
Rode todos os exemplos
Este captulo muito grande, mas extremamente importante para que
voc conhea bem a fsica de jogos. Todos os exemplos esto junto com o
cdigo-fonte que acompanha o livro. Eu recomendo que voc baixe todos e
rode em seu computador.
66
Os primrdios
Conceitos bsicos
Nem todo game to exigente na fsica quanto os jogos de ao. Existem
games que sequer possuem qualquer tipo de fsica. o caso do game que estou desenvolvendo para Facebook: o RandoSystem, que um quebra-cabeas
baseado em labirinto.
No BueiroBall (eu sei, o nome poderia ser melhor) voc tem que encaapar as bolas em determinada ordem, seno perde pontos (neste caso, as pretas s podem entrar depois das coloridas) e voc joga usando o acelermetro,
68
Acelerao e movimento
A parte que estuda isso na fsica a cinemtica. Eu no vou desperdiar
seu tempo explicando conceitos de fsica, mas, se quiser saber, recomendo a
srie fsica de video game, do portal The Code Bakers (http://www.thecodebakers.org/search/label/F%C3%ADsica).
O grande problema de jogos de movimento dinmico calcular onde voc
deve desenhar a figura no prximo frame. Se voc utiliza uma fsica simples,
como a que vimos no prottipo do AsteroidNuts (captulo anterior), isto
no problema. Basta decrementar ou incrementar o valor da coordenada
correspondente direo do movimento e pronto. No caso do AsteroidNuts,
estamos no espao, e consideramos a acelerao constante. Mas poderamos
criar efeitos interessantes, como o puxo da gravidade, por exemplo.
Para calcular a posio de um objeto com relao a um plano de coordenadas cartesianas, podemos usar o mtodo de Verlet (http://pt.wikipedia.org/wiki/
M%C3%A9todo_de_Verlet ou http://www.fisica.ufjf.br/~sjfsato/fiscomp1/node40.
html). A frmula bsica :
x(t + t) = (2 f)x(t) (1 f)x(t - t) + a(t)( t)2
Podemos calcular a posio em cada eixo, informando as foras que foram
aplicadas a eles (impulso e gravidade, por exemplo).
isso o que fiz no game Ataque das formigas marcianas, publicado
no The Code Bakers (http://www.thecodebakers.org/2012/04/fisica-de-videogame-3-finalmente-um.html). O cdigo-fonte do Game est no Google Code
(http://code.google.com/p/ataque-formigas-marcianas/), e ele usa a licena Apache
2.0 (Open Source).
70
reset();
void reset() {
limiteSuperior
=
altura
=
alturaAnterior
=
alturaLimite
=
aceleracao
=
velocidade
=
dt
=
forcaGrav =
massa
=
descendo
=
}
PISO;
limiteSuperior;
altura;
0;
0.0d;
0.0d;
01.d;
-9.8d;
1.0d;
true;
void atualizar() {
altura = altura + velocidade * dt +
(aceleracao * Math.pow(dt, 2)) / 2.0d;
double vel2 = velocidade + (aceleracao * dt) /2.0d;
double aceleracao = forcaGrav / massa ;
velocidade = vel2 + (aceleracao * dt) / 2.0d;
}
Coliso
Em fsica, temos dois tipos bsicos de coliso: elstica e inelstica (http://
www.coladaweb.com/fisica/mecanica/colisao-elastica-e-inelastica). Eu postei um
artigo sobre isso no portal The Code Bakers (http://www.thecodebakers.
org/2011/06/fisica-de-videogame-2-colisoes.html). O tipo de material dos dois ob-
O CR que estou usando (0,50) provoca um alto valor de restituio, permitindo que a bola quique alto. Se usarmos algo como bola murcha caindo
em argila, teremos uma baixssima restituio, j que os dois materiais usaro
a maior parte da energia cintica no trabalho de deformao, absorvendo o
impacto.
72
Deteo de coliso
Se temos um jogo onde existem objetos em movimento, ento tambm
teremos que detetar colises entre eles. Note que coliso nem sempre significa
desastre, por exemplo, quando o heri pula para uma plataforma, ocorre
uma coliso.
Eu criei alguns artigos sobre deteo de coliso no portal The Code
Bakers (http://www.thecodebakers.org/search/label/Game), mas um assunto
complexo e difcil de ser resolvido em desenvolvimento de games.
Todo Game Object que est em uma determinada camada pode ser afetado
por outros objetos da mesma camada. Por exemplo, a nave e os asteroides no
prottipo AsteroidNuts esto na mesma camada e podem se colidir.
Como saber se dois objetos colidiram? Se eles se tocarem ou se tiverem
pontos em comum, ento podemos deduzir que houve coliso. Todo Game
Object tem sua imagem exterior, logo, podemos assumir que esta a sua
fronteira? Imagine o GO da figura seguinte. Como detectaramos coliso de
um objeto como este?
Neste caso, a coliso se resume a determinar se os dois retngulos se intercetam. E isto pode ser feito facilmente, tanto no Android, como no iOS.
// Android
private boolean colisao (Rect r1, Rect r2) {
if (r1.intersect(r2)) {
return true;
}
return false;
}
// Objective C iOS
- (BOOL) colisao: (CGRect) r1 outro: (CGrect) r2
{
if (CGRectIntersectsRect(r1,r2)) {
return YES;
}
return NO;
}
74
Porm, temos outro problema: como vamos detetar colises? Bem, dependendo da forma dos colisores, existem vrias maneiras.
76
Engines de fsica
Para facilitar o desenvolvimento de jogos de ao, foram criadas vrias
bibliotecas de funes e clculos de fsica, chamadas de Physics engines
(Engines de fsica, a forma que eu prefiro). Existem vrios engines, sejam
gratuitos, pagos, 2D ou 3D. Todos eles servem para calcular: acelerao, movimento, coliso e seus efeitos.
perfeitamente possvel criar um game de ao sem utilizar um engine
de fsica. Na verdade, eu fiz isso em alguns projetos de jogos. O fator a ser
analisado na deciso de uso de um engine : qual o valor agregado por uma
fsica mais realista? certo que uma fsica mais realista vai demandar maiores recursos, como: CPU e/ou memria, ento, temos que ter certeza de que
seu uso vai agregar valor proporcional ao jogador, caso contrrio, estaremos
introduzindo o risco de lag toa.
Bullet_User_Manual.pdf.
Box2D
O Box2D uma biblioteca de funes fsicas para games totalmente
Open Source e gratuita, desenvolvida em C++ por Erin Catto. Seu site :
http://box2d.org. uma das mais famosas, talvez por ser uma das mais simples.
No possui recursos sofisticados, mas permite calcular movimento e coliso
de forma bem realista.
Ela possui verses para vrias linguagens, como o JBox2D (http://www.
jbox2d.org/), que bem fiel biblioteca original. Algo que ajudou a criar a
fama do Box2D foi sua utilizao no blockbuster Angry Birds (Rovio).
Como eu no posso abordar todos os engines de fsica em apenas um nico
livro, escolhi o Box2D, pois, alm da preferncia pessoal, muito comentado
hoje em dia.
78
Preparando o laboratrio
Considerando que nem todo mundo tem um Mac ao seu dispor, achei
melhor estudarmos os conceitos do Box2D com um laboratrio, baseado no
JBox2D e em Java. As diferenas so desprezveis, e podemos trabalhar em
qualquer plataforma Desktop. Depois, quando formos estudar a aplicao em
dispositivos mveis, ns usaremos o Box2D para iOS e o JBox2D para o
Android.
Eu recomendo que voc baixe e leia o manual do Box2D (C++): http://
box2d.org/manual.pdf. A definio de todas as funes est nele. Aqui, eu apenas listo um resumo. Se quiser o modelo de objetos em Java, leia o JavaDoc
do JBox2D, que vem dentro da distribuio. Eu aconselho voc a comprarar
sempre os dois.
Eu recomendo que voc baixe os dois: o Box2D e o JBox2D. Para baixar
o Box2D, acesse o link: http://code.google.com/p/box2d/downloads/list e baixe a
ltima verso (no meu caso 2.2.1). E, para baixar o JBox2D, acesse o link:
http://code.google.com/p/jbox2d/downloads/list e baixe a ltima verso (2.1.2.2).
Descompacte os dois arquivos em pastas separadas.
O cdigo-fonte deste projeto est junto com os fontes do livro. Veja no
captulo de introduo. A pasta ...\Codigo\JBox2DLab\jbox2lab.zip. um
projeto eclipse compactado, logo, voc pode importar para sua workspace.
Nota: Configure sua Workspace eclipse para trabalhar com caracteres
UTF-8. Abra o menu Window / Preferences, expanda o item General e
clique em Editors, clique no link Content-types, selecione Text e digite
UTF-8 no campo Default encoding, pressionando o boto Update. Isto
far com que os caracteres acentuados apaream de forma correta. Usurios
Mac acharo o menu Eclipse / Preferences.
Antes de comearmos, quero deixar claro que, nesta parte do livro, vamos
apenas tratar de Box2D, deixando os outros assuntos para mais adiante.
Eu criei um laboratrio Java, porque roda em qualquer plataforma (Microsoft Windows, Linux ou Mac OSX). Vamos aprender a usar o Box2D com ele
(na verdade, usando o JBox2D). Mais para o final do captulo, eu mostrarei
como usar o Box2D em projetos Android e iOS.
Porm, se voc no conhece Java, isto pode ser um problema. Na verdade,
neste livro eu assumo que voc conhece o bsico de Android e de iOS, logo,
tem que conhecer um pouco de Java e de Objective-C. claro que voc pode
optar por usar somente uma das plataformas, ignorando a outra.
80
O laboratrio Java
O laboratrio uma aplicao bem simples, que mostra a simulao de
uma bola caindo no cho e quicando.
Ele servir para exercitar vrios conceitos que veremos neste captulo. O
prprio Box2D tem um programa de laboratrio, chamado de TestBed, e o
JBox2D tem um programa semelhante, que pode ser acionado ao darmos um
duplo clique no arquivo jbox2d-testbed-2.1.2.2-jar-with-dependencies.jar,
que um JAR executvel, localizado na pasta target do projeto JBox2D
library.
82
suave dos frames. Mais adiante, veremos tcnicas para criar Game loops otimizados, voltados para games profissionais. Porm, por enquanto, essa no
minha preocupao, logo, criei um esqueleto de Game loop bem simples, que
usa a classe java.util.Timer para controlar sua execuo. Este game loop no
to preciso, mas funciona relativamente bem e permite estudarmos o Box2D
sem grandes preocupaes.
Eis o cdigo-fonte do nosso Game loop, junto com o seu disparador:
// *** Rotinas do Game Loop ***
/*
* Para maior simplicidade, estamos usando as classes java.
util.Timer e
* java.util.TimerTask. Alm disto, estamos usando
renderizao passiva.
* Se quiser um melhor resultado, veja o captulo Framework
bsico de game.
* Este tipo de implementao de Game loop no muito preciso,
servindo apenas
* para demonstrao do Box2D.
*/
public void runGameLoop() {
simulando = true;
task = new GameLoopTask();
timer = new Timer();
timer.scheduleAtFixedRate(task, 0, gameLoopInterval);
}
public void gameLoop() {
synchronized (this) {
// Um lembrete de que pode haver problemas de
concorrncia
update();
redesenhar();
};
this.pGrafico.repaint();
}
class GameLoopTask extends TimerTask {
@Override
public void run() {
gameLoop();
}
}
Eu uso um Timer (java.util.Timer) para disparar uma Task (classe GameLoopTask), que vai rodar a cada x milissegundos (varivel gameLoopInterval). A minha Task simplesmente executa o mtodo gameLoop(), que atualiza o estado do modelo (mtodo update()) e comanda
a criao do buffer do frame (mtodo redesenhar()). Depois, ele fora a
atualizao do painel grfico invocando seu mtodo repaint().
Eu estou usando a tcnica de Double buffering, ou seja, eu desenho em
uma imagem no atrelada tela e depois desenho esta imagem no contexto
grfico do painel. Assim, eu evito invocar mtodos de desenho complexos
dentro do mtodo paintComponent() do painel.
private void redesenhar() {
// Estamos usando a tcnica de double buffering
// Vamos desenhar em uma imagem separada
gx.setColor(Color.black);
gx.fillRect(0, 0, larguraImagem, alturaImagem);
gx.setColor(Color.yellow);
Retangulo rect = criarRetangulo(bola);
gx.drawOval(Math.round(rect.x), Math.round(rect.y),
Math.round(rect.width), Math.round(rect.width));
rect = criarRetangulo(chao);
gx.drawRect(Math.round(rect.x), Math.round(rect.y),
Math.round(rect.width), Math.round(rect.height));
}
84
Com isto, tenho certeza que o paintComponent() (invocado assincronamente) jamais pegar sujeira da varivel gImagem. Com o novo Memory
Model do Java, os comandos de leitura e gravao so reordenados para evitar
este problema. Usando o synchronized, ns sinalizamos isto.
Agora, vamos ver rapidamente como ns traduzimos as informaes do
mundo virtual do Box2D para a nossa tela.
86
Fundamentos do Box2D
Vamos mostrar como usar o Box2D comeando pelo exemplo que j criei
no laboratrio. Todo o cdigo-fonte relacionado com o Box2D est no grupo
*** Funes que lidam com o Box2D ***. Temos o mtodo initBox2D(),
que inicializa o mundo virtual e cria os objetos que sero animados, e temos
o mtodo update(), que comanda a sua atualizao. O mtodo update()
invocado pelo Game loop e faz com que todos os objetos dinmicos sejam
atualizados. Porm, como o Box2D sabe se os objetos devem se mover e para
onde devem ir? Isto o resultado da atuao de foras sobre eles e, neste
exemplo, a fora da gravidade.
Classe World
A classe World (ou b2World, na verso C++) o container para
todos os outros objetos e atravs dele que tudo calculado. Ns utilizamos
88
no Box2D (C++):
b2Vec2 gravidade(0.0f, -10.0f);
b2World* world = new b2World(gravidade, true);
...
delete world; // ao final, temos que deletar o objeto,
pois C++ no tem GC!
90
No se assuste com o tamanho do cdigo, pois a maior parte apenas configurao. Podemos mover toda essa parte para um arquivo XML e ler dentro
do programa, como vou mostrar mais adiante.
Agora, simule o programa dando um clique na tela. Rode at as duas bolas estarem no cho e observe atentamente o comportamento dos corpos. Eis
minhas observaes:
1 Corpos dinmicos colidem com corpos estticos e cinemticos. Voc
nota isso quando a bola mais esquerda resvala na barra cinemtica, que
est se movendo na horizontal. Isto afeta o movimento da bola. Outra prova
que a bola cai no cho e quica;
2 Corpos cinemticos no colidem com nada. Eles simplesmente se
movem, sem serem afetados por foras ou colises. Note que a barra que se
move na horizontal atravessa o quadrado esttico. E a outra barra, que se
move na vertical, atravessa a segunda bola, embora, no movimento de subida, ela carregue a outra bola para cima. Se esperarmos at que as duas bolas
estejam no cho, os caminhos das duas barras cinemticas vo se cruzar e elas
no sero afetadas por isto;
3 Corpos dinmicos colidem com tudo. Conforme demonstrado pelo
programa, as bolas colidem com o cho, com as barras e inclusive uma com a
outra. Corpos dinmicos so afetados por foras e por colises;
Agora, para efeito de simplicidade, vamos voltar ao exemplo inicial do
laboratrio (LabBox2D), de modo a explicar a criao dos corpos bsicos
(dinmicos e estticos).
Criamos corpos com a seguinte sequncia:
1. Criamos uma definio de corpo (JBox2D: BodyDef, Box2D:
b2BodyDef);
2. Ajustamos a posio do corpo com o mtodo .position.set() (Box2D:
.position.Set());
3. Criamos e instanciamos o corpo com o mtodo world.createBody()
(Box2D: world.CreateBody();
4. Informamos o tipo de corpo com o mtodo .setType() (Box2D:
propriedade type);
5. Criamos uma forma de colisor (Collision Shape) e informamos o
tamanho;
6. Criamos uma fixture para associar o corpo e o colisor;
Vamos ver um exemplo de criao de corpo:
JBox2D:
Box2D (C++):
b2BodyDef bolaDef;
bolaDef.position.Set(gObject.x, gObject.y);
b2Body * objeto = world->CreateBody(&bolaDef);
bolaDef.type = b2_dynamicBody;
b2CircleShape bola;
bola.m_p.Set(0, 0);
bola.m_radius = gObject.altura/2;
b2FixtureDef fixtureDef;
fixtureDef.shape = &bola;
fixtureDef.density = 4.0f;
fixtureDef.friction = 0.3f;
fixtureDef.restitution = 0.8f;
objeto->CreateFixture(&fixtureDef);
bolaDef.userData = (__bridge void *) gObject;
A forma do colisor determina a sensibilidade do objeto. No Box2D podemos ter vrios tipos de colisores:
Circulares: JBox2D CircleShape, Box2D: b2CircleShape;
Retangulares: JBox2D PolygonShape com setAsBox(),
Box2D PolygonShape com SetAsBox();
Segmentos de reta: Box2D b2EdgeShape. O JBox2D no tem
este tipo, mas pode ser criado;
Cadeia: Servem para ligar vrios segmentos de reta. No Box2D
b2ChainShape. O JBox2D no tem este tipo;
92
Fora e atrito
Os corpos dinmicos so sujeitos aplicao de foras, como a gravidade.
Porm, tambm podemos aplicar uma fora a eles, causando seu movimento.
De acordo com segunda lei de Newton (http://pt.wikipedia.org/wiki/Leis_de_
Newton), a fora (F) o resultado da multiplicao da massa (m) de um
corpo, por sua acelerao (a), que um vetor:
F = ma
94
padro MKS (metro, quilograma e segundos). Como o Box2D fez isso? Ser
que est certo?
Precisamos relembrar alguns conceitos envolvidos...
Fora: uma grandeza que tem a capacidade de vencer a inrcia
de um corpo, modificando-lhe a velocidade. medida em Newtons
(N). Lembrando que estamos trabalhando em um mundo 2D, logo,
a fora pode ser aplicada a um ou aos dois eixos;
Massa: o produto entre a densidade e o volume de um corpo. No
deve ser confundida apenas com peso do corpo. A unidade de medida de massa o quilograma (kg);
Velocidade: a variao da posio de um objeto no espao, com
relao ao tempo, medida em metros por segundo (m/s);
Acelerao: a variao da velocidade de um objeto no espao e
vetorial, ou seja, pode ocorrer em mais de um sentido. Sua unidade
de medida metros por segundo ao quadrado (m/s2);
Atrito: a fora de resistncia entre dois objetos. Neste caso, estamos tratando de atrito dinmico, que ocorre quando h movimento
relativo entre eles. Cada tipo de material tem um coeficiente de atrito, que deve ser combinado com o do outro para calcular a fora de
resistncia;
Nosso objeto tem massa calculada de 500 kg (Body.getMass()). O Box2D calcula a massa com base nas dimenses e densidade. Ele tem que extrapolar o volume do objeto e ele usa a rea, afinal, em 2D os objetos no
possuem volume. Ao clicar na barra de espao, eu aplico uma fora ao objeto:
Vec2 forca =
new Vec2(+300.0f * quadrado.getMass(), 0.0f *
quadrado.getMass());
Vec2 posicao = quadrado.getWorldCenter();
quadrado.setAwake(true);
origemX = quadrado.getTransform().position.x;
vMax = 0;
quadrado.applyForce(forca, posicao);
96
Uma fora tem tambm um sentido (um vetor), logo, devemos indicar isso
ao aplic-la. Estamos acertando bem no centro de massa do objeto, de modo
a evitar torque, pois queremos uma acelerao linear limpa.
O resultado uma fora de 150.000 N.
Para calcular a distncia que o objeto vai percorrer, eu posso aplicar a seguinte frmula:
d = V 2 / (2 . g . (f + G))
Onde:
d = distncia de parada;
g = acelerao da gravidade (estamos usando 10 m/s2);
G = inclinao do plano em percentuais (zero);
V = velocidade inicial (no nosso caso, a maior velocidade obtida
pela aplicao da fora);
f = coeficiente de atrito entre o cho e o objeto;
No sabemos exatamente por quanto tempo a acelerao foi aplicada... certo
que foi por uma frao de segundo, pois eu uso o mtodo world.clearForces();
aps cada atualizao do mundo. Ento, eu tive que obter a maior velocidade
que o objeto alcanou:
if (quadrado.getLinearVelocity().x > vMax) {
vMax = quadrado.getLinearVelocity().x;
}
gx.drawString(Velocidade X max: + (vMax) + m/s, 30, 60);
E esta velocidade foi cerca de 9,89 m/s. Ento, fcil saber o tempo em
que a fora foi aplicada:
v = a.t, logo: t = v/a, ento: t = 9,89 / 300, ou aproximadamente: 0,0330 s.
Bem, voltando ao clculo da distncia, temos que considerar o Coeficiente
de Atrito Combinado dos dois objetos. Uma das maneiras de calcular :
cac = sqr(coeficiente1 * coeficiente2).
O coeficiente do cho 0.5 (chaoFixDef.friction = 0.5f;) e o do quadrado
0.2 (quadradoFixDef.friction = 0.2f;), logo, o coeficiente de atrito combinado 0,31622. Ento, nossa frmula de distncia :
d = 9,892 / (2 . 10 . (0,31622)) = 15,47 m
Logo, chegamos a um resultado aproximado com o que o Box2D calculou,
e utilizamos os conceitos de fora, acelerao, velocidade, massa e atrito.
claro que este um exemplo bem simples, pois s existe um nico objeto
dinmico envolvido. Se tivermos mais objetos dinmicos, ou se tivermos acelerao nos dois eixos, teremos que considerar outros fatores, como o ngulo
final do objeto. Mas, vamos devagar.
Ateno: voc no vai necessitar fazer todos esses clculos para criar jogos!
Mas deve entender os conceitos e as propriedades dos corpos, de modo a projetar movimentos mais realistas em seus Games.
Agora, chegou o momento de falarmos sobre alguns mtodos novos.
Para comear, temos que entender o que so coordenadas locais e globais no
Box2D:
Coordenadas locais: so baseadas no centro do objeto, como se ele
estivesse posicionado no ponto de origem (0,0);
Coordenadas globais: so as coordenadas do mundo Box2D;
Vrias propriedades de objetos e parmetros de funo no Box2D exigem
coordenadas locais, logo, importante entender bem os conceitos.
Alguns mtodos que utilizamos neste exemplo:
quadrado.getWordCenter(): obtm as coordenadas do centro de
massa do objeto, transformadas em coordenadas globais;
quadrado.getTransform(): obtm as transformaes aplicadas ao
centro do corpo, como posio e rotao. Podemos saber a posio
atual com getTransform().position, j em coordenadas globais;
quadrado.getMass(): obtm a massa calculada do objeto;
quadrado.applyForce(): aplica uma fora em um determinado
ponto do mundo;
Corpos circulares
J vimos como aplicar fora a corpos quadrados, agora, vamos ver como
fazer isto com corpos circulares. O exemplo ForceBox2D (...\Codigo\ForceBox2D\forcebox2d.zip) mostra como isto acontece.
98
Retire o comentrio e rode novamente. Voc notar que a bola rola menos
e parece estacionar depois de algum tempo. Mas o que foi exatamente que eu
fiz? O mtodo setFixedRotation(true) impede que o objeto role, ou seja, se
submetido a uma fora lateral, ele vai deslizar e NUNCA rotacionar. A bola
parece estar rolando, porm, na verdade, est deslizando sobre o cho. Note
que eu tive que diminuir bem os coeficientes de frico da bola e do cho
(ambos para 0,1).
Para testar, rode o programa, clique com o mouse e tecle a barra de espao
(uma s vez). Voc ver que o quadrado recebe um peteleco na parte superior esquerda, e sai rodando (no sentido horrio) at parar.
Os corpos dinmicos no Box2D esto sujeitos atuao das foras,
que podem provocar ao de torque, causando a rotao do objeto. Quando isto acontece, o ngulo do objeto muda e os seus vrtices reais tambm.
Para renderizar corretamente um objeto em rotao, necessrio calcular as
coordenadas reais de seus vrtices.
100
Para obter a coordenada global de um vrtice (j aplicada rotao), podemos pegar a coordenada do ponto local correspondente a ele. E o que eu
fao no novo mtodo criarPoligono():
//Coordenadas do canto superior esquerdo
Vec2 ponto = body.getWorldPoint(
new Vec2(
body.getLocalCenter().x - tamanho.x / 2,
body.getLocalCenter().y + tamanho.y / 2
)
);
ponto = normalizarCoordenadas(ponto);
O mtodo getLocalCenter() me d o centro de massa em coordenadas locais. Como um retngulo, eu posso simplesmente calcular a coordenada do vrtice e transformar em coordenadas globais, atravs do mtodo
getWorldPoint(). Note que o mtodo getLocalCenter(), neste caso, sempre retornar (0,0).
Depois de obter as coordenadas globais no Box2D, preciso normaliz-las
para a minha janela grfica, como fazamos no mtodo criarRetangulo().
Por que eu no posso simplesmente desenhar um retngulo? Afinal, eu posso fazer uma transformao ao desenh-lo em Java (Graphics2D.rotate()). A
resposta simples: sim, voc pode! Na verdade, o que faremos mais adiante.
Note que eu apliquei uma fora nos dois eixos e posicionei bem na metade
superior do quadrado, de modo a provocar o torque desejado e a rotao no
sentido horrio.
Vec2 forca =
new Vec2(350.0f * quadrado.getMass(), 350.0f *
quadrado.getMass());
Vec2 posicao = quadrado.getWorldCenter().add(new Vec2
(0,3));
quadrado.setAwake(true);
origemX = quadrado.getTransform().position.x;
vMax = 0;
quadrado.applyForce(forca, posicao);
que faz exatamente este trabalho. Ele retorna uma instncia de java.awt.
Polygon.
private Polygon criarPoligono(Body body) {
int [] xpoint = new int [4];
int [] ypoint = new int [4];
Vec2 tamanho = (Vec2) body.getUserData();
//Coordenadas do canto superior esquerdo
Vec2 ponto = body.getWorldPoint(
new Vec2(
body.getLocalCenter().x - tamanho.x
body.getLocalCenter().y + tamanho.y
)
);
ponto = normalizarCoordenadas(ponto);
xpoint[0] = (int)ponto.x;
ypoint[0] = (int)ponto.y;
//Coordenadas do canto superior direito
ponto = body.getWorldPoint(
new Vec2(
body.getLocalCenter().x + tamanho.x
body.getLocalCenter().y + tamanho.y
)
);
ponto = normalizarCoordenadas(ponto);
xpoint[1] = (int)ponto.x;
ypoint[1] = (int)ponto.y;
//Coordenadas do canto inferior direito
ponto = body.getWorldPoint(
new Vec2(
body.getLocalCenter().x + tamanho.x
body.getLocalCenter().y - tamanho.y
)
);
ponto = normalizarCoordenadas(ponto);
xpoint[2] = (int)ponto.x;
/ 2,
/ 2
/ 2,
/ 2
/ 2,
/ 2
102
O mtodo obtm o tamanho original, que eu armazenei dentro da propriedade userData, e calcula as coordenadas locais de cada vrtice, transformando-as em globais com o mtodo getWorldPoint(). Depois, eu normalizo
as coordenadas (translao e escala) para a minha janela grfica. Finalmente, uma instncia de java.awt.Polygon retornada para ser renderizada:
gx.drawPolygon(criarPoligono(quadrado));
E ento? Ficou legal? Ainda no? Ento tente a imagem seguinte, bastando
executar a classe Cenario.java...
104
Primeiro, eu preciso saber o centro da imagem, pois eu vou rotacion-la tendo este ponto como eixo. Isto pode ser feito com o mtodo getWorldCenter(),
do objeto Body. S que as coordenadas precisam ser normalizadas (inverter
y e aplicar a escala). Depois, eu preciso saber o retngulo do objeto, e,
para isto, uso o nosso conhecido mtodo criarRetangulo().
Antes de desenhar a imagem (dentro do retngulo), eu aplico uma transformao configurao do Graphics2D, mandando rotacionar todos os desenhos de acordo com o ngulo fornecido e tendo a coordenada do centro da
imagem como eixo. importante guardar a transformao original ANTES
de mandar rotacionar, caso contrrio, TODOS os desenhos sero rotacionados. Note que eu guardo a configurao original na varivel transform e,
depois de desenhar a imagem, eu volto tudo como estava antes, com o mtodo
setTransform.
O desenho da imagem bem simples: eu uso o mtodo drawImage, do
objeto Graphics2D, informando a imagem (precisei fazer um cast, pois eu
usei BufferedImage), o canto superior esquerdo, a largura e altura. O ltimo
parmetro uma instncia de ImageObserver, que ns no vamos utilizar
agora.
E, quando voc tecla a barra de espao, eu aplico uma fora nos dois eixos
(acelerao de 350 m/s2), na metade superior do dolo, fazendo-o pular girando no sentido horrio.
106
cenario,
0,
0,
larguraImagem,
Agora, para fechar com chave de ouro, tenho certeza que voc vai adorar
este exemplo:
Torque e impulso
At agora, s aplicamos foras aos corpos, porm, h outras maneiras de
alterar o seu momento (http://pt.wikipedia.org/wiki/Torque), como o impulso e
o torque.
O impulso mede a variao da quantidade de movimento de um corpo.
o resultado da aplicao de uma fora em um intervalo de tempo. Se a fora aplicada constante durante todo o tempo, o clculo simples: I = F.t,
porm, se a fora varivel, o clculo bem mais complexo. No Box2D,
aplicamos impluso com os mtodos applyLinearImpulse() e applyAngularImpulse(). O primeiro aplica um impulso linear em um ou nos dois eixos, se
aplicado ao centro de massa do corpo, e o segundo, aplica um impulso angular, que provoca a rotao do objeto.
108
110
Deteo de coliso
Ok, j sabemos movimentar bem os objetos, porm, como saber quando
eles colidiram? Afinal, muitos games de ao dependem desta informao
para acumular pontos, destruindo inimigos, ou mesmo para saber quando o
heri foi atingido. No Box2D podemos saber isto criando uma classe que implementa a interface ContactListener (C++: b2ContactListener).
Esta interface possui alguns mtodos de Callback que podemos utilizar
para saber informaes sobre a coliso:
Begin Contact: dois corpos comearam a se sobrepor. Esta rotina s
pode ser invocada de dentro do loop do Box2D (Step);
End Contact: dois corpos deixaram de ter contato. Esta rotina pode
ser chamada de fora do loop do Box2D. Por exemplo, voc removeu
um dos objetos que estava sobre o cho;
Pre-solve: esta chamada acontece depois que houve a coliso, porm
antes de sua resoluo, ou seja, do reposicionamento dos objetos;
Post-solve: acontece depois que houve a coliso e ela foi processada.
O importante que o Box2D no deixa voc fazer certas operaes enquanto estiver dentro do processamento do mtodo step(). Por exemplo,
voc poderia destruir um objeto, causando um problema para o processamento
interno dele. A maneira que o Box2D recomenda voc armazenar todos os
dados de coliso que lhe interessam e process-los aps a execuo do mtodo
step().
O exemplo ContactBox2D (...\Codigo\ContactBox2D\contactbox2d.
zip) mostra um bom exemplo de deteo de coliso.
112
this.id = id;
@Override
public void beginContact(org.jbox2d.dynamics.contacts.
Contact c) {
DadosCorpo corpoA = (DadosCorpo) c.getFixtureA().
getBody().getUserData();
DadosCorpo corpoB = (DadosCorpo) c.getFixtureB().
getBody().getUserData();
if (corpoA.id >=4 && corpoB.id >= 4) {
atingiu = true;
}
}
@Override
public void endContact(org.jbox2d.dynamics.contacts.
Contact arg0) {
}
@Override
public void postSolve(org.jbox2d.dynamics.contacts.
Contact arg0,
ContactImpulse arg1) {
}
@Override
public void preSolve(org.jbox2d.dynamics.contacts.
Contact arg0,
Manifold arg1) {
}
}
Juntas ou junes
Em fsica mecnica, temos o conceito de graus de liberdade, que o nmero de parmetros independentes para obter a posio (estado) de um corpo.
Em um plano (espao bidimensional), temos trs graus de liberdade: deslocamento horizontal (abscissas), vertical (ordenadas) e rotao (em um eixo
perpendicular ao plano).
114
Uma junta (ou juno) uma conexo entre dois corpos, que limita seus
graus de liberdade. Por exemplo, se pregarmos um retngulo em um quadro de
avisos, com um alfinete bem no centro, ele poder girar em torno do alfinete,
mas no poder se mover.
Existem vrios tipos de juntas:
Rotacional;
Prismtica
Cilndrica;
Esfrica;
Engrenagem;
Mas vamos nos concentrar nos tipos de juntas que o Box2D permite criar,
usando a sua prpria nomenclatura:
Distance joint (juno de distncia): interliga dois corpos, fazendo
com que a distncia entre eles seja sempre constante. Exemplo: dois
corpos ligados por um cano;
Revolute joint (juno rotacional): quando dois corpos compartilham um ponto em comum, podendo girar um em relao ao outro,
em torno do ponto comum. Exemplo: sobrepomos dois retngulos
de papel (no totalmente) e os atravessamos com um alfinete;
Prismatic joint (juno prismtica): os corpos esto unidos, mas
no podem girar um com relao ao outro, porm, podem deslizar
sobre a junta. difcil pensar em um exemplo elucidativo, mas vamos tentar... Imagine atravessar dois pedaos de madeira com uma
barra quadrada, mas que deixe folga para que os pedaos de madeira
deslizem sobre a barra, embora no possam girar;
Pulley joint (polia): os corpos esto ligados a uma polia (ou roldana). Exemplo: imagine um varal de roupas de prender no teto. Ao
afrouxarmos uma das cordas, a parte do varal ligada a ela desce, e ao
puxarmos, a referida parte sobe;
Gear joint (engrenagem): liga duas juntas (prismticas ou rotacionais), permitindo que o movimento de uma afete o da outra. Exemplo: um crculo com junta rotacional ligado a um retngulo com junta prismtica (ligada a um plano);
Mouse joint: conecta um ponto em um corpo a um ponto no mundo
Box2D, geralmente, determinado pelo clique do Mouse;
Wheel joint (ex Line Joint): conecta um ponto de um corpo a uma
linha de outro corpo. Exemplo: a suspenso de um carro. A roda gira
e tambm se move para cima e para baixo, na linha de suspenso;
Rode o exemplo, clique com o mouse para iniciar a simulao. Voc ver
que os corpos caem juntos, como se estivessem ligados por algo rgido, como
um cano, porm, que permite rotao nas pontas. Clique algumas vezes com a
barra de espao e veja os objetos pularem e rodarem em torno dos seus pontos
de acoragem (o centro de massa).
H poucas diferenas entre este cdigo e os anteriores. Para comear, criamos uma junta entre os dois corpos, especificando tambm o ponto de ancoragem (onde a junta se prende) entre eles:
116
Eu desenho uma linha entre os centros dos dois corpos, devidamente normalizados para a minha janela grfica.
Ao teclar barra de espao, eu aplico uma fora ao quadrado:
Vec2 forca =
new Vec2(1000.0f, 1000.0f * quadrado.getMass());
Vec2 posicao = quadrado.getWorldCenter();
quadrado.setAwake(true);
quadrado.applyForce(forca, posicao);
118
(int)cdCorpoB.y);
120
122
A classe Body tem o mtodo getJointList(), que retorna uma lista encadeada (no uma estrutura Java!) apontando para a lista de junes. A varivel next indica a prxima juno do objeto. O mtodo getJointList()
retorna uma instncia de JointEdge, que um elemento desta lista encadeada. Cada JointEdge possui vrios ponteiros, incluindo o other, que para
o outro corpo da juno.
Resumindo, eu s desenho as linhas de juno enquanto elas existirem.
E, para fechar, quando a bola atinge um quadrado, eu guardo qual foi o
atingido. Depois, no mtodo update(), eu removo uma de suas junes:
if (atingiu) {
if (tijoloAtingido != null) {
Joint junta = tijoloAtingido.getJointList() ==
null ? Null :
tijoloAtingido.getJointList().joint;
if (junta !=null) {
world.destroyJoint(tijoloAtingido.
getJointList().joint);
}
}
atingiu = false;
}
Note que pode ocorrer coliso vrias vezes, ento, eu tenho que testar se
ainda existem junes naquele objeto.
Se voc rodar a simulao vrias vezes, ver que a parede vai se quebrando
aos poucos, com alguns segmentos ainda inteiros. Isto devido ao fato de eu
ter usado Weld joint. Seno, ela ficaria molenga, como o exemplo do verme.
Se eu no tivesse removido as juntas na coliso, a parede se dobraria e inclinaria, mas os tijolos continuariam juntos.
Box2D no Android
Vamos eleger o Android como nossa primeira plataforma, para comear.
Em primeiro lugar, quase tudo que fizemos em Java serve, incluindo o Game
Loop.
Ok. Vamos escolher o nosso exemplo ImageRotation, cuja classe
JogoDeBola simula uma bola sendo chutada em um cenrio com cho e
duas paredes. Eis como o jogo dever rodar no Android:
124
Antes de mais nada, deixe-me esclarecer que, mesmo sendo feito em Java,
o jogo no pode rodar diretamente no Android. A API de interface de usurio
(GUI) completamente diferente do Javax / Swing, logo, no d nem para
pensar em rodar o jogo como est. Vamos ter que adapt-lo para funcionar
como uma aplicao Android.
O cdigo-fonte do exemplo est em: ...\Codigo\Android\exemplobox2d.zip.
Neste livro, eu pretendo me concentrar apenas nas ferramentas e tcnicas
para desenvolvimento de games para Android e iOS, logo, no faz parte do
escopo ensinar a desenvolver aplicaes para estes dispositivos. Se voc no
conhece programao para Android, eu recomendo duas fontes que podem te
ajudar:
1. Meu outro livro: Mobile Game Jam (www.mobilegamejam.com)
que possui uma boa introduo programao Android;
2. O curso de programao Android do meu portal: The Code Bakers
(http://www.thecodebakers.org);
Para comear, crie um projeto Android. Voc pode utilizar o target que desejar, mas cuidado para no usar caractersticas mais avanadas, pois a maioria dos dispositivos ainda Android 2.3.3 (API 10).
Ns vamos desenhar diretamente na tela, e queremos que a aplicao rode
em tela cheia, na orientao Landscape (deitada). Ento, para comear,
altere a definio da sua Activity, dentro do AndroidManifest.xml, para
acrescentar o atributo android:screenOrientation:
<activity
android:name=com.obomprogramador.box2d.android.exemplo.
MainActivity
android:label=@string/app_name
android:configChanges=orientation
android:screenOrientation=landscape>
Isto determina a orientao da sua Activity na tela e landscape significa Paisagem, ou orientao deitada. E tambm devemos impedir que o
dispositivo pare a Activity ao ser rotacionado. Primeiramente, informamos
o atributo android:configChanges, que informa ao Android que nossa Activity quer ser informada caso o dispositivo mude sua orientao. E temos que
sobrescrever o mtodo onConfigurationChanged():
@Override
public void onConfigurationChanged(Configuration newConfig) {
newConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
super.onConfigurationChanged(newConfig);
}
Uma View precisa ser redesenhada sempre que for considerada invlida,
o que pode acontecer devido a vrios motivos. E ns vamos invalidar nossa
View aps cada atualizao do mundo Box2D. Agora, precisamos informar
nossa Activity que uma instncia de GameView ser o contedo a ser
apresentado na tela. Fazemos isto no mtodo callback onCreate():
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gView = new GameView (this.getApplicationContext());
gView.setOnTouchListener(this);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.
FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(gView);
initComponents();
}
126
precisamos detectar quando a GameView foi tocada, logo, ns passamos a instncia da Activity para ela (a Activity precisa implementar OnTouchListener.
Depois, ns desabilitamos o ttulo da janela e a marcamos como Fullscreen, logo, nem aquela barra com sinal, hora e bateria aparecer.
Finalmente, usamos o mtodo setContentView() para informa Activity
qual instncia de View fornecer o contedo para a tela. Normalmente, ns
usamos uma view definida em XML (Layout), mas no o nosso caso.
Para terminar, chamamos o nosso conhecido mtodo initComponents(),
que inicializa os principais componentes da animao:
private void initComponents() {
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager)
getApplicationContext().getSystemService(Context.
WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;
alturaImagem = screenHeight;
larguraImagem = screenWidth;
int raioReal = Math.round((larguraImagem * bolaPercentScreen)
/100);
fatorEscalaVisual = raioReal;
Options opc = new Options();
opc.inDither = true;
opc.inScaled = false;
opc.inPreferredConfig = Bitmap.Config.ARGB_8888;
fundo = BitmapFactory.decodeResource(getResources(), R.drawable.
cenario2, opc);
fundo = Bitmap.createScaledBitmap(fundo, larguraImagem,
alturaImagem, true);
imagemBola = BitmapFactory.decodeResource(getResources(),
R.drawable.bola, opc);
imagemBola = Bitmap.createScaledBitmap(imagemBola, (int)(10 *
fatorEscalaVisual),
(int)(10 * fatorEscalaVisual), true);
paint = new Paint();
alturaMundo = alturaImagem / fatorEscalaVisual;
larguraMundo = larguraImagem / fatorEscalaVisual;
initBox2D();
}
128
return false;
Ao invs de chamar o mtodo redesenhar(), ns mandamos nossa GameView se autoinvalidar. Ns no usamos a tcnica de Double buffering
porque resultaria em flicker (piscadas) na execuo do game. Se voc quiser, pode usar uma instncia de SurfaceView, ao invs de View como
sua GameView. E, para evitar problemas de concorrncia, ns no podemos
simplesmente invalidar a view, pois este cdigo est sendo executado por um
Thread diferente do principal e no pode ter acesso s estruturas de dados da
130
gx.drawBitmap(fundo, 0, 0, null);
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setTextSize(14);
if (simulando) {
paint.setColor(Color.BLUE);
gx.drawText(rodando, 10, paint.getTextSize()
+ 3, paint);
}
else {
paint.setColor(Color.RED);
gx.drawText(parado, 10, paint.getTextSize()
+ 3, paint);
}
Vec2 centroImagem = normalizarCoordenadas(bola.
getWorldCenter());
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
Retangulo rect = criarRetangulo(bola);
float angulo = rect.angulo * 57.2957795f;
gx.save();
gx.rotate(angulo, centroImagem.x, centroImagem.y);
gx.drawBitmap(imagemBola, rect.x, rect.y, null);
gx.restore();
}
132
Cuidado com o emulador Android! O emulador uma mquina virtual, que executada dentro do seu computador, logo, possui uma srie de
restries. Uma delas a capacidade de processamento. Em outras palavras,
sempre que puder, desenvolva seu game rodando em um dispositivo real (d
at para depurar), e no no emulador. Este mesmo programa em um emulador
roda cerca de 1/3 mais lento do que no aparelho e no adianta mudar FPS nem
intervalo de Game loop.
Box2D no iOS
Usar o Box2D no iOS mamo com acar, j que podemos inserir
cdigo C e C++ diretamente nos arquivos-fonte em Objective C. Ento, no
precisamos do JBox2D e podemos baixar diretamente a ltima verso do
Box2D C++ no site: www.box2d.org.
O cdigo-fonte do exemplo est em: ...\Codigo\iOS\Box2DTestbed.zip.
Preparando o projeto
Para comear, crie um projeto de aplicao iOS no Xcode. Mais uma vez,
neste livro eu no pretendo ensinar a fazer isto, mas, se quiser, pode ler o meu
outro livro Mobile Game Jam (www.mobilegamejam.com) para ver como
se faz.
Aps baixar e descompactar o Box2D_vX.X.X, copie a subpasta Box2D
para dentro do seu projeto no Xcode. Temos que alterar algumas coisas no
projeto do Xcode. Para comear, os arquivos do Box2D so feitos em C++ e
ns vamos usar elementos definidos neles dentro do nosso cdigo Objective
C, ento, temos que informar ao compilador que nosso cdigo misto. Podemos fazer isto de duas formas: mudar a extenso das implementaes de .m
para .mm, ou mudar a opo do prprio compilador, o que mais fcil. Para
mudar a opo do compilador:
1. Clique na raiz do projeto;
2. Clique em Targets;
3. Selecione a aba Build Settings, na janela do meio;
4. Procure a opo Compile sources as... e mude para Objective-C++;
Precisamos informar ao compilador onde procurar por arquivos de cabealho .h. Ns usamos a diretiva #import para incluir arquivos header.
Quando queremos usar nossos prprios arquivos, os informamos entre aspas
duplas. Porm, quando queremos usar alguma biblioteca, usamos entre os caracteres < e >. Veja o exemplo:
134
A aplicao roda muito bem no iOS e a programao basicamente a mesma da verso Android. Vamos nos concentrar nas diferenas.
Para comear, temos que determinar o tipo de projeto. Eu escolhi Universal, logo, ele rodar no iPhone e iPad. Eu criei uma instncia de UIView
chamada B2DTBGraphicView, que dever ser a classe da view nos dois
arquivos de Storyboard: o do iPhone e o do iPad. Para isto, s selecionar
a aba identity e mudar a classe.
action:@
136
gameRodando = NO;
[self startGameLoop];
138
140
self.bolaImagem);
CGContextRestoreGState(context);
Ento:
a = cosseno do ngulo da rotao (cos(a));
b = seno do ngulo da rotao (sen(a));
c = -1 x sen(a);
d = cos(a);
tx = x x * cos(a) + y * sen(a);
ty = y x * sen(a) y * cos(a).
Aplicando nas duas equaes:
x = cos(a) * x + (-1 * sen(a)) * y + (x x * cos(a) + y * sen(a));
y = sen(a) * x + cos(a) * y + (y x * sen(a) y * cos(a));
Calma! Antes de jogar o livro pela janela, eu quero dizer que esta uma
receita de bolo, que voc pode usar para qualquer rotao de imagem! Eu
s quero dar uma breve explicao de como isto feito. Se quiser entender a
matemtica por trs desta operao, veja em:
Rotao: http://en.wikipedia.org/wiki/Rotation_(mathematics)
Translao: http://en.wikipedia.org/wiki/Translation_(geometry)
No Android, ns podemos rotacionar o contexto especificando um ponto
de eixo, que pode ser o centro da imagem. No iOS, no existe isso. Temos
que combinar uma rotao com translao. Eu estou deslocando a origem (o
eixo de rotao) para o ponto especificado nas variveis tx e ty, calculado
com base no ngulo que eu desejo rotacionar a imagem. Depois, eu especifico
a rotao que eu desejo aplicar. O ngulo em Radianos, obtido diretamente
do Box2D.
Como eu disse, uma receita de bolo, ou seja, sempre que voc precisar
rotacionar uma imagem em torno do seu centro, use a mesma transformao,
s variando, obviamente, as coordenadas da imagem e o ngulo. Note que o
ponto inicial do retngulo o canto superior esquerdo.
Para concatenar a minha matriz de transformao com a matriz normal do
contexto, eu uso o mtodo:
CGContextConcatCTM(context, transform);
Eu preciso salvar e restaurar a matriz do contexto, caso contrrio, todos os
desenhos sero afetados pela transformao que eu criei. Eu fao isso com os
mtodos:
CGContextSaveGState(context);
CGContextRestoreGState(context);
142
Captulo 6
Renderizao com
OpenGL ES 2.0
Certamente, voc j ouviu falar sobre OpenGL, e aposto que tambm j
ouviu que muitos games foram desenvolvidos com ele, como:
Doom 3
Half life
Minecraft
Quake
Second Life
StarCraft
Portal
World of
Warcraft
144
OpenGL ES
A especificao OpenGL ES foi criada para sistemas embarcados, como
dispositivos mveis e esta API pode ser utilizada tanto em sistemas baseados
em Android como iOS. Atualmente, existem duas verses implementadas nos
dispositivos: OpenGL ES 1.x e OpenGL ES 2.0, com suporte diferenciado:
Android com nvel de API inferior a 8 (Froyo): OpenGL ES 1.x;
Android com nvel de API igual ou superior a 8: OpenGL ES 1.x e
2.0 (em hardware que suporte OpenGL ES 2.0);
iOS verso inferior a 3: OpenGL ES 1.x;
iOS verso igual ou superior a 3: OpenGL ES 1.x e 2.0 (o hardware
tem que ser: iPhone 3GS ou superior ou iPad 2.0 ou superior);
Ambas as verses esto disponveis na maioria dos dispositivos modernos
(Android >= Froyo e iOS >= iPhone 3GS) e so utilizadas at hoje.
A principal diferena entre elas quanto ao Pipeline de renderizao. As
GPUs mais antigas possuam sistemas dedicados para funes especficas, por
exemplo: transformao e iluminao, que podiam ser configurados atravs
das funes da API OpenGL. Para estas GPUs mais antigas, a opo utilizar
a verso 1.x. As GPUs mais modernas possuem processadores de uso geral,
Captulo 6 - Renderizao
que podem executar vrias funes e podem ser programados. Para estas
GPUs, a recomendao utilizar a verso 2.0 (ou 3.0 em breve!)
Neste livro, vamos nos concentrar na verso da OpenGL ES 2.0, que est
presente na maioria dos dispositivos modernos e vamos ver apenas imagens
em 2D, que esto dentro do escopo, porm, os conceitos se aplicam tambm
s imagens 3D.
A API OpenGL ES permite um controle total sobre como queremos que
nossas imagens sejam renderizadas. Podemos at criar programas para sobreamento (Shaders), especificando como a luz incidir sobre os vrtices do
nosso desenho, e estes programas sero executados na GPU, tornando o processamento do Game loop muito mais rpido.
Mas isto tem um custo: complexidade! A API OpenGL tudo, menos fcil
de aprender. complexa, com conceitos diferentes do que estamos acostumados, e a curva de aprendizado longa. A maioria das funes pode ser
feita de diversas maneiras e, dependendo da escolha, teremos melhor ou pior
performance.
Realmente, o uso de OpenGL representa um risco considervel em projetos de games e sua curva de aprendizado no deve ser menosprezada.
Um programador iniciante em plataforma Android consegue criar um programa simples, que desenhe uma imagem em uma view (usando onDraw())
em questo de minutos. Um programador experiente em Android consegue
criar um programa que desenhe a mesma imagem com OpenGL ES em questo de horas!
Afinal, eu devo ou no usar OpenGL no meu game?
Para mim, existem trs vantagens em utilizar OpenGL: API padronizada,
maior controle e uso de acelerao grfica. Devemos considerar as trs vantagens em conjunto. Por exemplo, ao escrever a renderizao de um Game
usando OpenGL ES, fica mais fcil port-lo de Android para iOS, por exemplo, afinal a API est presente em ambas as plataformas. Se eu aprender a usar
OpenGL ES, no preciso estudar detalhadamente os mecanismos de desenho
de cada plataforma.
Agora, as outras vantagens dependem mais da complexidade e dinamismo
do game. Se voc vai criar um game 3D, com certeza deveria investir em
OpenGL ES, porm, mesmo em games 2D, o uso de OpenGL ES pode trazer
os benefcios de padronizao, controle e desempenho esperados.
A maioria dos desenvolvedores de games mveis, tanto Android como
iOS, recomendam o uso do OpenGL ES, ao invs dos mecanismos normais
(Canvas e Quartz).
146
Ao usar OpenGL ES, transferimos algum processamento para a GPU, liberando recursos da CPU para outras tarefas, como o clculo de fsica do game,
por exemplo. Porm, no espere que isto resolva todos os problemas de lag
do seu game. Existem atitudes que podem ser tomadas ANTES de considerar
o uso ou no de OpenGL ES, como a criao de um Game Loop consistente, o
gerenciamento eficiente de memria, pr-carga de imagens etc.
Fundamentos
Quando desenhamos com mecanismos nativos, geralmente ns obtemos
um contexto grfico e utilizamos funes da API para desenhar na tela, por
exemplo:
Android: no mtodo onDraw() obtemos um Canvas e usamos o
mtodo drawBitmap();
iOS: no mtodo drawRect:(CGRect)rect obtemos o contexto grfico atual (UIGraphicsGetCurrentContext) e desenhamos usando a
funo CGContextDrawImage;
Daqui para a frente, eu me referenciarei ao motor (engine) grfico OpenGL, logo, vou passar a escrever o OpenGL, para diferenciar da especificao OpenGL ES. O motor uma implementao da especificao OpenGL ES 2.0.
No OpenGL (OpenGL ES 2.0), o processo diferente. Para comear, ns
criamos programas de sombreamento (Shaders), alm de vetores de vrtices e
textura, depois criamos as matrizes de transformao e projeo e mandamos
a GPU renderizar a imagem.
A primeira coisa que temos que aprender como o OpenGL enxerga as
coordenadas das imagens, que um plano cartesiano tridimensional, onde o
eixo das ordenadas cresce do centro para cima, ao contrrio do plano utilizado nas telas dos dispositivos mveis (o y cresce de cima para baixo).
Captulo 6 - Renderizao
148
4
8
16
32
64
128
256
512
1024
2048
4096
Cada dispositivo tem um tamanho mximo de textura. Em plataformas Android, podemos usar o seguinte cdigo para verificar isso:
int [] tamanho = new int[1];
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE,
tamanho,0);
Captulo 6 - Renderizao
No meu dispositivo Android, um Smartphone LG p500, ele retornou o valor 4.096. Mas o que isso quer dizer? Afinal, uma imagem de 64 x 64 pixels
j atinge esse valor... Parece que esta informao o maior tamanho que uma
dimenso de textura pode ter (altura ou largura).
Em dispositivos iOS, podemos usar os comandos:
int tamanhoMaximo;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &tamanhoMaximo);
Na verdade, as coordenadas de textura indicam textels ao invs de pixels. Um textel no tem tamanho fsico definido. Um textel a cor que determinado pixel da imagem ter, de acordo com sua posio no mapa de textura.
Os valores das coordenadas s e t da textura precisam ser aplicados aos
vrtices da imagem (de acordo com sua forma). Ento, saberemos qual a parte da imagem que ser mapeada naquele exato vrtice. O OpenGL vai calcular
150
Buffers
O OpenGL sempre utiliza buffers para renderizar as imagens e ns tambm
nos comunicamos com ele atravs deste tipo de estrutura. Como vimos anteriormente, temos que criar dois vetores float (no OpenGL, as coordenadas so
sempre nmeros reais): um com as coordenadas do quadrado que queremos
texturizar e outro com as coordenadas de aplicao da textura. Porm, para
passar essas informaes para a GPU, o OpenGL utiliza buffers, que so reas
de memria contnua.
O problema que a especificao muito aberta e podemos passar vrios
tipos de informao dentro de buffers:
Posies: coordenadas de cada vrtice (x, y, z) do nosso polgono;
Cores: a intensidade (entre 0 e 1) de cada cor (RGBA) em cada
vrtice do polgono;
Normais: coordenadas de vetores normais (perpendiculares) a
cada vrtice do nosso polgono. So utilizados para calcular como a
luz vai refletir em cada plano;
Texturas: a cobertura de textura (s, t) em cada vrtice do nosso
polgono;
E tem mais! Podemos compactar mais de uma informao em um mesmo
buffer (Packed Buffers).
Para no complicar, nos exemplos que eu vou mostrar eu uso buffers separados e no especifico as cores nem os normais. Isto porque estou ensinando
o OpenGL como ferramenta para criao de jogos 2D (inicialmente) e estas
duas informaes no so imprescindveis.
Buffer de posies
Para comear, temos que criar um vetor de posies dos vrtices do nosso
polgono, depois, ns o transformaremos em um Buffer. O importante que
este vetor tem que ser criado em uma ordem especfica:
Canto inferior esquerdo;
Captulo 6 - Renderizao
Por que nesta ordem? Isto est relacionado maneira como o OpenGL desenha as imagens. Se forem mais de dois vrtices, ele vai desenhar tringulos
(explicaremos mais adiante).
Buffer de textura
Agora, como vamos pintar o quadrado com uma textura, temos que especificar o vetor que indicar como a textura ser aplicada aos vrtices. Veja
um exemplo em Android:
static float textureCoords[] = {
};
0.0f,
0.0f,
1.0f,
1.0f,
1.0f,
0.0f,
1.0f,
0.0f
//
//
//
//
canto
canto
canto
canto
superior
inferior
superior
inferior
esquerdo
esquerdo
direito
direito
152
vetores) para dentro dos VBOs com a funo glBufferData(). Para usar o
buffer, s precisamos invocar a funo glBindBuffer().
Programas de sombreamento
A especificao OpenGL ES 2.0 nos permite criar programas de sombreamento (ou Shaders) para serem executados pela GPU. Estes programas
so criados usando a linguagem GLSL (http://www.khronos.org/registry/gles/
specs/2.0/GLSL_ES_Specification_1.0.17.pdf). Para renderizar e texturizar nossa
imagem, precisamos criar pelo menos dois Shaders: Vertex Shader e Fragment Shader.
Os Shaders so invocados no pipeline do OpenGL ES para determinar como
cada vrtice e pixel ser renderizado. Voc pode ver uma imagem do pipeline
do OpenGL no site do grupo Khronos: http://www.khronos.org/opengles/2_X/.
O OpenGL trabalha com comandos primitivos de desenho e dados de formatao de pontos. Os comandos primitivos de desenho, eis os principais:
Points (pontos): uma srie de pontos individuais;
Line Strips (tiras de linhas): uma srie de uma ou mais linhas
concectadas, sendo os vrtices os pontos de ligao;
Line Loops (polgonos): So semelhantes a Line Strips, exceto
que um segmento adicional criado para ligar o ltimo ao primeiro
vrtice;
Triangle strips (tiras de tringulos): uma srie de tringulos
conectados por um lado compartilhado;
Triangle fans (ventilador triangular): semelhante ao Triangle
Strip, s que todos compartilham o vrtice inicial;
Qualquer figura que voc queira desenhar ter que se encaixar em uma das
primitivas. Eu estou usando sempre Triangle Strips, da o ordenamento dos
meus vrtices no buffer.
Bem, aps processar a primitiva, o OpenGL ES vai invocar o Vertex Shader, que serve para mapear a posio de cada vrtice do nosso polgono nas
coordenadas da tela. Ns temos que fornecer um Vertex Shader para que o
OpenGL utilize. Para isto, ns criamos um texto contendo os comandos em
linguagem GLSL, o compilamos e depois o linkeditamos junto com o Fragment Shader.
O Fragment Shader processado mais no final do pipeline e trabalha
sobre cada pixel a ser renderizado, de modo a determinar sua cor, iluminao
e textura. Ele tambm deve ser criado como um texto em GLSL, compilado e
linkeditado junto com o Vertex Shader.
Captulo 6 - Renderizao
154
Matrizes
Apesar do OpenGL utilizar um plano cartesiano tridimensional, as telas
dos dispositivos so 2D, logo, temos que realizar uma transformao nas
coordenadas para criar uma projeo 2D de uma imagem 3D. Ns j falamos
um pouco sobre isso no captulo sobre fundamentos. S para relembrar, vamos repetir a imagem.
Captulo 6 - Renderizao
O OpenGL sempre trabalha com matrizes (da lgebra linear) para calcular
posio, projeo e viso. Ele multiplica as coordenadas de cada vrtice pelo
produto das trs matrizes, de modo a obter a coordenada final.
Eu no pretendo entrar em muitos detalhes sobre matrizes, porm importante notar que todas as matrizes so 4 x 4 e as coordenadas que usamos so
chamadas de coordenadas homogneas, nas quais um vrtice representado
pelo conjunto: (x, y, z, w). O w o divisor dos valores das outras coordenadas, permitindo ajustar a posio relativa dos objetos em profundidade. Ns
no vamos usar diretamente as coordenadas homogneas neste livro, porm,
as matrizes devem prever isso (4 X 4).
No OpenGL, sempre que necessitarmos mover, redimensionar ou girar um
objeto, ns evitamos alterar o vetor de coordenadas. Simplesmente, criamos
uma matriz de transformao do modelo ou matriz modelo, que contm as
primeiras transformaes a serem aplicadas ao nosso modelo (definido pelas
coordenadas de vrtice). Se quisermos manter nosso objeto no mesmo local,
sem alterar tamanho e ngulo de rotao, podemos inicializar a matriz modelo
com a matriz identidade, que no afeta as coordenadas dos vrtices ( a mesma coisa que multiplicar por 1).
Alm da matriz modelo, existe a matriz de viso ou de cmera (view matrix), que indica como o observador ver a renderizao. Como a matriz modelo sempre multiplicada pela de cmera, normalmente tratamos o produto
como Model-View Matrix (matriz de modelo e viso). Para projetar a imagem 3D em um plano 2D (a tela), usamos uma terceira matriz, que chamada
de matriz de projeo, e tambm multiplicada pelo produto das outras duas.
A coordenada final ser calculada pela multiplicao da coordenada do vrtice
pela matriz produto, resultante da multiplicao das trs (modelo, cmera e
projeo).
T x = A x
()
Onde:
x : vetor coluna contendo as coordenadas do ponto;
T : funo de transformao linear;
A : matriz de transformao linear;
Desta forma, o OpenGL calcula as novas coordenadas dos pontos da imagem em seu pipeline, pois ns informamos a matriz combinada (projeo e
cmera) em nosso Vertex Shader:
uniform mat4 uMVPMatrix;
attribute vec4 aPosition;
156
Captulo 6 - Renderizao
158
Captulo 6 - Renderizao
Eu modifiquei a imagem do dolo para parecer mais antiga, com uma rachadura direita, e feita de tijolos. Achei que ficou mais game assim. Note
que na figura existem dois dolos: um mais no alto e acima (cor dourada) e
outro mais embaixo e direita (avermelhado), que parece ser menor que o
primeiro, aparentando estar por detrs dele. Na verdade, ambos os dolos so
quadrados com mesmo tamanho, s que o segundo est com coordenada z
menor. Logo, a projeo perspectiva faz com que ele aparea menor que o
primeiro.
Este exemplo no executa animao alguma, apenas exibindo a imagem do
dolo na tela. Veremos como animar imagens mais adiante.
160
Aqui vale uma recomendao: conecte um dispositivo Android, com verso igual ou superior a 2.3, e desenvolva testando diretamente no aparelho.
No use o emulador.
O emulador Android s roda OpenGL, a partir da verso da plataforma
4.0.3 r2, e voc tem que indicar na configurao da AVD que vai utilizar a
GPU do sistema host (o seu desktop). Mesmo assim, ele vai rodar muito lento.
Eu no recomendo.
Entendendo a GLSurfaceView
No ambiente Android, podemos usar OpenGL ES 2.0 em Java (Dalvik)
ou em cdigo nativo, utilizando o NDK. Qual seria a vantagem de programar
diretamente em C++? Desempenho? Pode ser. Mas voc deve lembrar que,
desde a verso Froyo (2.2), o Android utiliza JIT (Just in time) para compilar
classes Dalvik em cdigo nativo, o que acelera muito a execuo das aplicaes. Uma das vantagens seria a utilizao direta de bibliotecas em C++,
como o OpenGL, e outra seria o provvel aumento de desempenho, que s
benfico em aplicaes muito especficas.
Hoje em dia, a maioria dos jogos em Android feita utilizando Java (Dalvik) mesmo, logo, vamos abordar apenas o uso de OpenGL nas aplicaes Java.
O Android disponibiliza algumas classes e interfaces bem teis para o uso
do OpenGL (pacote: android.opengl), entre elas:
GLSurfaceView: uma classe de view dedicada, com Thread prprio
para renderizao, que pode ser acoplada a uma instncia de GLSurfaceView.Renderer, para desenho na tela;
GLSurfaceView.Renderer: interface que determina o comportamento de um renderizador de imagens OpenGL;
GLES20: uma classe de convenincia, que encapsula as funes
C++ do OpenGL, oferecendo mtodos estticos para utilizarmos;
Matrix: uma classe de convenincia para lidarmos com as matrizes
de transformao (projeo e cmera), com vrios mtodos estticos;
Captulo 6 - Renderizao
));
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mView = new OpenGLBasico1View(getApplication(
}
setContentView(mView);
@Override
protected void onPause() {
super.onPause();
mView.onPause();
}
@Override
protected void onResume() {
super.onResume();
mView.onResume();
}
}
162
importante especificarmos qual a verso do contexto OpenGL que vamos utilizar, neste caso 2 significa OpenGL ES 2.0. Tambm instanciamos
a nossa classe de renderizao e passamos para a view.
Podemos tambm escolher como ser a renderizao: contnua ou sob
demanda, utilizando o mtodo setRenderMode(). Este mtodo recebe um
parmetro indicando o tipo de renderizao que desejamos. No caso da renderizao contnua (GLSurfaceView.RENDERMODE_CONTINUOUSLY,
default), a nossa classe de renderer ser invocada para renderizar a imagem de
forma contnua (loop). Se escolhermos renderizao sob demanda (GLSurfaceView.RENDERMODE_WHEN_DIRTY), a imagem s ser renderizada
se invocarmos o mtodo requestRender().
Bom, at agora molezinha... A mgica acontece mesmo na classe
de renderizao, que implementa a interface GLSurfaceView.Renderer, que
possui trs mtodos:
abstract void onDrawFrame(GL10 gl) : invocado quando necessrio redesenhar um novo frame, utilizando os VBOs (Vector
Object Buffers), texturas e matrizes;
abstract void onSurfaceChanged(GL10 gl, int width, int height)
: invocado quando o tamanho da imagem mudou, normalmente
quando o dispositivo rotacionado. Aqui, devemos recalcular a matriz de projeo;
abstract void onSurfaceCreated(GL10 gl, EGLConfig config)
: invocado quando a superfcie criada. Neste momento, ns inicializamos buffers, carregamos texturas e realizamos operaes de
inicializao que exigem um contexto OpenGL, como a compilao
de Shaders;
Bem, e agora? O que faremos? O trabalho praticamente todo feito na
nossa classe de renderizao. Eu sigo esta lista:
1. Criar os vetores de vrtices e textura, tomando cuidado com a orientao dos elementos;
Captulo 6 - Renderizao
A implementao
Vrtices e textura
A primeira coisa criar os vetores de vrtices e textura:
static float squareCoords[] = {
-2.0f, -2.0f, 0.0f, // canto inferior esquerdo
-2.0f, 2.0f, 0.0f, // canto superior esquerdo
2.0f, -2.0f, 0.0f, // canto inferior direito
2.0f, 2.0f, 0.0f // canto superior direito
};
static float squareCoords2[] = {
0.0f, -4.0f, -1.0f, // canto inferior esquerdo
0.0f, 0.0f, -1.0f, // canto superior esquerdo
4.0f, -4.0f, -1.0f, // canto inferior direito
4.0f, 0.0f, -1.0f // canto superior direito
};
164
superior
inferior
superior
inferior
esquerdo
esquerdo
direito
direito
Captulo 6 - Renderizao
Variveis auxiliares
Note que eu declarei algumas variveis que so ponteiros ou matrizes,
como:
private int mTextureID : identificador (handler) da textura no
OpenGL;
private FloatBuffer verticesQuadrado : buffer local para receber o
vetor de vrtices;
private FloatBuffer texturaQuadrado : buffer local para receber o
vetor de coordenadas de textura;
private int[] buffers = new int[2] : identificadores dos dois VBOs
que criaremos na GPU;
private float[] matrizProjecao = new float[16] : nossa matriz de
projeo;
private float[] matrizCamera = new float[16] : nossa matriz de
cmera;
private float[] matrizModelo = new float[16] : matriz normal do
modelo, ou seja, onde aplicamos movimento e rotao. Neste exemplo, ela sempre a matriz identidade, pois no desejamos mudar a
posio do objeto;
private float[] matrizIntermediaria = new float[16] : usada para
calcular a matriz final, que a multiplicao das outras;
private int programaGLES : identificador do programa Shader que
criamos. Ele contm o Vertex e o Fragment Shader compilados e
linkeditados;
private int muMVPMatrixHandle : identificador da matriz de
transformao que criamos e vamos passar para o OpenGL;
private int maPositionHandle : identificador do VBO de vrtices;
private int maTextureHandle : identificador do VBO de textura;
Inicializao dos buffers locais e carga da imagem
Sugiro que voc leia sobre as classes do pacote java.nio, especialmente
as classes ByteOrder, ByteBuffer e FloatBuffer, ou ento veja os exemplos de OpenGL do Android (http://developer.android.com/training/graphics/
opengl/shapes.html).
feita no construtor da classe. Primeiro, inicializamos um ByteBuffer
com o tamanho do vetor de vrtices (nmero de vrtices multiplicado pelo
tamanho de um float). Tambm informamos qual o byte order nativo
que estamos utilizando. Vamos criar dois buffers locais, um para cada dolo:
166
Captulo 6 - Renderizao
168
Captulo 6 - Renderizao
170
GLES20.GL_
GLES20.GL_
Captulo 6 - Renderizao
172
0,
esquerda,
direita,
Captulo 6 - Renderizao
GLES20.GL_
Cada dispositivo de vdeo (GPU) pode ter um determinado nmero de texturas ativas simultaneamente. O OpenGL determina isso atravs de Texture
Units. Neste caso, estou ativando a unidade de textura zero (GL_TEXTURE0) e estou associando o identificador (handler) da minha textura a ela. Isto
174
significa que a minha textura ser utilizada na prxima operao de renderizao. So duas texturas diferentes, ento, eu tenho que fazer isto para cada dolo.
Agora, tenho que indicar quais so os buffers que vou utilizar para renderizar os vrtices e aplicar a textura. Lembre-se que eles j esto na GPU, logo,
no passamos ponteiros para buffers locais. Tambm temos que habilitar os
vetores, o que far com que as coordenadas sejam passadas aos atributos dos
Shaders:
// Vrtices:
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]);
GLES20.glVertexAttribPointer(maPositionHandle, 3,
GLES20.GL_FLOAT, false,12, 0);
GLES20.glEnableVertexAttribArray(maPositionHandle);
// Textura:
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[1]);
checkGlError(glEnableVertexAttribArraymaPositionHandle);
GLES20.glVertexAttribPointer(maTextureHandle, 2,
GLES20.GL_FLOAT, false,
8, 0);
GLES20.glEnableVertexAttribArray(maTextureHandle);
Captulo 6 - Renderizao
Como eu no estou modificando a posio da imagem, estou criando minha matriz modelo com a matriz identidade (no altera nada nas coordenadas
dos pontos). Ento, eu a multiplico pela matriz de cmera e depois multiplico
o resultado pela matriz de projeo, obtendo a matriz de transformao final.
Eu repeti o clculo das matrizes para os dois dolos, embora isto no seja
necessrio neste caso, pois cada dolo usa matriz modelo identidade e as mesmas matrizes de cmera e projeo.
Ento, eu uso o mtodo glUniformMatrix4fv() para atribuir a matriz de
transformao ao uniform uMVPMatrix, que eu defini no Vertex Shader.
Assim, ele vai recalcular os vrtices de acordo com o meu modelo, cmera e
projeo, gerando as novas coordenadas.
E, para concluir, eu invoco o mtodo para desenhar a textura nos vrtices
transformados:
GLES20.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0,
squareCoords.length / 3);
checkGlError(glDrawArrays);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
Concluindo
Terminamos o exemplo! Se rodarmos, teremos a frustrante sensao de ver
imagens quadradas aparecendo na tela.Sem graa.Poderamos ter feito isso
176
Entendendo o GLKit
O iOS possui um framework chamado GLKit para desenvolver aplicaes baseadas em OpenGL ES. O GLKit inclui: funes, classes e outros tipos
de dados que facilitam o desenvolvimento de aplicaes mveis utilizando
o OpenGL ES. claro que voc no precisa usar o GLKit, mas altamente
recomendvel que o faa.
Captulo 6 - Renderizao
http://developer.apple.com/library/ios/#documentation/GLkit/Reference/GLKit_
Collection/Introduction/Introduction.html
As caractersticas mais importantes do GLKit que vamos usar so as classes: GLKViewController, GLKView, GLKBaseEffect e GLKTextureInfo.
GLKViewController
O GLKViewController possui vrias propriedades e mtodos interessantes
e j implementa um loop de renderizao, inicialmente baseado em 30 FPS, o
que pode ser mudado atravs da propriedade: preferredFramesPerSecond,
limitado a 60 FPS.
Ele possui alguns mtodos (delegados de GLKViewControler ou de
GLKView) que devemos sobrescrever, como:
- (void)viewDidLoad : devemos inicializar o contexto OpenGL,
indicar a nossa GLKView e carregar todas as informaes invariantes, como: VBOs, texturas etc;
- (void)dealloc : liberamos nossas estruturas alocadas na GPU
(VBOs, programa etc), e o prprio contexto OpenGL;
- (void)update : devemos atualizar nosso modelo pois um novo
frame ser renderizado;
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect :
devemos renderizar o frame;
Podemos usar o GLKViewController de duas maneiras: instanciando diretamente a classe e interceptando os mtodos em um delegate, ou criar
uma subclasse. Eu prefiro este ltimo mtodo, que utilizado pelo template
fornecido no Xcode (OpenGL ES Game). Alis, este template no cria uma
subclasse de GLKView, usando o View Controller como delegate dela.
GLKBaseEffect
Esta classe j fornece os Shaders necessrios e permite usarmos at duas
texturas em uma operao de desenho. Ela poupa muito trabalho, porm, se
quisermos, podemos fazer da mesma forma que o Android e gerarmos nosso
prprio programa e associarmos as texturas manualmente.
GLKTextureInfo / GLKTextureLoader
A classe GLKTextureLoader pode carregar texturas a partir de imagens em
arquivos, gerando instncias de GLKTextureInfo. Isto poupa todo o trabalho
de carga e definio de texturas.
178
Captulo 6 - Renderizao
A implementao
Ns vamos usar o mesmo Frustum do exemplo Android, com as mesmas
configuraes para matriz de cmera e de projeo.
Abra a implementao do View Controller e apague tudo, s deixando at
a linha em negrito:
//
// OGLB1ViewController.m
// OpenGLIOSBasico1
//
// Created by Cleuton Sampaio on 16/01/13.
// Copyright (c) 2013 Cleuton Sampaio. All rights reserved.
//
#import OGLB1ViewController.h
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
180
So exatamente os mesmos vetores que usamos na implementao Android, s que definidos em linguagem C. Na verdade, podemos salvar os vetores em arquivos e carreg-los, o que facilita mais ainda o desenvolvimento
em mltiplas plataformas. Se voc quiser realmente desenvolver games, pode
criar um framework seu, que carregue de arquivos as vrias configuraes do
OpenGL, como:
Coordenadas de vrtices de cada Game Object;
Coordenadas de texturas, associadas a cada Game Object;
Dados do Frustum;
Cdigo-fonte dos Shaders;
Captulo 6 - Renderizao
Isto diminuiria o cdigo-fonte da aplicao e reduziria o risco de divergncias entre as plataformas. O que voc ter que criar o cdigo que l e aplica
as configuraes, de acordo com a verso (Android ou iOS).
Depois, criamos uma extenso de classe para colocar nossas propriedades
e mtodos privados, dentro da implementao (*.m):
@interface OGLB1ViewController () {
GLuint _vertexBuffer;
GLuint _vertexBuffer2;
GLuint _textureBuffer;
GLKMatrix4 matrizCamera;
}
@property
@property
@property
@property
(strong,
(strong,
(strong)
(strong)
- (void)setupGL;
- (void)tearDownGL;
@end
Uma extenso de classe nos permite criarmos propriedades e mtodos internos, para uso apenas na implementao do View Controller. Comeamos
definindo variveis para armazenar nossos indicadores (handlers) dos buffers
remotos, alm da nossa matriz de cmera.
Depois, definimos como propriedades: o contexto OpenGL ES, o GLKBaseEffect, e duas variveis GLKTextureInfo para armazenarem nossas texturas.
Lembre-se de sintetizar os getters / setters com @synthesize.
Note que criamos dois mtodos internos: setupGL, que inicializa tudo, e
tearDownGL, que termina tudo.
Inicializando o View Controller
Agora, temos que implementar o mtodo - (void)viewDidLoad e inicializar nosso VC:
- (void)viewDidLoad
{
[super viewDidLoad];
182
[self setupGL];
Captulo 6 - Renderizao
184
[error
Captulo 6 - Renderizao
Outra medida importante desfazer o Bind Buffer, aps renderizar alguma coisa:
186
Atualizando o modelo
Uma das coisas legais do GLKViewController que ele j tem um loop de
atualizao e desenho embutido. O default 30 FPS, mas voc pode mudar
isso com a propriedade preferredFramesPerSecond. Quando for a hora de
atualizar o modelo, seu mtodo update ser invocado:
- (void)update
{
Captulo 6 - Renderizao
esquerda = -aspect;
direita = aspect;
baixo = -1.0f;
cima = 1.0f;
perto = 1.0f;
longe = 10.0f;
self.effect.transform.projectionMatrix
GLKMatrix4MakeFrustum(esquerda,
direita, baixo, cima, perto, longe);
//
188
on);
glBindBuffer(GL_ARRAY_BUFFER, _textureBuffer);
glVertexAttribPointer(GLKVertexAttribTexCoord0,
2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, BUFFER_
OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glDrawArrays(GL_TRIANGLE_STRIP, 0,
sizeof(squareCoords) / 3);
// Idolo 2:
self.effect.texture2d0.name = self.textureInfo2.
name;
self.effect.texture2d0.enabled = YES;
[self.effect prepareToDraw];
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer2);
glVertexAttribPointer(GLKVertexAttribPosition,
3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, BUFFER_
OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribPosition);
glBindBuffer(GL_ARRAY_BUFFER, _textureBuffer);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_
FLOAT, GL_FALSE, sizeof(GLfloat) * 2, BUFFER_OFFSET(0));
Captulo 6 - Renderizao
glBindBuffer(GL_ARRAY_BUFFER, 0);
Depois, temos que aplicar transformaes ao nosso objeto, como: reposicionar, rotacionar etc. Ns fazemos isso na matriz de modelo. Como no
estamos fazendo nada, simplesmente a inicializamos com a matriz identidade,
multiplicando-a pela matriz de cmera para formar a Model-View Matrix.
Note que informamos isso ao GLKBaseEffect:
GLKMatrix4 matrizModelo = GLKMatrix4Identity;
GLKMatrix4 matrizIntermediaria =
GLKMatrix4Multiply(matrizCamera, matrizModelo);
self.effect.transform.modelviewMatrix =
matrizIntermediaria;
Isto necessrio antes de qualquer desenho e temos que repetir sempre que
alterarmos alguma propriedade do Base Effect.
O resto igual verso Android: atribumos os VBOs e mandamos desenhar. Depois, repetimos tudo para o segundo dolo.
190
Achatando as coisas
A projeo perspectiva legal, mas muito mais complexa para trabalhar.
Se voc estiver criando Games 2D (ou mesmo 2.5D, com iluso tridimensional), no precisa disso tudo.
A projeo ortogrfica facilita muito o desenvolvimento. A forma da projeo um paraleleppedo, limitado pelos planos: perto, longe, cima, baixo,
direita e esquerda.
Implementao em Android
O exemplo est em: ..\Codigo\OpenGLAndroid\openglbasico2.zip.
Basicamente, mudamos a criao da matriz de projeo, que fica no mtodo onSurfaceChanged():
@Override
public void onSurfaceChanged(GL10 gl, int width, int
height) {
GLES20.glViewport(0, 0, width, height);
float perto = 1.0f;
float longe = 10.0f;
float baixo = -1.0f;
float cima = 1.0f;
float proporcao = (float) width / (float) height;
float esquerda = -proporcao;
float direita = proporcao;
Captulo 6 - Renderizao
192
Captulo 6 - Renderizao
Implementao em iOS
O exemplo est em: ..\Codigo\OpenGLiOS\OpenGLIOSBasico2.zip.
Ns mudamos a criao da matriz de projeo, que feita no mtodo
update:
- (void)update
{
glViewport(0, 0, self.view.bounds.size.width, self.
view.bounds.size.height);
float proporcao = fabsf(self.view.bounds.size.width
/ self.view.bounds.size.height);
float
float
float
float
float
float
perto = 1.0f;
longe = 10.0f;
baixo = -1.0f;
cima = 1.0f;
esquerda = -proporcao;
direita = proporcao;
self.effect.transform.projectionMatrix =
GLKMatrix4MakeOrtho(esquerda * 5, direita * 5,
baixo * 5, cima * 5, perto, longe);
}
Captulo 7
Framework de Fsica e
Renderizao
Agora, que j vimos o bsico de OpenGL ES nas duas plataformas
(Android e iOS), chegou o momento de juntarmos o Box2D e criarmos um
exemplo animado. Para simplificar, pretendo usar o mesmo exemplo que j
mostrei: a bola batendo nas paredes. Na prxima figura, vemos o resultado da
execuo do novo programa.
196
Um framework bsico
O objetivo deste livro fornecer um kit de ferramentas e tcnicas para
desenvolvedores independentes de games (indie game developers), de modo
a criarem games mveis para plataforma Android e iOS. Ento, minha nfase
facilitar o porte de games entre as duas plataformas, criando cdigo-fonte
parametrizvel externamente.
Ento, neste captulo, eu vou mostrar como integrar o Box2D com o OpenGL ES usando um framework bsico que permita ler todas as configuraes de
um arquivo XML. Com isto, ainda teremos Boilerplate code, mas no vamos
ter que copi-lo, pois vamos usar herana para transmitir o comportamento
para outras classes.
Eu tenho o meu prprio framework, que foi evoluindo ao longo do tempo,
mas acho que o melhor seria comear simples, mostrando como fazer as coisas bsicas e deixar que voc evolua seu framework como achar melhor.
Este framework bsico, podendo e devendo ser estendido por voc, de
acordo com suas necessidades. Eu mesmo vou fazer isso em exemplos posteriores. Desta forma, eu no explicarei cada mtodo do exemplo, nem vou
198
O arquivo do esquema XML est dentro dos dois projetos deste captulo
(no use o modelo dos captulos seguintes!):
Arquivo ZIP: ..\Codigo\OpenGLAndroid\openglavancado.zip;
Arquivo de esquema: modeloGame.xsd.
Eu criei um modelo de classes baseado no esquema XML, tanto no Android (Java) como no iOS (Objective C). Vamos analisar o diagrama de classes do modelo Android, s para entender o relacionamento dos elementos.
Um GameModel composto por Cenas, que indicam como as vrias cenas (ou nveis) do game devem ser configuradas.
Uma Cena contm:
Nmero: identificador da cena;
Textura de fundo: imagem background, que ser exibida como
fundo de tela (use sempre imagens com tamanho em potncia de 2;
FPS: taxa de frames por segundo para configurar a atualizao do
mundo Box2D;
ConfigFrustum: o nome frustum no apropriado, afinal, eu vou
usar projeo ortogrfica, mas deixei assim mesmo. So as configuraes da cmera e dos planos de corte, para que eu monte as
matrizes de transformao;
200
O valor de x a diagonal da tela, que vale (arredondando) 577. Na imagem, temos uma bola com dimetro a. Neste caso, para que a imagem da
Coordenadas e VBOs
Sistemas de coordenadas
Temos que entender como funcionar a integrao entre as coordenadas
Box2D, OpenGL ES e pixels da tela.
Para comear, eu optei por usar um plano cartesiano com origem no centro
da tela, e o valor das ordenadas na posio correta (aumentando para valores
acima de zero). O Box2D usa um plano de coordenadas semelhante, logo, posso tambm utilizar a mesma orientao para o OpenGL, lembrando de mudar
a matriz de projeo para que fique adequada.
Orientao da tela
Antes de mais nada, deixe-me falar sobre a orientao da tela. Em games
de ao, geralmente a orientao da tela landscape, ou seja: deitada. S
que a tela pode ficar deitada de duas maneiras: esquerda ou direita (posio
dos botes no dispositivo). Mudar a orientao pode ser problemtico, se o
seu game prev que os objetos se movam em trs graus de liberdade (movimento livre 2D), pois ao mudar a orientao, voc muda a posio dos objetos
que esto alinhados, tendo que refazer toda esta parte.
202
Eu sei. A figura confusa. Eu tentei vrias maneiras sem sucesso. Mas vou
tentar explicar: eu tenho uma tela com determinada altura e largura, em pixels.
O game vai ficar muito mais fcil se eu trabalhar minha projeo OpenGL de
acordo com o tamanho da tela, logo, as coordenadas da minha projeo devem
ser relativas a ela. Eis o comando de criao da Matriz, tanto em Android,
como em iOS:
Android:
Matrix.orthoM(matrizProjecao,
Matrix.orthoM(matrizProjecao,
Matrix.orthoM(matrizProjecao,
Matrix.orthoM(matrizProjecao,
Matrix.orthoM(matrizProjecao,
0, -width / 2,
0, width / 2,
0, -height / 2,
0, height / 2,
0, perto, longe);
iOS:
iOS: matrizProjecao = GLKMatrix4MakeOrtho(-larguraViewPort / 2,
larguraViewPort / 2,
-alturaViewPort / 2,
alturaViewPort / 2,
perto, longe);
204
matriz de modelo! Nos outros exemplos, nossa matriz modelo era sempre a
identidade, pois no movamos os objetos.
As texturas so carregadas das mesma maneira que fizemos nos outros exemplos.
basicamente o mesmo processo, considerando as diferenas: estou usando o Box2D C++, logo, a sintaxe deve ser levada em conta. A maior diferena
que a funo GLKMatrix4Rotate recebe o ngulo em radianos. Depois, eu
associo o produto das matrizes de cmera e modelo propriedade modelViewMatrix, da instncia de GLKBaseEffect. Por ltimo, associo a matriz
de projeo.
206
Renderizao do modelo
Como j vimos, no Android a classe GLSurfaceView.Renderer tem o
mtodo onDrawFrame(), invocado sempre que for necessrio atualizar a
view. Neste caso, eu renderizo primeiro a textura de fundo, utilizando um
truque para que o fundo cubra toda a tela:
1. Fao as matrizes de modelo, cmera e projeo iguais matriz
identidade;
2. Uso um VBO com coordenadas baseadas na unidade: (-1,-1), (-1,1),
(1, -1) e (1,1);
3. Desenho o fundo;
Assim, a imagem cobrir a tela toda.
Depois, eu renderizo cada GameObject que tenha textura:
1. Indico o identificador da textura que vou usar;
2. Informo qual o vetor de vrtices que vou usar. O identificador do
VBO foi carregado na propriedade VBOVertices, do GameObject;
3. Informo o MESMO vetor de textura (no h variao);
4. Ajusto a posio atual do centro do objeto, obtida do Box2D, na
matriz de modelo;
5. Ajusto o ngulo atual do objeto, obtido do Box2D, na matriz de
modelo;
6. Multiplico a matriz de modelo pela de cmera e a de projeo;
7. Desenho a textura do GameObject.
Eu sugiro que voc veja os dois exemplos, Android e iOS, identificando
estas etapas no cdigo.
Mipmaps
Um dos problemas mais irritantes que existe o aliasing, um efeito causado pela captura (amostragem) ou renderizao de um sinal, seja ele de udio
ou de vdeo. Seu efeito prtico o serrilhamento da imagem. Observe bem
as duas prximas figuras.
208
Note como na primeira imagem, tanto a bola como o cenrio, apresentam algum serrilhamento. Na segunda imagem, as duas figuras aparecem
perfeitas.
O aliasing acontece quando reduzimos uma imagem, porm existem tcnicas para suavizar o efeito. Basicamente, podemos desfocar as pontas de uma
imagem, dando a impresso de que ela est contnua. A criao de Mipmaps
uma das tcnicas para isto.
Basicamente, so criadas vrias verses da mesma imagem, com tamanho
e nvel de detalhe diferente, armazenadas em conjunto. No momento de renderizar a imagem, selecionada aquela cujo tamanho mais se aproxima do tamanho a ser utilizado. Mipmaps podem ser gerados manualmente ou automaticamente, porm ocupam maior memria. Estima-se que o uso de Mipmaps
aumente em 1/3 o tamanho necessrio para armazenar uma nica textura.
No OpenGL ES ns temos a opo de solicitar a criao dos Mipmaps com
a opo:
GL_TEXTURE_MIN_FILTER = GL_LINEAR_MIPMAP_NEAREST
Android:
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, hTextura);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR_MIPMAP_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_REPEAT);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, imagem, 0);
iOS:
NSDictionary * options = [NSDictionary
dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO],
GLKTextureLoaderOriginBottomLeft,
[NSNumber numberWithBool:YES],
GLKTextureLoaderGenerateMipmaps,
nil];
Uso do Framework
No Android
Mais uma vez, os arquivos de exemplo Android esto em:
\Codigo\OpenGLAndroid\openglavancado.zip.
Eu criei este framework para agilizar o desenvolvimento, promovendo o
reuso dos componentes. No exemplo Android, temos os pacotes:
com.obomprogramador.games.exemplo;
com.obomprogramador.games.openglavancado;
com.obomprogramador.games.xmloader;
Voc s precisa escrever as classes do pacote com.obomprogramador.games.exemplo, que so:
Activity do game;
Renderer do game (derivado de: OpenGLAvancadoRenderer);
Para comear, crie seu XML de modelo do game, e coloque na pasta assets/modelo. Abra o arquivo de XML que eu criei para ver um exemplo.
Depois, crie uma Activity para o seu Game. Nela, voc dever:
1. Instanciar o Renderer que ser utilizado pela GLSurfaceView;
2. Instanciar a classe OpenGLAvancadoView, passando o Contexto
de aplicao e a instncia do Renderer para ela;
Eis o exemplo que eu criei (MainActivity.java):
public class MainActivity
OnTouchListener {
extends
Activity
implements
210
Finalmente, voc ter que criar uma subclasse de OpenGLAvancadoRenderer. Nela, voc poder tratar eventos, como o toque na tela, por exemplo,
ou ento sobrescrever alguns mtodos do Renderer. Eis o meu exemplo:
public class Renderer extends OpenGLAvancadoRenderer {
public Renderer(Context context) throws Exception {
super(context);
}
@Override
public void toque() {
super.toque();
if (simulando) {
synchronized(world) {
// Comanda a aplicao de foras
GameObject go = new GameObject();
go.setId(1);
int inx = cenaCorrente.getObjetos().
indexOf(go);
go = cenaCorrente.getObjetos().
get(inx);
Body bola = go.getB2dBody();
Vec2 forca =
new Vec2(50.0f * bola.getMass(),
50.0f * bola.getMass());
Vec2 posicao = bola.getWorldCenter().
add(new Vec2 (0,3));
bola.setAwake(true);
bola.applyForce(forca, posicao);
}
}
}
}
212
Concluso
Combinando OpenGL ES e Box2D, criamos um game com boa sensao
de realidade, pois os efeitos de movimento e coliso so aceitveis e, tambm,
com bom desempenho grfico, evitando lags e outros efeitos indesejveis.
Mas a principal vantagem do uso do OpenGL ES - e eu volto a insistir
nisto - a padronizao. Criar um framework comum para plataformas to
distintas, como Android e iOS, seria muito mais difcil se utilizssemos os
mecanismos grficos nativos. Note como foi possvel abstrair as propriedades
e torn-las parametrizveis.
Mas funciona mesmo?
Se voc adepto ou adepta de So Tom, veja as prximas figuras, que
mostram, respectivamente, o exemplo sendo executado em um dispositivo Android (Smartphone LG p500, com Cyanogen Mod 7, verso Android:
2.3.4), e em um dispositivo iOS (iPad com iOS 6.0.1).
214
uid/TP40008793-CH105);
Captulo 8
Tcnicas Comuns em Games
Existem muitos problemas interessantes em games, para os quais existem
tcnicas simples que proporcionam solues razoveis. Alis, eu sugiro que
voc sempre parta de solues simples para os seus problemas de desenvolvimento de games. No tente implementar tcnicas sofisticadas logo de cara.
E claro que existem vrias outras maneiras de fazer o que eu vou mostrar
aqui, porm, vou tentar fazer da maneira mais simples e funcional possvel, de
modo a dar uma base para que voc possa melhorar.
216
Captulo 8 -
Tcnicas Comuns em Games
217
Com estes tutoriais, voc ser capaz de exibir textos de maneira correta,
com o espacejamento proporcional adequado, exatamente como em um editor
de textos.
Eu, por outro lado, no acredito que esta seja a forma mais eficiente de
implementar um HUD. O jogador est pouco ligando se voc usou o espacejamento correto (kerning) ou no! O que ele quer ver a informao relavante,
de forma descomplicada.
218
Captulo 8 -
Tcnicas Comuns em Games
219
Para o rtulo Tempo: eu crio uma textura completa. Para isto, eu gosto
de usar o programa de desenho do LibreOffice, mas voc pode usar qualquer
outro programa, como o Inkscape ou o Gimp.
220
Captulo 8 -
Tcnicas Comuns em Games
221
Bem, como eu desenharei texto frequentemente, resolvi aumentar o framework do captulo anterior para implementar a renderizao baseada em
layout. Para isto, criei um modelo de tela em XML, que descreve as posies fixas na tela e as texturas. No exemplo Android, o arquivo fica em:
assets/texturas/modelotela.xml, e, no iOS, fica dentro de Supporting files.
Abra o arquivo e veja como eu defini as posies e texturas.
Definindo posies
As posies so definidas dentro do tag posicoes:
<mapaTela>
<posicoes>
<posicao>
<id>1</id>
<!-- Espao para o rtulo de tempo -->
<altura>10</altura>
<largura>30</largura>
<topo>0</topo>
<esquerda>0</esquerda>
</posicao>
<posicao>
<id>2</id>
<!-- Espao para a o primeiro algarismo das
horas -->
<altura>10</altura>
<largura>6</largura>
<topo>0</topo>
<esquerda>30</esquerda>
</posicao>
...
</posicoes>
222
tela, que devero ser convertidos em pixels pelo renderizador, para calcular o
VBO da posio. Da mesma forma, a altura e largura tambm so especificadas atravs de percentuais da diagonal da tela.
Utilizando a unidade baseada em percentual da diagonal da tela, eu mantenho a proporo correta dos elementos de layout em telas de tamanho
diferente.
Definindo texturas
As posies apenas definem VBOs de vrtices. Eu posso determinar quais
texturas sero mapeadas em quais posies. Para isto, preciso saber quais texturas eu quero ter no meu HUD, logo, aps definir posies, eu defino as
texturas:
...
</posicoes>
<texturas>
<textura>
<id>1</id>
<ascii>0</ascii>
<imagem>tempolabel.png</imagem>
<visivel>true</visivel>
<clicavel>false</clicavel>
<posicaoFixa>1</posicaoFixa>
</textura>
<textura>
<id>2</id>
<ascii>48</ascii>
<imagem>zero.png</imagem>
<visivel>false</visivel>
<clicavel>false</clicavel>
<posicaoFixa>0</posicaoFixa>
</textura>
...
</texturas>
</mapaTela>
Captulo 8 -
Tcnicas Comuns em Games
223
224
Captulo 8 -
Tcnicas Comuns em Games
225
226
Captulo 8 -
Tcnicas Comuns em Games
227
o funcionamento da interface grfica dos dispositivos, o que um desperdcio, afinal, Android e iOS possuem excelentes recursos de navegao, com os
quais os usurios j esto acostumados. Alm disto, diante da dificuldade para
renderizar textos, podemos criar telas informativas ou de ajuda, ou mesmo de
configurao, de maneira simples e rpida.
Se voc quiser, poder integrar a tela e o controlador do seu game ao fluxo de uma aplicao, o que incluir telas convencionais, telas OpenGL ES e
controles de navegao.
Vamos fazer um exemplo bem simples. Ele ser baseado no exemplo anterior (com HUD) e funcionar desta forma:
1. Ao executar a aplicao, uma tela convencional do dispositivo ser
exibida (Activity no Android, UIView no iOS), com um link para o
Game;
2. Ao clicar no link, a tela OpenGL ES do game caregada e o jogo
comea;
3. Haver uma textura esttica na tela OpenGL ES, com o rtulo Sair.
Ao clicar nela, o game retorna para a tela anterior;
Para implementar isto, vamos utilizar a propriedade clicvel, que inclumos no modelo de tela para cada textura.
Vou mostrar a imagem das duas telas no Android (no iOS a mesma coisa).
Plataforma Android
O cdigo-fonte deste exemplo est em: ..\Codigo\OpenGLAndroid\appintegradadroid.zip.
Na plataforma Android bem simples:
1. Criei uma subclasse de Activity e a registrei no arquivo AndroidManifest.xml, como a atividade principal;
2. Nesta Activity principal, criei uma TextView (com os atributos:
android:clicable = true e android:onclick = chamar. Poderia ser
um boto tambm, ou uma imagem;
228
3. Na Activity principal, criei um mtodo para receber o clique e invocar a Activity OpenGL ES;
4. Modifiquei o arquivo modelotela.xml para incluir a textura do boto sair (arquivo sair.png);
5. Na Activity do game, eu intercepto o toque e invoco o mtodo toque, no Renderer;
6. No Renderer, eu identifico se o toque foi dentro de alguma textura
clicvel. Se foi, eu comando a Activity do Game para se auto finalizar, o que ativar a Activity anterior (a que o usurio acionou);
A Activity principal invoca a do Game no mtodo chamar():
public void chamar(View view) {
Intent i = new Intent (this.getApplicationContext(),
GameActivity.class);
this.startActivity(i);
}
Captulo 8 -
Tcnicas Comuns em Games
229
Body bola = go.getB2dBody();
Vec2 forca = new Vec2(50.0f * bola.getMass(),
Vec2 forca =50.0f * bola.getMass());
Vec2 posicao = bola.getWorldCenter().add(
new Vec2 (0,3));
bola.setAwake(true);
bola.applyForce(forca, posicao);
}
}
}
}
Se o toque foi fora da textura, ento ele tem que aplicar uma fora bola,
conforme j fazia no exemplo anterior. O mtodo clicouEmTextura verifica
se o clique foi dentro da textura selecionada:
private boolean clicouEmTextura(MotionEvent eventoToque) {
boolean resultado = false;
Coordenada toque = new Coordenada (
eventoToque.getX(),
eventoToque.getY(),
0.0f);
for (Textura t : modeloTela.getTexturas()) {
if (t.isClicavel()) {
if (toqueDentro(t,toque)) {
resultado = true;
((GameActivity)activity).sair();
}
}
}
return resultado;
}
230
Eu no posso fazer isso dentro do Renderer, ento eu criei navegao bidirecional entre a classe GameActivity e a classe Renderer, incluindo uma
propriedade que aponta para a Activity. Na GameActivity, eu criei o mtodo
sair():
public void sair() {
this.finish();
}
Captulo 8 -
Tcnicas Comuns em Games
231
Plataforma iOS
O cdigo-fonte deste exemplo est em: ..\Codigo\OpenGLiOS\AppIntegradaIOS.zip.
Apesar de parecer muito complexo, mais devido ao Xcode do que ao Objective C (na minha opinio), o processo to simples como criar qualquer
aplicao com mltiplas views, baseada em Storyboard.
A melhor maneira de comear criar uma aplicao normal, com o template Single View, marcando as opes de usar Storyboards e ARC.
Lembre-se de direcionar como universal, ou seja: pode ser executada em
qualquer dispositivo iOS. Depois:
1. Adicione os frameworks: GLKit..framework e OpenGLES.framework;
2. Altere Header Search Path para: ${PROJECT_DIR}/**;
3. Altere Compile Sources As para: Objective C++;
4. Adicione o Box2D do outro projeto (copie o folder evitando criar
referncias!);
5. Adicione as imagens e Arquivos XML (dentro de supporting files);
6. Crie um grupo e adicione as classes (arquivos .h e ,m) do projeto OpenGLCom Textu. No acrescente os outros arquivos, s as
classes;
7. Nos dois Storyboards, crie um boto para invocar o game e adicione mais um GLKViewController, mudando a classe para
OGCTViewController;
8. Nos dois Storyboards, crie uma segue do boto Entrar no game
para a outra View (GLKView);
Antes de mais nada, deixe-me repetir: estou assumindo que voc sabe
programar aplicaes no iOS, logo, conhece Storyboards e segues. Se
voc no conhece, ento recomendo meu livro anterior: Mobile Game Jam
(http://www.mobilegamejam.com/).
Eu estou usando uma segue modal. Se voc quiser criar segues do tipo
push, ter que criar um NavigationController. Com segues do tipo push,
voc pode abrir uma segunda tela, mantendo o jogo pausado.
Como funciona?
Quando voc iniciar a aplicao, a tela exibida ser semelhante da verso
Android, com um boto para entrar no game. Ao clicar no boto, a segue
que criamos vai instanciar a View do Game, juntamente com o nosso View
232
Controller (voc mudou as classes da View e do ViewControler, como recomendado no passo 7?)
Ento, temos apenas que implementar a volta... Para comear, nosso mtodo de captura de toque, handleTapFrom, tem que ser modificado:
- (void)handleTapFrom:(UITapGestureRecognizer *)
recognizer {
if (simulando) {
if (![self clicouEmTextura:recognizer]) {
// Comanda a aplicao de foras
b2Vec2 forca =
b2Vec2(200.0f * bola->GetMass(), 200.0f *
bola->GetMass());
b2Vec2 posicao = bola->GetWorldCenter();
posicao.y += 2;
bola->SetAwake(true);
bola->ApplyForce(forca, posicao);
}
}
}
Captulo 8 -
Tcnicas Comuns em Games
233
[self dismissViewControllerAnimated:Y
ES completion:nil];
}
}
}
return resultado;
}
- (BOOL) toqueDentro: (OGBPCoordenada *) toque
naTextura: (OGMTTextura *) t
{
BOOL resultado = false;
OGMTPosicaoTela * p = t.posicaoAtual;
float cTop = (alturaViewPort / 2) - ((p.topo * diagonalTela)
/ 100);
float cLeft = - (larguraViewPort / 2) + ((p.esquerda *
diagonalTela) / 100);
float cAltura = (p.altura * diagonalTela) / 100;
float cLargura = (p.largura * diagonalTela) / 100;
float cBottom = cTop - cAltura;
float cRight = cLeft + cLargura;
float toqueX = toque.x - (larguraViewPort / 2);
float toqueY = (alturaViewPort / 2) - toque.y;
if ((toqueX >= cLeft && toqueX <= cRight) &&
(toqueY >= cBottom && toqueY <= cTop)) {
resultado = YES;
}
}
return resultado;
Tempo e movimento
Lembra-se que eu havia dito para no se preocupar com o Game loop, por
que mais tarde eu entraria nesse assunto? Bem, o momento chegou! Vamos
dar uma ajeitada no nosso Game loop.
234
Game Loop
Muito se fala sobre como o Game loop e sobre o Render loop. Basicamente, suas funes so:
Game loop: atualiza o Modelo do game (lembre-se do padro Model-View-Controller), seja usando o step do Box2D, ou calculando novas posies de cada objeto mvel na mo. Ele tambm verifica se objetivos foram alcanados, alvos atingidos etc;
Render loop: atualiza a Viso (view) a partir do Modelo (Model).
Ele deve gerar um novo frame visvel para o jogador, com a posio corrente de todos os GameObjects, alm das informaes e
indicadores (HUD);
Existem duas abordagens para implementar os dois, e eu as tenho mostrado
nos ltimos exemplos.
Game loop e Render loop sincronizados
Isto significa que os dois so executados em sequncia, dentro de uma taxa
de atualizao fixa. Antes de concluir uma iterao, o Game loop invoca o
Render loop, seja diretamente ou indiretamente (invalidando a viso). O que
importa que o cdigo dos dois executado pelo mesmo Thread.
Captulo 8 -
Tcnicas Comuns em Games
235
O GLKViewController j cria um loop de atualizao, que invoca em sequncia o mtodo update, e comanda a atualizao da GLKView, que invoca
o mtodo drawInRect. Note que a diretiva @synchronized est a apenas
para efeitar, pois no tem efeito prtico porque no h threads concorrentes
tentando acessar os mesmos dados. Eu a deixei a apenas para efeito de marcar
uma possvel migrao para abordagem assncrona.
uma implementao simples e prtica, que nos isola de muitos problemas. Podemos at configurar a taxa de atualizao do GLKViewController
atravs da propriedade preferredFramesPerSecond. O valor default 30
FPS. O GLKViewController vai utilizar uma taxa de FPS prxima que voc
deseja e voc pode consult-la atravs da propriedade framesPerSecond.
A maioria dos desenvolvedores de game considera que esta abordagem
(Game loop e Render loop sincronizados) suficiente. Apenas em casos ultraextremos, nos quais a renderizao ou a atualizao podem passar por picos
de demora, seria interessante separar os dois loops.
Game loop e Render loop assncronos
Nesta abordagem, o Game loop executado por um Thread separado do
Render loop, com sua prpria taxa de atualizao. O Render loop ativado
236
Captulo 8 -
Tcnicas Comuns em Games
237
public void run() {
gameLoop();
}
}
238
agora = System.currentTimeMillis();
Captulo 8 -
Tcnicas Comuns em Games
239
ultimo = System.currentTimeMillis();
// Invocar a atualizao:
update();
// Cdigo de renderizao:
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT
COLOR_BUFFER_BIT);
GLES20.glUseProgram(programaGLES);
...
}
GLES20.GL_
240
Eu j testei at com 60 FPS no meu dispositivo menos potente: um smartphone Android LG p500, com 600 mhz de clock, e funcionou bem. Se aumentar muito o FPS vai notar que a bola gira em alta velocidade e se move
lentamente. Como estamos aplicando a fora em um vetor acima do centro,
o torque gerado absorve grande parte da energia. Se chutar mais prximo ao
centro, ver que ela se move mais rapidamente.
Ajuste para telas de propores diferentes de 2/3
Antes de mais nada, temos que fazer um ajuste no nosso framework...
Quando criei o Modelo de Tela, eu sabia que existiam telas de propores
diferentes de 2/3. Por exemplo, 320 x 480 e 480 x 720 tm razo = 2/3. E o
que isso importa? Bem, eu calculo TODAS as medidas a partir da proporo
Metro/Tela, logo, se a proporo mais esticada, isto pode fazer com que
texturas posicionadas muito prximas aos limites (superior, inferior, esquerdo
e direito) fiquem parcialmente fora da tela.
Quando fui testar este exemplo em um tablet Motorola Xoom 2 Media Edition, cuja tela tem 800 x 1280 pixels (razo = 0,625), notei que algumas texturas estavam com parte fora da tela. Isto no acontece com os GameObjects,
porque eu posiciono limites (teto, cho, parede direita e parede esquerda).
Mas com as posies do Modelo de Tela, isto pode acontecer.
Ento, criei um filtro para telas com proporo menor que 2/3. Este filtro
desloca ligeiramente o centro, de modo que todas as posies do modelo de
tela fiquem dentro dos limites. J testei em vrios tipos de emulador e de dispositivos, com sucesso.
Captulo 8 -
Tcnicas Comuns em Games
241
No Android
Crie um mtodo de verificao na classe OpenGLAvancadoRenderer:
protected void verificarNovaProporcao(Coordenada centro,
float metadeAltura,
float metadeLargura) {
float esquerda = centro.getX() - metadeLargura;
float direita = centro.getX() + metadeLargura;
float topo = centro.getY() + metadeAltura;
float baixo = centro.getY() - metadeAltura;
float limiteEsquerdo = -1 * (larguraViewPort / 2);
float limiteDireito = larguraViewPort / 2;
float limiteSuperior = alturaViewPort / 2;
float limiteInferior = -1 * (alturaViewPort / 2);
if (esquerda < limiteEsquerdo) {
centro.setX(centro.getX() + (limiteEsquerdo - esquerda));
}
else if (direita > limiteDireito) {
centro.setX(centro.getX() - (direita - limiteDireito));
}
if (topo > limiteSuperior) {
centro.setY(centro.getY() - (topo - limiteSuperior));
}
else if (baixo < limiteInferior) {
centro.setY(centro.getY() + (limiteInferior - baixo));
}
}
242
No iOS:
- (void) carregarModeloTela
{
mapaCaracteres = [[NSMutableDictionary alloc] init];
for (OGMTPosicaoTela * p in modeloTela.posicoes) {
float cTop = (alturaViewPort / 2) - ((p.topo * diagonalTela)
/ 100);
float cLeft = - (larguraViewPort / 2) + ((p.esquerda *
diagonalTela) / 100);
Captulo 8 -
Tcnicas Comuns em Games
243
float
float
float
float
Eu criei quatro novas propriedades na classe de posio, de modo a representar os limites (possivelmente ajustados) da textura. Eu uso isso no teste de
toque dentro de texturas:
Android:
private boolean toqueDentro(Textura t, Coordenada
toque) {
boolean resultado = false;
PosicaoTela p = t.getPosicaoAtual();
float toqueX = toque.getX() - (larguraViewPort / 2);
float toqueY = (alturaViewPort / 2) - toque.getY();
if ((toqueX >= p.getScreenLeft() && toqueX <=
p.getScreenRight()) &&
(toqueY >= p.getScreenBottom() && toqueY <=
p.getScreenTop())) {
resultado = true;
244
return resultado;
iOS:
- (BOOL) toqueDentro: (OGBPCoordenada *) toque naTextura:
(OGMTTextura *) t
{
BOOL resultado = false;
OGMTPosicaoTela * p = t.posicaoAtual;
float toqueX = toque.x - (larguraViewPort / 2);
float toqueY = (alturaViewPort / 2) - toque.y;
if ((toqueX >= p.screenLeft && toqueX <= p.screenRight) &&
toqueY >= p.screenBottom && toqueY <= p.screenTop)) {
resultado = YES;
}
}
return resultado;
Isto garante que as texturas do modelo sempre ficaro dentro da tela, independentemente da proporo entre altura e largura.
Voltando ao Game Loop...
Para comear, eu acabei com o Thread extra. Agora, o Game loop invocado dentro do Render loop, ou seja, no incio do mtodo onDrawFrame().
E no s isso: eu tambm sincronizei o Game loop para que apresente uma
taxa de FPS prxima que informamos no XML. Veja as mudanas no mtodo onDrawFrame():
@Override
public void onDrawFrame(GL10 gl) {
// Controle de FPS:
long agora = System.currentTimeMillis();
long intervaloPassado = tempo;
if (ultimo > 0) {
intervaloPassado = agora - ultimo;
Captulo 8 -
Tcnicas Comuns em Games
245
}
if (intervaloPassado < tempo) {
// Colocar o Thread em sleep mode, pois falta
// algum tempo para iniciar
try {
Thread.sleep((long) (tempo - intervaloPassado));
intervaloPassado += (tempo - intervaloPassado);
} catch (InterruptedException e) {
Log.e(GAMELOOP, Interrompido o sleep: +
e.getMessage());
}
}
// Verifica se atingiu 1 segundo
contagemFrames++;
segundo += intervaloPassado;
if (segundo >= 1000) {
// atingiu 1 segundo
FPSmedio = contagemFrames;
segundo = 0;
contagemFrames = 1;
}
ultimo = System.currentTimeMillis();
update(intervaloPassado / 1000.0f);
246
O resultado um Game loop simples, porm com uma taxa de FPS consistente, que me permite desenvolver o game sem lags e aceleraes indevidas.
claro que a exibio do FPS acaba roubando algum tempo til do Game,
mas, em seu lugar ns certamente exibiramos alguma outra informao no
HUD, logo, a alterao esperada.
Vamos ver a implementao no iOS
No iOS, eu j tenho o tempo decorrido como uma propriedade da classe
GLKViewController, ento, eu movi o cdigo de verificao de FPS para
dentro do mtodo update:
- (void)update
{
float deltaT = [self timeSinceLastUpdate];
// Controle de FPS:
Captulo 8 -
Tcnicas Comuns em Games
247
world->Step(deltaT,
cenaCorrente.box2d.velocityInterations,
cenaCorrente.box2d.positionInterations);
}
Movimento
J vimos algumas opes para criar um Game loop, agora necessrio ver
como movimentar os GameObjects de forma consistente, evitando a acelerao indevida.
Se estamos utilizando Box2D, basta passarmos a taxa de FPS no momento
de invocar o mtodo step:
world.step(deltaT,
cenaCorrente.getBox2d().getVelocityInterations(),
cenaCorrente.getBox2d().getPositionInterations());
248
Agora, estamos movendo constantemente o objeto, mesmo que uma pequena frao de cada vez, dando a iluso de movimento contnuo e suave.
Nada como um exemplo
Vamos pensar em um outro exemplo: uma nave que atravessa a tela na horizontal, da esquerda para a direita, em velocidade constante relativa de 8 m/s.
O cdigo-fonte deste exemplo est em:
Android: ..\Codigo\OpenGLAndroid\movimentodroid.zip;
iOS: ..\Codigo\OpenGLiOS\MovimentoIOS.zip;
As imagens que estou usando so de domnio pblico e vieram todas do
site openclippart.org.
Captulo 8 -
Tcnicas Comuns em Games
249
Neste exemplo, voc ver uma nave atravessar a tela, da esquerda para a
direita, repetidamente. A velocidade da nave 3 m/s, ela vai percorrer a distncia em cerca de 3 segundos, dependendo da proporo entre altura e largura
da tela. Se frao altura/largura for proporcional a 2/3, ento ela demorar
cerca de 3 segundos para atravessar a tela.
Eu poderia ter criado um novo base code, tirando o Box2D e simplificando tudo, mas achei melhor reusar o framework de game que temos, afinal, eu
posso misturar objetos animados com Box2D e objetos animados manualmente, o que comum em games. Ento, usei o mesmo base code do exemplo
anterior (GameLoop) e fiz algumas mudanas. Todas esto precedidas pelo
comentrio // @@@@ Alterao assim, voc saber onde eu modifiquei o cdigo-fonte.
250
Para comear, eu criei uma nova propriedade nos meus GameObjects, que
est dentro do arquivo modeloExemplo.xml:
<objeto>
<!-- nave -->
<id>1</id>
<tipo>2</tipo>
<forma>1</forma>
<centro>
<x>0</x>
<y>0</y>
</centro>
<!-- Vamos alinhar a nave esquerda -->
<alinhar>2</alinhar>
<altura>0.5</altura>
<esticarAltura>false</esticarAltura>
<largura>1.5</largura>
<esticarLargura>false</esticarLargura>
<arquivoTextura>nave.png</arquivoTextura>
<fisicaBox2d>false</fisicaBox2d>
<densidade>1.0</densidade>
<atrito>0.1</atrito>
<coefRetribuicao>0.6</coefRetribuicao>
</objeto>
Captulo 8 -
Tcnicas Comuns em Games
251
case ALINHAMENTO_ESQUERDA:
go.setCentro(
new Coordenada(this.telaTopLeft.getX(),
go.getCentro().getY(),0.0f)
);
// @@@@ Alterao
if (go.isFisicaBox2D()) {
go.getB2dBody().setTransform(new
Vec2(this.
telaTopLeft.getX()
/ proporcaoMetroTela, go.getCentro().getY()),
0.0f);
}
break;
...
// @@@@ Alterao
if (go.isFisicaBox2D()) {
// Recalcula os tamanhos dos objetos
if (go.getFixture() != null) {
body.destroyFixture(go.getFixture());
}
...
if (go.getArquivoTextura() != null) {
Coordenada centro = null;
// @@@@ Alterao
if (go.isFisicaBox2D()) {
centro = new Coordenada(go.getCentro().getX() *
proporcaoMetroTela,
go.getCentro().getY() * proporcaoMetroTela,
go.getCentro().getZ()*proporcaoMetroTela);
}
else {
centro = go.getCentro();
}
...
252
Captulo 8 -
Tcnicas Comuns em Games
253
world.step(deltaT,
cenaCorrente.getBox2d().getVelocityInterations(),
cenaCorrente.getBox2d().getPositionInterations());
GameObject nave = cenaCorrente.getObjetos().get(0);
nave.getCentro().setX(nave.getCentro().getX()
+
(velocidadeMS * deltaT));
if (nave.getCentro().getX() > larguraViewPort) {
nave.getCentro().setX(origemX);
}
}
No iOS:
Vrias alteraes dentro do mtodo recalcularAlinhamentos:
...
case ALINHAMENTO_ESQUERDA: {
go.centro = [[OGBPCoordenada alloc] initWithx:
telaTopLeft.x
y: go.centro.y
z: 0.0f];
// @@@@ Alterao:
if (go.fisicaBox2D) {
novaCoord.x = telaTopLeft.x / proporcaoMetroTela;
novaCoord.y = go.centro.y;
254
}
...
}
else {
coordCentro = go.centro;
Captulo 8 -
Tcnicas Comuns em Games
255
256
diagonalTela = pow(alturaViewPort, 2)
pow(larguraViewPort, 2);
diagonalTela = sqrt(diagonalTela);
proporcaoMetroTela = (float) (diagonalTela /
cenaCorrente.box2d.proporcaoMetroTela);
// @@@@ Alterao:
...
Efeito de paralaxe
Paralaxe um conceito de astronomia, mas, em games, uma tcnica para
aumentar a iluso de profundidade em jogos 2D. Tambm conhecido como
Parallax scrolling.
Nesta tcnica, os objetos que esto em planos mais afastados (com relao
ao observador) se deslocam em velocidade menor que os objetos que esto em
planos mais prximos (do observador).
claro que voc pode conseguir isso facilmente em um jogo 3D, afinal, a
prpria projeo dos elementos vai lhe proporcionar o efeito de paralaxe, caso
voc decida seguir um jogador com a cmera. Mas em jogos 2D, um
pouco mais complicado.
Captulo 8 -
Tcnicas Comuns em Games
257
Tcnicas de paralaxe
Existem algumas tcnicas para conseguir o efeito de paralaxe:
Camadas
Todos os sistemas grficos possuem o conceito de camadas. Uma camada
um contexto grfico, no qual podemos desenhar, e elas podem ser sobrepostas,
formando um sanduche que a tela final. Ns podemos deslocar ligeiramente as camadas com diferentes unidades, criando o efeito de paralaxe.
uma tcnica interessante, s que exige maior conhecimento e investimento no sistema grfico nativo de cada plataforma.
Camadas de GameObjects
Outra maneira mais simples criar camadas lgicas de GameObjects, separando-os em grupos: primeiro plano, segundo plano e terceiro plano. Normalmente, os objetos em primeiro plano so aqueles com os quais o jogador
pode interagir e tambm respondem a eventos, colises etc. Os de outros planos servem apenas como cenrio.
Nesta tcnica, s existe uma nica camada fsica de desenho, mas os objetos so animados ANTES da renderizao, com velocidades diferentes. Temos uma velocidade para o primeiro plano, uma menor para o segundo plano
e uma ainda menor para o terceiro plano. Na verdade, podemos ter quantos
planos desejarmos.
Esta tcnica independente de plataforma nativa e pode ser facilmente
implementada.
258
O carro se move para o sentido direito da tela, mas, na verdade, est parado. S criei uma iluso para que as rodas paream se mover (duas imagens
sendo alternadas a cada 2 frames). As rvores e postes esto em segundo plano
e se alternam, aleatoriamente, e as casas, prdios e o morro esto em terceiro
plano, tambm se alternando aleatoriamente.
O cdigo-fonte dos exemplos est em:
Android: ..\Codigo\OpenGLAndroid\paralaxdroid.zip;
iOS: ..\Codigo\OpenGLiOS\ParalaxIOS.zip;
Captulo 8 -
Tcnicas Comuns em Games
259
Implementao geral
Eu peguei o exemplo Movimento e fiz as mudanas. Para comear, eu
criei GameObjects invisveis e animados por fora do Box2D:
<objeto>
<!-- carro -->
<!-- Est no primeiro plano: z = 0 -->
<id>1</id>
<tipo>2</tipo>
<forma>1</forma>
<centro>
<x>0</x>
<y>0</y>
<z>0</z>
</centro>
<!-- Vamos alinhar ao cho -->
<!-- inclumos alinhamento da base ao cho: tipo 5 -->
<alinhar>5</alinhar>
<altura>1.5</altura>
<esticarAltura>false</esticarAltura>
<largura>3.0</largura>
<esticarLargura>false</esticarLargura>
<arquivoTextura>carro.png</arquivoTextura>
<fisicaBox2d>false</fisicaBox2d>
<visivel>false</visivel>
<densidade>1.0</densidade>
<atrito>0.1</atrito>
<coefRetribuicao>0.6</coefRetribuicao>
</objeto>
260
Captulo 8 -
Tcnicas Comuns em Games
261
Se voc quiser usar a parte inferior da tela (alinhar no limite fsico de baixo), vai ter muito trabalho. Veja o prximo exemplo.
Implementao Android
Vamos comear com as alteraes na classe GameObject:
public
class
GameObject
implements
Comparable
<GameObject>, Cloneable {
...
public static enum ALINHAMENTO_GO {
ALINHAMENTO_NENHUM,
ALINHAMENTO_CHAO,
ALINHAMENTO_ESQUERDA,
ALINHAMENTO_DIREITA,
ALINHAMENTO_TETO,
ALINHAMENTO_BASE_CHAO
};
...
private boolean visivel;
...
@Override
public Object clone() throws CloneNotSupportedException {
GameObject copia = new GameObject();
copia.setAlinhamento(this.getAlinhamento());
copia.setAltura(this.getAltura());
copia.setArquivoTextura(this.getArquivoTextura());
copia.setAtrito(this.getAtrito());
copia.setB2dBody(this.getB2dBody());
copia.setCentro(this.getCentro());
copia.setCoefRetribuicao(this.getCoefRetribuicao());
copia.setDensidade(this.getDensidade());
copia.setEsticarAltura(this.isEsticarAltura());
copia.setEsticarLargura(this.isEsticarLargura());
copia.setFisicaBox2D(this.isFisicaBox2D());
copia.setFixture(this.getFixture());
copia.setForma(this.getForma());
copia.setHandlerTextura(this.getHandlerTextura());
copia.setId(this.getId());
copia.setJaCalculado(this.isJaCalculado());
262
copia.setLargura(this.getLargura());
copia.setTipo(this.getTipo());
copia.setVisivel(this.isVisivel());
copia.setVobTextura(this.getVobTextura());
copia.setVobVertices(this.getVobVertices());
return copia;
A principal a implementao da interface Cloneable, que exige a criao de um mtodo para clonar um GameObject.
Agora, vamos ver as modificaes no mtodo onSurfaceChanged:
// Agora, vamos separar os objetos de segundo e terceiro
planos
segundoPlano = new ArrayList<GameObject>();
terceiroPlano = new ArrayList<GameObject>();
for (Iterator<GameObject> iterator = cenaCorrente.getObjetos().
iterator();
iterator.hasNext();) {
GameObject go = iterator.next();
float alturaGo = (go.isJaCalculado()) ? go.getAltura() :
go.getAltura() *
proporcaoMetroTela;
float larguraGo = (go.isJaCalculado()) ? go.getLargura() :
go.getLargura() *
proporcaoMetroTela;
float linhaBase = this.telaBottomRight.getY() * 0.50f;
if (go.getAlinhamento() == ALINHAMENTO_GO.ALINHAMENTO_
BASE_CHAO) {
// Alinha todos na base
go.getCentro().setY(linhaBase + alturaGo/2);
}
if (go.getId() == 11) {
go.getCentro().setY(linhaBase - alturaGo/2);
}
Captulo 8 -
Tcnicas Comuns em Games
263
if (go.getCentro().getZ() == SEGUNDO_PLANO) {
segundoPlano.add(go);
}
else if (go.getCentro().getZ() == TERCEIRO_PLANO) {
terceiroPlano.add(go);
}
if (go.getId() == 1) {
try {
carro1 = (GameObject) go.clone();
carro1.setVisivel(true);
carroAdesenhar = cenaCorrente.getObjetos().
indexOf(go);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
else if (go.getId() == 2) {
try {
carro2 = (GameObject) go.clone();
carro2.setVisivel(true);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
// Por segurana, no d para remover um objeto de uma
coleo durante a iterao
for (GameObject go : segundoPlano) {
cenaCorrente.getObjetos().remove(go);
}
for (GameObject go : terceiroPlano) {
cenaCorrente.getObjetos().remove(go);
}
264
Captulo 8 -
Tcnicas Comuns em Games
265
// Loop dos objetos de segundo e terceiro plano:
int adicionarSegundoPlano = 0;
int adicionarTerceiroPlano = 0;
for (GameObject go : adicionados) {
float antesX = go.getCentro().getX();
if (go.getCentro().getZ() == TERCEIRO_PLANO)
{
// Anima objeto de terceiro plano
go.getCentro().setX(go.getCentro().getX() ((velocidadeMS * 0.5f) *
deltaT));
}
else if (go.getCentro().getZ() == SEGUNDO_PLANO) {
// Anima objeto de segundo plano
go.getCentro().setX(go.getCentro().getX() ((velocidadeMS) *
deltaT));
}
float direita = go.getCentro().getX() + ((go.getLargura() *
proporcaoMetroTela) / 2);
if (direita < this.telaTopLeft.getX()) {
if (go.getCentro().getZ() == TERCEIRO_PLANO) {
adicionarTerceiroPlano++;
}
else {
adicionarSegundoPlano++;
}
}
}
List<GameObject> aRemover = new ArrayList<GameObject>();
for (GameObject go : adicionados) {
float direita = go.getCentro().getX() + ((go.getLargura() *
proporcaoMetroTela) / 2);
if (direita < this.telaTopLeft.getX()) {
aRemover.add(go);
}
266
Captulo 8 -
Tcnicas Comuns em Games
267
desenharTextos();
// Vamos renderizar os Game Objects de terceiro plano:
for (GameObject go : adicionados) {
if (go.getCentro().getZ() == TERCEIRO_PLANO) {
desenharObjeto(go);
}
}
// Agora, os de segundo plano:
for (GameObject go : adicionados) {
if (go.getCentro().getZ() == SEGUNDO_PLANO) {
desenharObjeto(go);
}
}
// Finalmente, os de primeiro plano:
for (GameObject go : cenaCorrente.getObjetos()) {
if (go.getArquivoTextura() != null && go.isVisivel())
{
desenharObjeto(go);
}
}
Implementao iOS
A implementao iOS foi baseada no mesmo projeto, MovimentoIOS.
As modificaes na interface da classe OGBPGameObject foram:
...
typedef enum {
ALINHAMENTO_NENHUM,
ALINHAMENTO_CHAO,
ALINHAMENTO_ESQUERDA,
ALINHAMENTO_DIREITA,
ALINHAMENTO_TETO,
268
...
@synthesize visivel;
...
- (id) mutableCopyWithZone:(NSZone *)zone
{
OGBPGameObject *copiaGo =
[[OGBPGameObject allocWithZone:zone] init];
copiaGo.alinhamento = self.alinhamento;
copiaGo.altura = self.altura;
copiaGo.arquivoTextura = [self.arquivoTextura
copy];
copiaGo.atrito = self.atrito;
copiaGo.b2dBody = self.b2dBody;
copiaGo.centro = [self.centro mutableCopy];
copiaGo.coefRetribuicao = self.coefRetribuicao;
copiaGo.densidade = self.densidade;
copiaGo.esticarAltura = self.esticarAltura;
copiaGo.esticarLargura = self.esticarLargura;
copiaGo.fixture = self.fixture;
copiaGo.forma = self.forma;
copiaGo.idGO = self.idGO;
copiaGo.largura = self.largura;
copiaGo.tipo = self.tipo;
copiaGo.glProps = [self.glProps mutableCopy];
copiaGo.fisicaBox2D = self.fisicaBox2D;
return copiaGo;
}
Captulo 8 -
Tcnicas Comuns em Games
269
tenho duas propriedades que so classes que eu criei, eu tenho que implementar o protocolo NSMutableCopying nelas tambm (OGBPCoordenada
e OGBPGLProps).
Agora, vamos ver as modificaes no mtodo recalcularAlinhamentos,
que o equivalente ao onSurfaceChanged(), do Android:
segundoPlano = [[NSMutableArray alloc] init];
terceiroPlano = [[NSMutableArray alloc] init];
for (OGBPGameObject * go in cenaCorrente.objetos) {
float alturaGo = go.altura * proporcaoMetroTela;
float larguraGo = go.largura * proporcaoMetroTela;
float linhaBase = telaBottomRight.y * 0.50f;
if (go.alinhamento == ALINHAMENTO_BASE_CHAO) {
// Alinha todos na base
go.centro.y = linhaBase + alturaGo/2;
}
if (go.idGO == 11) {
go.centro.y = linhaBase - alturaGo/2;
}
if (go.centro.z == SEGUNDO_PLANO) {
[segundoPlano addObject:go];
}
else if (go.centro.z == TERCEIRO_PLANO) {
[terceiroPlano addObject:go];
}
if (go.idGO == 1) {
carro1 = [go mutableCopy];
carro1.visivel = YES;
carroAdesenhar = [cenaCorrente.objetos
indexOfObject:go];
}
else if (go.idGO == 2) {
carro2 = [go mutableCopy];
carro2.visivel = YES;
}
}
270
Captulo 8 -
Tcnicas Comuns em Games
271
if (contagemFrames > 2) {
// troca imagem do carro
contagemFrames = 0;
carroFinal = (self->trocou) ? carro2 : carro1;
self->trocou = !self->trocou;
[cenaCorrente.objetos setObject:carroFinal at
IndexedSubscript:carroAdesenhar];
}
// Loop dos objetos de segundo e terceiro plano:
int adicionarSegundoPlano = 0;
int adicionarTerceiroPlano = 0;
for (OGBPGameObject * go in adicionados) {
float antesX = go.centro.x;
if (go.centro.z == TERCEIRO_PLANO) {
// Anima objeto de terceiro plano
go.centro.x = go.centro.x - ((velocidadeMS
* 0.5f) * deltaT);
}
else if (go.centro.z == SEGUNDO_PLANO) {
// Anima objeto de segundo plano
go.centro.x = go.centro.x - ((velocidadeMS)
* deltaT);
}
float direita = go.centro.x + ((go.largura *
proporcaoMetroTela) / 2);
if (direita < telaTopLeft.x) {
if (go.centro.z == TERCEIRO_PLANO) {
adicionarTerceiroPlano++;
}
else {
adicionarSegundoPlano++;
}
}
}
NSMutableArray * aRemover = [[NSMutableArray alloc]
init];
for (OGBPGameObject * go in adicionados) {
float direita = go.centro.x + ((go.largura *
272
*)view
drawInRect:(CGRect)
Captulo 8 -
Tcnicas Comuns em Games
273
{
...
// Vamos desenhar os indicadores dinmicos:
[self desenharTextos];
// Vamos renderizar os Game Objects de terceiro
plano:
for (OGBPGameObject *go in adicionados) {
if (go.centro.z == TERCEIRO_PLANO) {
[self desenharObjeto:go];
}
}
// Agora, os de segundo plano:
for (OGBPGameObject *go in adicionados) {
if (go.centro.z == SEGUNDO_PLANO) {
[self desenharObjeto:go];
}
}
// Finalmente, os de primeiro plano:
for (OGBPGameObject *go in cenaCorrente.objetos) {
if (go.arquivoTextura != nil && go.visivel) {
[self desenharObjeto:go];
}
}
274
Todos os trs jogos do filme (Indie Game: The Movie) so muito bons e
divertidos. Porm, o Fez, na minha opinio, sensacional! O criador, Phil
Fish, conseguiu inovar em um game plataforma, pois acrescentou a possibilidade de girarmos o game no eixo y.
Todos os jogos plataforma tm algumas coisas em comum:
So 2D;
O cenrio composto de obstculos e plataformas, entre as quais o
jogador pode pular;
Normalmente, a cmera centrada no PlayerObject, ou em seu
entorno.
Criar um game plataforma com esse framework que fizemos bem simples. Para comear, o PlayerObject (O GameObject controlado pelo jogador) deve poder saltar sem ficar quicando. Podemos conseguir isso zerando
o coeficiente de retribuio da nossa configurao. Depois, ele deve ter um
tamanho compatvel com a tela. Se o criarmos grande demais, teremos dificuldade em faz-lo saltar entre as plataformas.
O cenrio de um game plataforma se movimenta em funo do PlayerObject (PO), logo, podem existir partes ocultas que s aparecem quando o jogador se aproxima. como deslizssemos o mundo com a mo, enquanto
o observamos atravs de uma lente.
Em nossos exemplos com a bola, sempre usamos cho, teto e paredes, s que sem textura associada. Isso criaria um efeito fantasmagrico,
pois o PO fica batendo em coisas invisveis (vamos mostrar isso no exemplo).
Ento, as plataformas devem ter uma textura associada. E, se possuem textura,
ns temos que posicion-las ao final, depois de calcular seus vrtices, mantendo o centro como referncia.
Outro problema posicionar a cmera... No OpenGL no existe cmera, que apenas uma matriz de transformao que multiplicamos pela matriz
de modelo. Quando usamos cmera (gluLookAt), criamos mais um elemento
para atrapalhar o posicionamento do game. Ento, resolvi retirar essa varivel da equao, tornando a matriz cmera igual a identidade.
Simplesmente eu vou manipular a matriz de projeo a cada atualizao,
focando o centro na posio do nosso PlayerObject.
Eu fiz um exemplo simples e voc pode usar qualquer modelo. Usei a mesma bola que usamos nos exemplos anteriores, alterando seu tamanho e seu
coeficiente de retribuio para zero:
Captulo 8 -
Tcnicas Comuns em Games
275
<objeto>
<!-- Bola -->
<id>6</id>
<tipo>2</tipo>
<forma>1</forma>
<centro>
<x>0.0</x>
<y>0.0</y>
<z>0.0</z>
</centro>
<alinhar>0</alinhar>
<altura>0.5</altura>
<esticarAltura>false</esticarAltura>
<largura>0.5</largura>
<esticarLargura>false</esticarLargura>
<arquivoTextura>bola.png</arquivoTextura>
<densidade>4.0</densidade>
<atrito>0.1</atrito>
<coefRetribuicao>0.0</coefRetribuicao>
</objeto>
276
Implementao Android
Vamos eliminar a matrizCamera, que gerada no final do mtodo onSurfaceCreated(), substituindo-a pela matriz identidade:
Matrix.setIdentityM(matrizCamera, 0);
-width
width
-height
height
-1, 1);
/
/
/
/
2,
2,
2,
2,
Captulo 8 -
Tcnicas Comuns em Games
277
Matrix.setIdentityM(matrizProjecao, 0);
Matrix.orthoM(matrizProjecao, 0, camX - larguraViewPort/2,
camX + larguraViewPort/2,
camY - alturaViewPort/2,
camY + alturaViewPort/2,
-1, 1);
278
Vec2(chao.
Implementao iOS
Substituir matriz de cmera pela matriz identidade:
matrizCamera = GLKMatrix4Identity;
Captulo 8 -
Tcnicas Comuns em Games
279
2,
2,
2,
2,
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer {
280
Sistemas de partculas
Essa a ltima tcnica que vamos explicar no livro, antes de entrarmos no
projeto exemplo. Porm, nem de longe a ltima tcnica que existe. A programao de games muito mais rica e complexa do que eu apresentei ao longo
deste livro. Porm, creio que consegui meu objetivo: resumir as principais
tcnicas, apresentando-as de maneira simples e biplataforma (Android e iOS).
Um sistema de partculas uma simulao computacional formada por
vrios objetos de propores diminutas, com o objetivo de representar elementos fludicos, como: fumaa, exploso, fogo, gua, nuvens e at, pasmem,
cabelos! Sim, cabelos podem ser representados com um sistema de partculas
com rastro.
Composio
Um sistema de partculas composto pela prpria partcula e por um emissor, que origina diversas partculas, sendo distribudas de acordo com sua
necessidade.
Cada partcula possui uma textura acoplada e, em sistemas mais complexos, as partculas podem variar suas texturas e/ou seu brilho ou transparncia.
O emissor contm dados sobre a quantidade de partculas, sua origem, seus
vetores de rota, seus tempos de vida etc. As partculas partem se afastando
do objeto que representa a origem. A maneira como partem e seu ngulo so
muito importantes. Por exemplo, quando representamos fogo, as partculas
tendem a seguir um formato de cabea de cometa (supondo que temos gravidade no ambiente do game), outro exemplo, quando temos um jato direcional, como um motor de foguete, um fogo de artifcio ou uma arma, neste
caso, a tendncia um formato de leque. Quando temos uma exploso, especialmente em ambiente sem gravidade, a tendncia que as partculas se
espalhem em todas as direes.
Captulo 8 -
Tcnicas Comuns em Games
281
Um exemplo
O uso mais comum para sistemas de partculas representar exploses.
Jogos de ao sempre tem algum tipo de exploso e o efeito de um sistema de
partculas acrescenta realismo ao game.
Vou criar um pequeno exemplo, utilizando nosso framework, para representar um mssil destruindo um asteroide. Embora no seja necessrio,
vou utilizar Box2D para animar o mssil, pois quero mostrar um exemplo
de anlise de coliso com Android e iOS, mas as partculas sero animadas
manualmente.
282
Captulo 8 -
Tcnicas Comuns em Games
283
Implementao em Android
Vamos comear pelo sistema de partculas, que est no pacote com.obomprogramador.games.particlesystem. A classe Particle representa uma nica
284
Captulo 8 -
Tcnicas Comuns em Games
285
}
286
Captulo 8 -
Tcnicas Comuns em Games
287
bolafogo.getHandlerTextura(),
bolafogo.getVobVertices(),
2,3);
particleSystem.setParado(false);
}
else {
tempoAcumulado += deltaT;
particleSystem.update(deltaT);
}
}
}
}
288
Captulo 8 -
Tcnicas Comuns em Games
289
Fora o Fading, eu precisava pegar cada partcula, verificar se estava ativa, renderiz-la e testar se ainda existiriam mais partculas ativas no sistema.
Eu sobrescrevi o mtodo onDrawFrame() na classe Renderer.java:
@Override
public void onDrawFrame(GL10 gl) {
super.onDrawFrame(gl);
if (this.emChamas) {
int contagem = 0;
for (Particle p : particleSystem.particles) {
if (p.isAtiva()) {
if (p.getDuracaoSegundos() < p.getTempoAtiva()) {
p.setAtiva(false);
continue;
}
contagem++;
290
Captulo 8 -
Tcnicas Comuns em Games
291
GLES20.glUniform1f(maFadeFactor, (float) percentalfa);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false,
matrizIntermediaria, 0);
GLES20.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0,
QUANTIDADE_DE_VERTICES);
checkGlError(glDrawArrays);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
}
}
if (emChamas) {
if (contagem == 0) {
emChamas = false;
particleSystem = null;
asteroide.setVisivel(false);
}
else {
if (contagem < (particleSystem.qtdOriginal / 3)) {
particleSystem.refilParticles();
}
}
}
}
}
Implementao iOS
Bem, agora est na hora de largar tudo, sentar em posio de ltus e ficar
repetindo OOMMMM... A implementao iOS um pouco mais hardcore
292
Captulo 8 -
Tcnicas Comuns em Games
293
294
Captulo 8 -
Tcnicas Comuns em Games
295
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
// Limpar e preparar:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(_program);
...
Eu ainda estou utilizando as classes: GLKTextureInfo e GLKTextureLoader, logo, eu armazeno as texturas utilizando uma instncia de GLKTextureInfo e a propriedade name contm o indicador da textura a ser utilizada.
Outros detalhes que mudam so os atributos de textura e vrtices. Com o
GLKBaseEffect, ns usvamos os indicadores que ele nos formecia:
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT,
GL_FALSE, sizeof(GLfloat) * 3, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribPosition);
glBindBuffer(GL_ARRAY_BUFFER, _textureBuffer);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT,
GL_FALSE, sizeof(GLfloat) * 2, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
296
Concluso
Existem milhares de tcnicas e formas de implementao diferentes. As
que demonstrei nesse captulo nem sempre so as melhores, porm, so fceis
de entender, simples de implementar e eficientes.
Eu recomendo que voc rode os exemplos, altere e use bastante, criando
seu prprio framework de games.
Captulo 9
Vamos Criar um Game
Seu objetivo ao ler este livro criar um game, certo? Ento, para facilitar
as coisas e reunir tudo o que vimos at agora, vou mostrar um game bem simples, porm interessante: Bola no Quintal.
298
Voc tem um cenrio, com um muro dividindo duas reas. Na parte esquerda, ficam voc e a bola e, na parte direita, alguns objetos equilibrados sobre
pedaos de madeira. Voc deve jogar a bola e derrubar os objetos com ela.
Sempre que ela toca o cho, volta para voc jogar novamente.
No primeiro nvel, voc deve apenas derrubar os objetos, no menor tempo possvel. Nos outros nveis, voc dever derrub-los em uma determinada
ordem. Alm disto, as bolas vo ficando mais pesadas e vazias nos nveis
superiores, o que faz com que seja mais difcil acertar.
Eu criei arquivos de recursos com idioma Ingls e localizao para Portugus. O motivo simples: se eu no tiver recursos no idioma do jogador,
mostrarei a verso em Ingls.
O jogo armazena os menores tempos de concluso de cada nvel. Em uma
verso comercial futura, ele vai compartilhar isso atravs de redes sociais de
games, como o Game Center, da Apple.
Cada nvel definido em seu prprio arquivo XML de modelo, e contm
um tempo limite para concluso. Se o jogador no conseguir derrubar todos os
objetos neste tempo, aparece uma imagem informando que ele perdeu.
Por que no um jogo de tiro?
Na verdade, um jogo de tiro, como o AsteroidNuts (mostrado no incio),
seria at mais simples. Porm, bem comum. Eu quis fazer um game que
usasse bem os recursos do livro, e o Bola no Quintal atende a esse objetivo.
Na verdade, o framework se adapta muito bem a jogos de bola. Finalmente, outra grande vantagem a atratividade para Casual Gamers, crianas
etc. Um jogo de bola agrada a todos, porm, existem pessoas que no curtem
Shooters.
Limitaes
O objetivo deste trabalho fornecer um conjunto de ferramentas e tcnicas, com exemplo de aplicao em cdigo-fonte, para que voc construa games mveis para Android e iOS. Logo, o game que vou mostrar apenas um
prottipo funcional, que emprega quase tudo o que vimos no livro.
Ele s tem trs nveis, embora seja muito expandir isso para 30 ou mais. O
objetivo mostrar como criar nveis e as diferenas de dificuldade entre eles.
O game tambm carece de efeitos sonoros e msica. Resolvi deixar o game o
mais simples possvel para apresentao no livro.
Todas as ilustraes foram compostas em parte (ou todo) com base em
imagens do site OpenClippart.org, logo, no so imagens para uso comercial.
Finalmente, outra coisa que deixei de fora foi o Social gaming, ou seja, o
uso de uma rede social de games, como o Game Center, da Apple, ou o OpenFeint. Embora o compartilhamento de conquistas seja importante, eu preferi
deixar de fora, pois foge ao escopo do livro.
Resumindo, o game funcional e perfeitamente jogvel, porm, no um
produto acabado, pronto para o mercado.
300
A concepo
A ideia de fazer um jogo como o Bola no Quintal surgiu antes de eu
comear a escrever o livro. Eu gosto particularmente de jogos de bolas, prova
disto que, no meu livro anterior, eu criei o BueiroBall (que tambm vai
ser lanado no mercado em breve). Porm, quando eu estava experimentando
com o Box2D, criei alguns efeitos interessantes, como o da prxima figura.
Jogos casuais
Jogos casuais so para pessoas comuns, que jogam apenas em determinados momentos, como: antes de dormir, na fila de espera ou ento no nibus.
Ao contrrio de jogos 3D, que exigem muita ateno do jogador, eles so feitos para divertir e passar o tempo. Um bom exemplo o famoso Angry Birds, que conquistou at quem no gosta de games. Recentemente, eu comprei
o game do filme Detona Ralph (Wreck It Ralph), para o iPad, e os vrios
minigames que ele contm so realmente divertidos e viciantes.
O jogo deve ser fcil de jogar, mas isto no quer dizer que ele deva ser fcil
de finalizar. O jogador, geralmente, gosta de nveis crescentes de dificuldade.
O prprio Angry Birds assim.
Ento, eu pensei que o jogo deve ser simples, com jogabilidade fcil e
possvel de rodar em um smartphone ou em um tablet.
Jogabilidade
O jogo simples: voc tem que chutar a bola, tocando-a e arrastando-a
at onde quiser. O ngulo do arrasto e a sua distncia determinaro a direo
e a fora aplicadas na bola. Confesso que fiquei preocupado com este tipo de
ao em um Smartphone, no qual a bola ficaria bem pequena, porm, depois
dos primeiros testes, constatei que no problema.
Eu havia pensado em acionar a bola de outras formas, como usando um
acelermetro, porm, jogadores de smartphone, geralmente, no gostam muito de ficar balanando o aparelho. Tambm pensei em um sistema mais
completo, onde o jogador indicaria o ponto de toque na bola e o ngulo, indicando a fora em um sensor (como os de jogos de golfe), mas ficaria chato
para a maioria dos jogadores casuais.
O resultado ficou bom, sendo que eu testei com vrias pessoas, de idades
diferentes. claro que o pessoal mais velho tem certa dificuldade para jogar
em smartphones, porm apresentam a mesma dificuldade com outros jogos,
como o Angry Birds, logo, para estas pessoas melhor o uso de um tablet.
302
Como usei o prprio framework que desenvolvi at aqui, eu consigo controlar o FPS do game, evitando que ele acelere em dispositivos mais rpidos. Da mesma forma, as imagens so redimensionadas de acordo com o
tamanho da diagonal da tela, sendo geradas usando Mipmaps, o que resulta
em escalas muito boas.
Implementao Bsica
Vamos ver aqui as alteraes bsicas que fiz para ambas as verses.
A verso Android est em: ..\Codigo\GameDroid\byballdroid.zip.
A verso iOS est em: ..\Codigo\GameIOS\ByBall.zip;
Sugiro que voc carregue um ou o outro projeto e estude a implementao.
I18N e L10N
Internacionalizao (I18N) e Localizao (L10N) so aspectos fundamentais de games. Neste game, eu j utilizei o mecanismo de internacionalizao
do Android e do iOS, criando a localizao default em Ingls, acrescentando
Portugus como localizao opcional.
No Android, feito assim:
Strings: colocamos os textos em Ingls dentro de: res/values/strings.xml e a verso em Portugus dentro de: res/values-pt/strings.
xml;
Imagens: colocamos as imagens em Ingls, ou as que no tem idioma, dentro de: res/drawable-mdpi, e as que contm texto em Portugus dentro de: res/drawable-pt-mdpi;
No iOS, bem mais complicado. Primeiramente, como o Game Universal, temos que criar dois Storyboards: um para iPhone e outro para iPad. Depois, temos que repetir o mesmo layout em ambos, incluindo todas as views e
segues. A localizao de strings fcil:
1. Crie um arquivo Localizable.strings;
2. No painel de propriedades (lado direito), selecione Identity e adicione as duas localizaes: Portugus e Ingls;
Para as imagens que devam ser localizadas, faa a mesma coisa. Os recursos localizados ficam em pastas separadas: en.lproj (Ingls) e pt-lproj
(Portugus).
304
int maxOrdem = 0;
for (OGBPGameObject *go in cenaCorrente.objetos) {
if (go.ordem > maxOrdem) {
maxOrdem = go.ordem;
}
if (go.gameobject == GAMEOBJECT_NPC) {
quantidadeNPC++;
306
iOS:
for (OGBPGameObject * go in cenaCorrente.objetos) {
if (go.gameobject == GAMEOBJECT_NPC) {
int qtd = [[qtdOrdem objectAtIndex:go.ordem]
intValue];
qtd++;
[qtdOrdem setObject:[NSNumber numberWithInt:qtd]
atIndexedSubscript:go.ordem];
}
if (go.alinhamento == ALINHAMENTO_SOBRE_OUTRO) {
OGBPGameObject * goBase = [[OGBPGameObject
alloc] init];
goBase.idGO = go.sobre;
int idBase = [cenaCorrente.objetos
indexOfObject:goBase];
goBase = [cenaCorrente.objetos objectAtIndex:idBase];
float yChao = goBase.centro.y + goBase.altura
/ 2;
yChao = yChao + go.altura / 2.0f;
go.centro.y = yChao;
go.yOriginal = go.centro.y;
go.xOriginal = go.centro.x;
go.b2dBody->SetTransform(b2Vec2(go.centro.x,
go.centro.y),
0.0f);
}
}
No iOS, eu utilizei uma segue que vai de cada boto de nvel at o nosso
ViewController de game. Ento, eu uso o mtodo prepareForSegue para
alterar propriedades no ViewController do game:
-(void)prepareForSegue:(UIStoryboardSegue
sender:(id)sender
{
*)segue
308
Note que eu tenho duas outras segues: uma para o boto ajuda e outra
para o boto pontos, que mostram, respectivamente, o help da aplicao
e a pontuao do jogador.
Eu separei o modelo de game em arquivos diferentes, embora pudesse colocar vrias cenas em cada arquivo, considerei que seria mais simples. No
Depois, carregamos o modelo do game e da tela utilizando os nomes informados. Tambm alteramos a carga da cena, para informar o nmero do nvel
desejado.
No iOS j alteramos as propriedades do View Controller.
chutou = true;
trocarAviso(15, true);
310
[self displayMem:5];
world->Step(deltaT,
cenaCorrente.box2d.velocityInterations,
cenaCorrente.box2d.positionInterations);
if (!acabou) {
segundosDeJogo += deltaT;
}
if (segundosDeJogo >= cenaCorrente.limitesegundos)
{
// O jogo acabou
tempoAviso = 3;
[self resetBola];
chutou = YES;
[self trocarAviso:15 forcar:YES];
acabou = YES;
}
if (aviso != nil) {
tempoAviso -= deltaT;
if (tempoAviso <= 0) {
[self trocarAviso: 0 forcar: YES];
}
}
[self tratarColisaoNPC];
if (resetarBola) {
[self resetBola];
}
Colises
Eu poderia identificar as colises entre a bola (Player Object) e as garrafas
(NPCs) diretamente dentro de um ContactListener do Box2D. Porm, eu
no preciso saber quando a bola atingiu uma garrafa, mas apenas se a garrafa
caiu, o que faz com que sua altura seja menor que a altura original. O motivo
que, ao ser atingida, a garrafa pode apenas balanar, sem cair, logo, o que
eu preciso saber se a garrafa caiu e, no, se foi atingida. Em um game do tipo
Shooter seria diferente.
312
endContact(org.jbox2d.dynamics.contacts.
@Override
public void postSolve(org.jbox2d.dynamics.contacts.
Contact arg0,
ContactImpulse arg1) {
@Override
public void preSolve(org.jbox2d.dynamics.contacts.
Contact arg0,
Manifold arg1) {
}
}
iOS
No iOS um pouco mais complicado... O Box2D feito em C++, logo, o
ContactListener tem que ser uma classe C++ , que estenda a classe b2ContactListener. Ento, eu criei uma classe separada no projeto iOS, formada
pelos arquivos Contato.h e Contato.mm (mm a extenso para cdigo
em C++ dentro do projeto).
#import Contato.h
#import OGBPGameObject.h
void Contato::BeginContact(b2Contact* contact) {
OGBPGameObject * id1 = (__bridge OGBPGameObject *)
(contact->GetFixtureA()->GetBody()>GetUserData());
OGBPGameObject * id2 = (__bridge OGBPGameObject *)
314
(contact->GetFixtureB()->GetBody()->GetUserData());
if ((id1.idGO == 1 &&
id2.idGO == 2) || (id1.idGO == 2 &&
id2.idGO == 1)) {
[this->vc bolaChao];
}
contact,
const
void Contato::PostSolve(b2Contact*
b2ContactImpulse* impulse) {
}
contact,
const
Contato::Contato(OGCTViewController * mvc)
this->vc = mvc;
}
Contato::~Contato() {
}
Tem mais uma novidade: tive que criar uma propriedade apontando para o
View Controller, de modo a invocar o mtodo bolaChao. Esta propriedade
passada no momento em que crio a instncia do ContactListener:
if (!mContato) {
mContato = new Contato(self);
}
world->SetContactListener(mContato);
Registro de tempos
Eu precisava de uma maneira de registrar os melhores tempos em cada
nvel, funcionalidade comum em games mveis. Porm, deixei de fora a parte
de Social Gaming, que compartilhar seus pontos com os amigos.
claro que eu poderia usar um banco de dados SQLite para armazenar
a pontuao, porm, para simplificar as coisas, eu usei os mecanismos mais
comuns para armazenamento.
Android
No Android, eu crio um arquivo texto, usando os mtodos openFileOutput() e openFileInput(), da classe android.content.Context. Para isto,
criei uma classe Records.java que lida com a gravao e leitura da pontuao:
public static long[] getNiveis(Context context) {
Records.context = context;
if (Records.niveis == null) {
Records.niveis = loadLevels();
}
return Records.niveis;
}
private static long[] loadLevels() {
long [] levels = new long [Records.MAXLEVELS];
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
for (int x=0; x<Records.MAXLEVELS; x++) {
levels[x] = cal.getTimeInMillis();
}
String nome = records.txt;
try {
FileInputStream arquivo = Records.context.
316
Records.getNiveis(this.
chutou = true;
trocarAviso(16, true);
acabou = true;
long [] niveis = Records.getNiveis(context);
if (diferencaTempo < niveis[cenaCorrente.getNumero()
318
1]
iOS
Eu sei que j parece um bordo, mas: No iOS um pouco mais
complicado...
Eu resolvi usar uma Property List para armazenar a pontuao. Para isto,
criei um arquivo records.plist, dentro do grupo Supporting files:
<?xml version=1.0 encoding=UTF-8?>
<!DOCTYPE plist PUBLIC -//Apple//DTD PLIST 1.0//EN
http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version=1.0>
<dict>
<key>niveis</key>
<array>
<real>0.0</real>
<real>0.0</real>
<real>0.0</real>
</array>
</dict>
</plist>
+(void)updateLevels:(NSMutableArray *)niveis
{
NSError *error;
NSString *errordesc;
NSFileManager* fileManager = [NSFileManager
defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomai
ns(NSDocumentDirectory, NSUserDomainMask, YES);
NSString
*documentsDirectory
=
[paths
objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory str
ingByAppendingPathComponent:@records.plist];
BOOL success = [fileManager fileExistsAtPath:writab
leDBPath];
if (!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle]
resourcePath] stringByAppendingPathComponent:@recor
ds.plist];
success = [fileManager copyItemAtPath:defaultDBPath
toPath:writableDBPath error:&error];
}
NSDictionary *plistDict = [NSDictionary
dictionaryWithObjects:
[NSArray arrayWithObjects: niveis, nil]
forKeys:[NSArray arrayWithObjects:
@niveis, nil]];
NSData *plistData = [NSPropertyListSerialization
dataFromPropertyList:plistDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errordesc];
if(plistData) {
writableDBPath = [documentsDirectory stringBy
AppendingPathComponent:@records.plist];
[plistData writeToFile:writableDBPath
atomically:YES];
}
320
*documentsDirectory
[paths
https://developer.apple.com/library/ios/#documentation/FileManagement/
Conceptual/FileSystemProgrammingGUide/Introduction/Introduction.html
Para apresentar os pontos ao jogador, eu criei uma view, que ao ser invocada, l a Property List e mostra para o usurio (classe BYBLTextoViewController, mtodo viewDidLoad):
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
CGRect rect = [[UIApplication sharedApplication]
statusBarFrame];
[[UIApplication sharedApplication]
setStatusBarHidden:YES withAnimation:UIStatusBarAnima
tionFade];
UIImage *img = [UIImage imageNamed:@splash.png];
CGSize landSize = CGSizeMake(self.view.frame.size.
height, self.view.frame.size.width +
rect.size.width);
UIColor *background = [[UIColor alloc]
initWithPatternImage:
[self resizeImage:img scaledToSize:landSize]
];
self.view.backgroundColor = background;
if (self.isAjuda) {
NSBundle * bundle = [NSBundle bundleForClass:self];
322
}
[texto setText: saida];
324
Como temos muitas texturas, em certos momentos o sistema fica com baixa memria mesmo.
Como resolver este problema?
Existem alguns passos que voc deve seguir, de modo a se certificar que
est utilizando a memria e liberando quando no mais necessria.
Uso do GLKBaseEffect
Neste game, ns carregamos o GLKViewController a cada novo nvel jogado, entrando no modo OpenGL. Ento, a liberao de memria se torna
crtica.
H vrios posts na Internet, especialmente no Stack Overflow (http://
stackoverflow.com/), argumentando que o GLKBaseEffect tem algum tipo de
memory leak, ou seja, no est liberando memria corretamente.
Eu no detectei um Memory Leak especfico do GLKBaseEffect. Se
voc liberar as texturas e os buffers, provavelmente no ter problemas (veremos isto mais adiante). Eu preferi utilizar o OpenGL ES diretamente, pois
quero ter um controle maior do processo de renderizao.
No exemplo \Codigo\OpenGLiOS\OpenGLBasico1.zip, eu uso apenas
as funes do OpenGL ES, criando e compilando Shaders, logo, voc pode
usar este cdigo como base.
Voc ter que criar e compilar os Shaders, da mesma maneira que fazemos
na verso Android. E, alm disto, ter que alterar a maneira como carrega
texturas (eu suspeitei tambm do GLKTextureLoader).
Carregando texturas sem o GLKTextureLoader
Modifiquei significativamente o mtodo carregarCoordenadasTextura,
da classe OGCTViewController, que eu uso para carregar as imagens e passar para a GPU:
- (void)carregarCoordenadasTextura:
props imagem: (NSString*) nome
{
(OGBPGLProps
*)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
CGImageRef spriteImage = CGImageRetain([UIImage
imageNamed:nome].CGImage);
326
Eu carrego a imagem para uma referncia (CGImageRef) e crio um contexto Quartz, que desenha a imagem em uma rea que eu criei (spriteData).
Depois, eu transfiro a imagem para a GPU, com a funo glTextImage2D e
libero o contexto, a imagem e o buffer que aloquei.
Assim, eu garanto que no haver outras reas de memria com a imagem,
alm da utilizada pela GPU.
Renderizando sem o GLKBaseEffect
Ns temos que usar o programa que criamos com os nossos Shaders e passar
os argumentos necessrios, incluindo a matriz MVP (Model-View-Projection):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(_program);
if (cenaCorrente.texturaFundo != nil) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, cenaCorrente.glProps.
textureName);
glBindBuffer(GL_ARRAY_BUFFER, cenaCorrente.glProps.
vobVertices);
glVertexAttribPointer(maPositionHandle, 3,
GL_FLOAT, GL_FALSE,
sizeof(GLfloat) * 3, BUFFER_OFFSET(0));
glEnableVertexAttribArray(maPositionHandle);
glBindBuffer(GL_ARRAY_BUFFER, _textureBuffer);
glVertexAttribPointer(maTextureHandle, 2, GL_
FLOAT, GL_FALSE,
sizeof(GLfloat) * 2, BUFFER_OFFSET(0));
glEnableVertexAttribArray(maTextureHandle);
GLKMatrix4 matrizModelo = GLKMatrix4Identity;
glUniformMatrix4fv(uniforms[UNIFORM_
MODELVIEWPROJECTION_MATRIX], 1, 0, matrizModelo.m);
glUniform1f(uniforms[UNIFORM_FADE_FACTOR],
1.0f);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
Para comear, temos que usar a funo glBindTexture para indicar qual
textura vamos usar, depois, temos que passar os identificadores dos atributos
de posio e textura que criamos nos Shaders (maPositionHandle e maTextureHandle), ao invs do padro que o GLKBaseEffect utiliza (GLKVertexAttribPosition e GLKVertexAttribTexCoord0).
Depois, temos que passar a matriz multiplicada Modelo-Cmera-Projeo
como um uniform para o nosso Shader. E, como estou usando o mesmo
Shader do exemplo de Sistema de Partculas, mantive o uniform que indica
o percentual de transparncia.
328
}
[self deleteBox2D];
self->mapaCaracteres = nil;
self->gameModel = nil;
self->cenaCorrente = nil;
self->modeloTela = nil;
panRecognizer = nil;
tapRecognizer = nil;
telaTopLeft = nil;
telaBottomRight = nil;
view = nil;
bolaBody = nil;
bola = nil;
nomeGame = nil;
nomeTela = nil;
inicio = nil;
qtdOrdem = nil;
aviso = nil;
world = NULL;
330
modeloTela = nil;
Eu vou liberando a memria dos VBOs de vrtices e textura, alm das prprias texturas, de cada objeto do game, incluindo o cenrio de fundo. Depois,
tambm destruo os objetos Box2D que criei.
Finalmente, eu invoco o mtodo deleteBox2D, que libera a parte final
dos objetos Box2D:
- (void) deleteBox2D
{
if (mContato != nil && mContato->vc != nil) {
mContato->vc = nil;
delete mContato;
mContato = nil;
}
if (world != NULL) {
delete world;
}
332
Concluso
O game ficou pronto e rodou bem em ambas as plataformas. Conseguimos
criar um game para Android e iOS utilizando Box2D e OpenGL ES.
Para os que no acreditam, vou provar.
Impresso e Acabamento
*UiFD(GLWRUD&LrQFLD0RGHUQD/WGD
7HO