• Nenhum resultado encontrado

Prototipação do game ��������������������������������������������������

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 o

c

odea

O 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;