Bem, vamos começar realmente o nosso game. Imaginemos uma ideia simples e vamos tentar criar um protótipo com ela, de modo a avaliar se está boa para virar um Game.
Tudo começa com uma ideia. Que tal criarmos um “shooting” game? Afi- nal de contas, “shooting” games são fáceis de criar e, geralmente agradam a todos. Podemos começar 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 distância percorrida, em anos-luz.
Uma dica: se você começar a complicar, pensando coisas do tipo: “já tem muita coisa assim, preciso criar algo diferente...” seu projeto não vai andar. Você vai criar um protótipo do jogo, logo, n�o é o momento de pensar nisso. Depois, você pode fazer as otimizações que desejar.
Um rascunho é tudo o que precisamos
Eu peguei meu iPad e, usando o “Sketchbook”, criei vários desenhos. Eu fui desenhando o que me vinha à mente, sem me preocupar muito com estética ou funcionalidade. O desenho que mais gostei foi o da próxima figura.
A nave se move para cima e para baixo, e os asteroides vêm em sua di- reç�o, pois ela está se movendo para a frente. Existem asteroides “bons” (os brilhantes), que repõem 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 colisão.
Ilustraç�o 14: Rascunho do jogo
Se o seu jogo tiver mais telas ou níveis, você pode ir criando rascunhos para cada um deles. O importante é colocar a idea no papel (digital), antes que você se esqueça.
u
Sando oc
odeaO Codea é um ambiente de programação sensacional. É simples e prático, permitindo criar rapidamente jogos e simulações gráficas. Ele é uma aplicaç�o para iPad, que está disponível na AppStore por cerca de US$ 10,00.
Os pontos positivos do Codea s�o:
1. Tem recursos que facilitam o desenvolvimento rápido de games; 2. Já vem integrado com o engine de física Box2D;
3. Usa a linguagem Lua (www.lua.org), que é simples e pouco verbosa; 4. Vem com Spritepacks (conjuntos de imagens) e outros recursos para
criação de games.
Porém, o Codea carece desesperadamente de documentação decente. Não parece um produto comercial e, certamente, não tem o suporte necessário, em caso de problemas. O site da empresa, “Twolivesleft.com”, é muito minima- lista e n�o tem uma documentaç�o formal. Existem vários tutoriais (alguns criados por terceiros) que explicam rapidamente como ele funciona.
É claro que podemos criar Games com o Codea e distribuir na AppStore, pois ele tem um “Codea Runtime” (https://github.com/TwoLivesLeft/Codea- -Runtime) que permite fazer isto. Porém, quando se trata de criar um produ- to comercial, eu sou muito conservador, pois, em caso de problemas, o meu cliente vai reclamar comigo e eu, como desenvolvedor do Game, não terei a quem recorrer. É por isso que eu não uso o Codea para desenvolvimento, mas apenas para prototipação.
No Codea, podemos criar código-fonte para responder a eventos e para criar classes, que ser�o utilizadas no �ame. A linguagem é a Lua (www.lua. org), criada pela PUC-RJ. Lua é uma linguagem simples, intuitiva e pouco verbosa, cujo aprendizado é extremamente simples. Nós iremos ensinando Lua conforme o protótipo for se desenvolvendo. Aliás, foi assim que eu apren- di a usar Lua. Só para começar, Lua é “canse sensitive” e n�o precisa de ponto e vírgula no final da linha. Os comentários s�o iniciados por “--”.
Um tutorial rápido em Codea
Vamos fazer uma “porcaria” em Codea para você “pegar o jeito”. Comece iniciando um novo projeto, na tela do Codea (Ahn, galera: só funciona no iPad, ok? Se você n�o tem um iPad, leia a prototipaç�o no “Processing”). Escreva um nome qualquer para o projeto, por exemplo: “bola”. Ele vai criar todo o código do projeto, que apenas escreve “Hello world” na saída.
Ilustraç�o 16: O código gerado pelo Codea
Ele gerou um código-fonte muito simples, como podemos ver na figura. O arquivo “Main” é o arquivo principal de sua aplicação Codea, ele contém algumas funções de “Callback”, ou seja, são invocadas pelo engine do Co- dea em determinadas situações. A primeira função é a “setup”, que é chama- da na inicialização. Neste caso, estamos usando a função “print” para gerar uma mensagem na saída da execução. A outra, é a função “draw”, invocada a cada novo frame gerado. Neste caso, ela apenas limpa a tela, com a função “background (<red>, <green>, <blue>)”, e ajusta a grossura da “caneta” para desenhos em 5 pixels, com a funç�o “strokeWidth(5)”.
Se você quiser ver uma referência rápida (algo semelhante a um “Help”), toque no símbolo do Codea na tela principal (É um quadrado verde com dois “C”, um grande e um pequeno).
Como eu rodo o programa?
Vamos ver uma referência rápida dos comandos do editor:
• Seta para a esquerda, no canto superior esquerdo: volta ao início; • Bot�o “+”, no canto superior direito: cria uma nova classe ou um
arquivo vazio;
• Tabs na parte superior da tela: alternam entre os arquivos; • “X” no canto inferior esquerdo: fecha o arquivo;
Clique no bot�o “play”, no canto inferior direito da tela do editor e você irá para o ambiente de execução.
Ilustraç�o 17: O ambiente de execuç�o
O ambiente de execução é bem simples. Ele tem um painel de proprieda- des, um de output (para saída de comandos “print”), e uma barra de controle. A barra de controle está sempre no canto inferior esquerdo, e tem alguns botões:
• Seta para a esquerda: termina o programa;
• Pausa / Play: para ou continua a execuç�o do programa; • Seta curva: reseta o programa, executando do início;
• Máquina fotográfica: tira uma foto instantânea do programa; • Filmadora: filma a execuç�o 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
-- 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 x = x + 1 if x > WIDTH then x = 0 end sprite(“SpaceCute:Rocketship”,x,y) end
O resultado da execução é um foguete que sai do canto esquerdo da tela e vai até o canto direito, recomeçando quando atinge o fim.
Ilustraç�o 18: O foguete se move pela tela
Vamos ver o que mudamos. Para começar, criamos duas variáveis, “x” para posição horizontal e “y” para vertical. Note que o valor de “y” é a metade da tela (HEIGHT / 2) fixamos a orientaç�o em “portrait”, pois queremos que
o iPad seja segurado “em pé”. Faz sentido, pois os asteroides ficar�o mais próximos. Depois, retiramos os painéis que aparecem do lado esquerdo com a funç�o “displayMode(FULLSCREEN)”.
Sempre que for necessário criar um novo frame, a função “draw” será in- vocada. E, neste momento, ela vai apagar a tela e desenhar um “sprite” (uma imagem) de uma das coleções de sprites que já vem com o Codea. É um pe- queno foguete com um tripulante. Ele vai desenhar sempre na posição indica- da pelas variáveis “x” e “y”. O Codea usa a orientação cartesiana correta, com o ponto inicial (0,0) no canto inferior esquerdo.
A cada chamada da função “draw”, vamos incrementando o valor de “x”. Quando ele se torna maior que a largura da tela (WIDTH), nós voltamos ao valor zero.
Criando o protótipo do “AsteroidNuts” no Codea
Agora, que já entendemos um pouco do Codea, vamos tentar criar nosso protótipo. Para começar, temos dois tipos de �ame Objects: Nave e Asteroide. A nave é um Player Object, ou seja, controlado pelo usuário, e o asteroide é um NPC.
Todo o código-fonte pode ser baixado, logo, não é necessário digitar os
comandos. Veja na “Introduç�o”. Tem um arquivo “Asteroid.txt” (“..\Codigo\
AsteroidNuts_Codea\Asteroid.txt”) com a classe toda.
N�o importa se temos dois tipos de asteroides (bom e ruim), pois s�o ape- nas variações de propriedades de um asteroide.
Nossa classe tem que armazenar os atributos de posição do Asteroide, além do sei tipo (“bom” ou “ruim”). O Codea usa a API Nspire (http://www.inspired- -lua.org/2011/05/5-object-classes/), que facilita a criaç�o de classes. Para criar uma nova classe, abra o editor do projeto e clique no bot�o “+”, que fica no canto superior direito. Escolha “Create new class” e dê o nome “Asteroid”. Você vai notar que já tem alguns comandos:
Asteroid = class()
function Asteroid:init(x)
-- you can accept and set parameters here self.x = x
function Asteroid:draw()
-- Codea does not automatically call this method end
function Asteroid:touched(touch)
-- Codea does not automatically call this method end
A linguagem Lua não suporta a criação de classes diretamente, porém como sendo uma linguagem baseada em protótipos, permite modificar o com- portamento e característica de objetos dinamicamente, imitando o conceito de classes. A API Nspire tem a funç�o “class()” que facilita a criaç�o de classes. Neste caso, criamos um objeto “Asteroid” e acrescentamos a ele uma proprie- dade (“x”) e três métodos (“init”, “draw” e “touch”).
O método “init” será invocado sempre que quisermos pegar um novo obje- to da nossa classe, por exemplo:
ast1 = Asteroid(10)
Os métodos “draw” e “touched” são dicas que deveríamos implementar estes métodos e invocá-los, quando necessário. O Codea inseriu comentários indicando que estes dois métodos não serão invocados automaticamente.
Para começar, vamos trabalhar nosso construtor (o método “init”). Primei- ramente, temos que pensar em como iniciar o Asteroide. Cada Asteroide deve- rá surgir no canto direito da tela, em uma altura variável. Haverá uma faixa de valores no eixo das ordenadas (Y), onde os asteroides poder�o surgir. Eu n�o quero usar a tela toda, pelo menos neste protótipo. Ent�o, faz sentido passar- mos em passar os limites para o construtor da classe, de modo que este possa gerar um asteroide dentro da faixa esperada.
function Asteroid:init(upper,lower)
-- you can accept and set parameters here local tipo =math.random(1,100)
self.position = vec2(100,50) self.dimension = vec2(50,50) self.position.x = WIDTH - 100 self.position.y = math.random(upper,lower) if tipo > 80 then self.type = 2 else self.type = 1 end end
O prefixo “Asteroid:” indica que este é um método a ser atribuído aos objetos do tipo “Asteroid”, e o sufixo “init” é o nome do método. O prefi- xo “self.”, em alguns comandos de atribuição, se refere ao objeto da classe “Asteroid”, logo, “self.type = 2” significa que estamos atribuindo “2” à pro- priedade “type”, do objeto. Se ela não existir, será criada automaticamente.
Lua é uma linguagem de tipagem dinâmica, ou seja, cada variável tem seu tipo atribuído dinamicamente (e pode mudar). Passamos dois parâmetros para o construtor: “upper” – limite superior no eixo das ordenadas, e “lower” – li- mite inferior. Nós criamos três propriedades no construtor:
• “position”: do tipo “vec2” (do Codea), que retorna um vetor com coordenadas bidimensionais (“x” e “y”). Armazena a posiç�o atual do asteroide;
• “dimension”: igualmente vec2, armazena a largura e altura da caixa que contém a figura do asteroide;
• “type”: do tipo “number” (Lua), que armazena o tipo do asteroide, se ele é “bom” (type = 2) ou “ruim” (type = 1). É claro que isto po- deria ser resolvido melhor por hierarquia ou composição, mas é um protótipo, logo, é descartável;
Nossa estratégia para decidir se vamos criar um asteroide “bom” ou “ruim” é apenas aleatória. Poderíamos levar alguns fatores em consideraç�o, como o nível de energia do jogador e a quantidade de acertos que ele tem, mas, para efeito de protótipo, vamos deixar assim. �eramos um número aleatório entre 1 e 100 (math.random(1,100)) se ele for maior que 80, nós o transformamos em “bom”.
Inicialmente, calculamos a posiç�o horizontal (abscissas) em WIDTH – 100. Depois vou explicar o motivo, mas está relacionado com o tamanho do asteroide, que é 50. E calculamos a posiç�o vertical (ordenadas) com um nú- mero aleatório entre o limite inferior e o superior (math.random(upper,lower)). Agora, demos que desenhar um asteroide. O melhor local para fazer isto é no método “draw”. Nós invocaremos o método “draw” sempre que desejar- mos redesenhar um objeto Asteroid específico.
function Asteroid:draw()
-- Codea does not automatically call this method spriteMode(CORNERS)
if self.type == 2 then
sprite(“Tyrian Remastered:Energy Orb 1”, self.position.x,
self.position.y,
self.position.y+self.dimension.y) else sprite(“Tyrian Remastered:Eggstroid”, self.position.x, self.position.y, self.position.x+self.dimension.x, self.position.y+self.dimension.y) end end
A primeira coisa que fizemos foi mudar o modo de desenhar um “sprite”, que é uma imagem carregada na tela. Por default, o Codea sempre considera a posiç�o de um sprite (coordenadas “x” e “y”) como o centro da imagem. Eu prefiro trabalhar com os cantos e especificar o tamanho da imagem, ent�o eu mudei o modo de desenho para CORNERS (spriteMode(CORNERS)), que significa: “x” e “y” ser�o o canto inferior esquerdo da “caixa” que contém o sprite, e os parâmetros “w” e “h” serão, respectivamente, as coordenadas do canto superior direito da mesma.
Se for do tipo “2”, é um asteroide “bom”, logo, eu uso um sprite da biblio- teca do Codea, que representa uma esfera brilhante. Eu uso a função “sprite” para desenhar a figura do asteroide na tela, na posiç�o especificada (proprie- dade “position”) e do tamanho especificado (propriedade “dimension”). O pri- meiro parâmetro é o nome da figura, o segundo e o terceiro, as coordenadas do canto inferior esquerdo, e o quarto e o quinto, as coordenadas do canto superior direito.
Finalmente, temos dois métodos “finalx” e “finaly”, que retornam as coor- denadas do canto superior direito do asteroide, para facilitar seu uso.
Agora, precisamos representar a nave (arquivo “ship.txt”). Vamos criar uma classe da mesma maneira. Em seu método “init”, nós criamos as mesmas propriedades do asteroide, exceto o “type”. Mas colocamos o “lower” e o “upper” como propriedades da nave. Veja o método “init”:
function ship:init()
-- you can accept and set parameters here self.position = vec2(100,80)
self.dimension = vec2(100,50)
self.position.y = (HEIGHT - self.dimension.y)/2 self.upper = self.position.y - 4* self.dimension.y self.lower = self.position.y + 5 * self.dimension.y
O método “draw” é mais simples:
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
Desenhamos da mesma maneira que o asteroide. E a nave também tem os métodos “finalx” e “finaly”. Na verdade, ambos (Asteroid e ship) poderiam ser derivadas de uma classe ancestral comum “GameObject”, mas eu não quis complicar as coisas, pois é um protótipo. Um método “callback” importante é o “touched”, que será invocado quando o usuário tocar na tela.
function ship:touched(touch)
-- Codea does not automatically call this method if touch.y >= self.upper and touch.y <= self.lower then self.position.y = touch.y
end end
Eu simplesmente pego a altura do toque do jogador e posiciono a nave nela. Note que não é necessariamente o toque na nave, mas na tela toda. Eu poderia sofisticar este controle, mas, como é um protótipo, eu preferi deixar tudo mais simples. Na verdade, quem invoca este método é o meu arquivo “Main”, já que ele n�o será invocado automaticamente pelo Codea (veja o comentário no início do método “touched”.
Agora, chegou o momento de criar a mecânica do jogo. Eu poderia ter usado os recursos de física cinética do Codea (fornecidos pela biblioteca Bo- x2D), com o objeto “body”. Assim, toda a parte de movimento e colis�o seria automática. Porém, considerei que o protótipo ficaria complexo demais e, na verdade o jogo é muito simples na parte de física. Então, eu mesmo criei a física necessária para movimentar e verificar colisões.
No arquivo “Main”, eu crio algumas variáveis globais para controle do jogo, as quais nem vou mencionar diretamente, exceto duas:
• “asters = {}”; • “nave = nil”;
A variável “asters” é do tipo “table”, que é um array dinâmico e associativo em Lua. E a variável “nave” é um objeto, inicializado com valor nulo (“nil”). A table “asters” vai armazenar os asteroides ativos em determinado momento (aqueles que n�o chegaram ao fim do caminho: o canto esquerdo da tela), e a variável “nave” vai representar o objeto “ship”.
A função “setup” será invocada automaticamente pelo Codea, e inicializa as variáveis do game. function setup() displayMode(FULLSCREEN) upperlimit = (HEIGHT - 50) / 2 - 200 lowerlimit = (HEIGHT - 50) / 2 + 250 nave = ship() lightyears=0 energy=200 contador=0 fimjogo=0 posdec=1 limrandom=60 inirandom=1 gatilho= 53 interval=12 lastaster=0 maxasters=10 limitdec=60 end
Veja na linha em negrito como eu crio uma instância de uma classe, neste caso, a nave. O método “init” da classe “ship” será invocado e vai devolver uma instância já inicializada, que pode ser referida através da variável “nave”.
A função “touched” será invocada sempre que o jogador tocar na tela. O parâmetro que ela recebe (“touch”) contém, entre outras coisas, as coordena- das de onde o jogador tocou na tela. Eu repasso isso para o método “touch” da nave.
A função “draw” é um pouco extensa (e poderia ser refatorada), mas eu tenho que comentar algumas coisas sobre ela, afinal é o “�ame 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”)
fill(121, 249, 16, 255) fontSize(60) textMode(CORNER) text(“AsteroidNuts”, 10, HEIGHT-65) fill(91, 137, 229, 255) text(string.format(“Light years: %d”, lightyears),10,HEIGHT-120) text(string.format(“Energy: %d”,energy), 10, HEIGHT-185) if fimjogo==1 then fim() else contador = contador + 1 if contador >= limitdec then lightyears = lightyears + 10 energy = energy - 1 if energy <= 0 then fim() end contador=0 end newaster() nave:draw() if lightyears > 200 then posdec = 2
elseif lightyears > 500 then interval=6
posdec=10 maxasters=20
elseif lightyears > 1000 then limrandom=3 gatilho=1 interval=1 posdec=40 maxasters=30 limitdec=40 end
for i,v in ipairs(asters) do if v.position.x < 0 then table.remove(asters,i)
currentasters = currentasters - 1 else
v.position.x = v.position.x - posdec v:draw()
if asteroidhit(v) == 1 then
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
A funç�o “draw” será chamada automaticamente pelo Codea a cada 60 hertz (1/60 de segundo). Eu tenho que verificar se acrescento mais asteroides e movimentar os que já existem, além de verificar se a nave colidiu com algum deles. Também verifico se os asteroides chegaram ao fim do seu caminho.
A primeira coisa é incrementar o contador de “anos-luz” (lightyears) e de- crementar o de energia, afinal, a nave se movimentou. Eu uso uma funç�o “newaster()” para verificar se tenho que criar mais um asteroide (“newas- ter()”), e depois eu redesenho a nave, invocando seu método “draw”.
function newaster()
if math.random(1,limrandom) > gatilho then if currentasters < maxasters then
if lastaster >= interval then ast = Asteroid(upperlimit,lowerlimit) table.insert(asters,ast) currentasters = currentasters + 1 lastaster=0 else lastaster = lastaster + 1 end end end end
Esta funç�o “newaster” cria e insere um novo asteroide (“table. insert(aster,ast)”) se as seguintes condições forem atingidas:
• �atilho aleatório maior que 53, para dar um toque de imprevis�o ao jogo;
• O número de asteroides ativos for menor que 10;
• O intervalo entre os asteroides for maior ou igual ao intervalo esta- belecido (só pode lançar um novo depois de x asteroides);
Na verdade, esta função faz parte da macro função de Estratégia do jogo, e deve ser bem elaborada para aumentar a jogabilidade. Mas, como é um protó- tipo, eu deixei assim mesmo.
Depois, eu criei um “loop” para movimentar os asteroides, verificando se houve colis�o de algum deles com a nave:
for i,v in ipairs(asters) ...
end
A tabela “asters” armazena os asteroides como um vetor dinâmico comum. Para percorrer esta tabela, usamos a forma de comando “for” com a função “ipairs(tabela)”. A cada iteraç�o, o valor de “i” e “v” apontar�o, respectiva- mente, para a posição do asteroide e para o objeto asteroide e eu posso refe- renciá-lo diretamente:
v.position.x = v.position.x - posdec
A primeira coisa que eu faço no loop é verificar se o asteroide chegou ao fim do caminho, removendo-o da tabela:
if v.position.x < 0 then table.remove(asters,i)
currentasters = currentasters – 1 ...
Não dá problema algum remover um elemento durante a iteração.
Se o asteroide ainda n�o chegou ao fim, eu decremento sua posiç�o no eixo das abscissas, e verifico se houve colis�o com a nave (funç�o “asteroidhit”). Se houve, eu verifico se era um asteroide “bom” ou “ruim”, tomando a atitude correta (incrementar ou decrementar energia). Note que eu toco sons diferen- tes, dependendo do tipo de asteroide com o qual a nave colidiu. Para tocar um som, o Codea tem a funç�o:
sound (nome, raiz)
O parâmetro “nome” indica o nome do som a ser tocado, e a “raiz” o modi- fica aleatoriamente. Você deve experimentar com este segundo parâmetro 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 sequência de comandos abaixo:
• font(nome da fonte): troca a família tipográfica (a fonte) dos caracteres;
• fill(red, green, blue, alfa): estabelece a cor para a escrita;