Como criar um jogo para Android com Corona SDK (Parte 2)

Seleção de Personagem

Atualizado em 03/07/2017!

Então quer dizer que você completou com sucesso todas as tarefas da primeira etapa deste mega tutorial de como criar um jogo para Android com Corona SDK?

Meus parabéns!

Mas não respire aliviado ainda cowbow, pois muitos desafios o aguardam se espera um dia se tornar um desenvolvedor de games!

Nesta segunda parte do tutorial você vai ver:

  1. Seleção de Personagens
  2. Orientação à Objetos
  3. Joystick e Botões
  4. Conclusões e Futuro

Parte 1: Seleção de Personagens

Embora nos exemplos anteriores tenhamos apenas oferecido o Cormano como única opção de cowboy, sabemos que ele não é unanimidade entre os gamers mundo afora. Eu particularmente gostava de jogar com o Bob e suas espingardas (com largura de tiro maior), enquanto meus amigos curtiam o xerife Billy (com maior velocidade de disparos).

Como próximo passo, criaremos uma tela de seleção de personagem. Seguindo as boas práticas de programação Lua que começamos a criar com a tela de splash screen criaremos um arquivo Lua para cada tela do game, então, crie um playerselect.lua e coloque lá o código abaixo:

Uma breve explicação quanto a este código pode ser resumida no seguinte: para cada estado possível da tela, e são 4 (um para cada personagem) temos uma imagem diferente com o personagem realçado, ou seja, removemos a imagem anterior e carregamos a nova imagem na tela, dando a impressão de seleção de personagem, assim como no jogo original.

Você pode acessar essas quatro imagens da seleção e personagem neste zip aqui (é o mesmo zip de imagens de sempre).

Ao ser tocada a primeira vez, a tela realça o personagem e guarda seu nome na variável selected, no segundo toque no mesmo personagem, a tela é dispensada e a função de callback é disparada, conforme configurarmos mais adiante.

As funções deste arquivo servem para expor alguns valores para os demais arquivos do projeto (principalmente o main.lua), como por exemplo, para o main.lua saber quem foi o personagem escolhido. Usaremos ela mais tarde.

Agora, para que a tela de seleção de personagem apareça logo após a splash screen, primeiro devemos incluir esta linha como primeira no arquivo main.lua:

Depois, adicione esta linha como penúltima linha do arquivo, logo antes da chamada à tela de splash. Isso vai fazer com que a tela de player select apareça depois que a tela de splash suma:

Agora por fim, devemos configurar a função de callback do nosso objeto playerSelect, para que ele saiba o que fazer quando um personagem for selecionado. Neste caso, ele deve esconder a tela de player select e exibir a tela de jogo, mas mostrando o sprite do personagem que selecionamos.

Xiiii, isso vai dar trabalho…

Parte 2: Orientação à Objetos

Lua não é orientado à objetos. É uma linguagem simples de scripting que é perfeita para criação de lógica de jogos. Entretanto, quando se programa há anos como eu e sabe-se que bons projetos não vão longe sem um mínimo de arquitetura e por isso você já deve ter notado que estou estruturando melhor os arquivos deste projeto em pastas, arquivos, etc.

Dá uma olhada na forma final que o projeto vai ter, e já vai vendo se está fazendo o seu do jeito certo:Estrutura de pastas do projeto

Desta forma, um conceito importantíssimo para criação de bons projetos são os conceitos de orientação à objetos que entre uma de suas premissas está o fato de que não se deve repetir código. Mas se todos personagens pulam, correm e param, como que não repetiremos esse código?

A ideia é simples, vamos criar um arquivo chamado character.lua e nele vamos colocar tudo o que é comum aos personagens, como segue:

Note que este arquivo carrega os sprites de maneira dinâmica, conforme o nome que for passado por parâmetro. Isso porque cada personagem terá suas próprias sprites, ou seja, só o que muda entre os personagens é seu nome (as spritesheets possuem os nomes dos personagens) e a largura e altura das sprites. O resto é exatamente igual.

A ideia é que todos os personagens vão carregar esta biblioteca em uma variável e chamar suas funções para carregar as sprites e poder correr, pular, etc, como veremos a seguir para o personagem Billy (imagens disponíveis neste zip, o mesmo de sempre):

Billy Galopando

Note a simplicidade de criar um novo personagem usando esta arquitetura de software simplíssima baseada em Orientação à Objetos, ao criarmos o personagen Steve:

Steve galopando

Não postarei o código dos demais personagens pois acredito que sejam capazes de deduzir por vocês mesmos, pois muda-se pouquíssima coisa. Note que o que fizemos foi criar uma espécie de herança da orientação à objetos de uma forma tosca mas que funciona.

Agora que temos todos os personagens criados, vamos recriar novamente nosso main.lua para que se adeque à nova realidade. Use o código abaixo para o seu main.lua, para pouparmos tempo:

De grande diferença aqui, reduzimos a quantidade de linhas de código ao usar uma variável personagem que carrega o arquivo do personagem correspondente baseado no personagem selecionado na tela de player select. Fora isso, apenas uma série de códigos tiveram seus lugares alterados por causa da nova organização.

O que queremos a seguir, é que fique como na imagem abaixo.

Sunset Riders Lite com Cenário, Joystick e Botões

Parte 3: Joystick e Botões

Agora temos algumas adicionais que nos levarão a estudar duas bibliotecas novas: ui.lua e joystick.lua (todas as imagens, incluindo os botões e joysticks estão na pasta de imagens, enquanto que estas duas bibliotecas estão nesse zip e devem ficar na raiz do projeto).

A biblioteca ui.lua lhe permite criar botões de forma muito mais fácil e é largamente utilizado em projetos Lua. O uso do arquivo ui.lua neste game é para criar dois botões, o de pular (que dispara a função de salto do personagem) e o de atirar, que ainda não tem utilidade mas que no futuro poderia disparar projéteis com as armas dos personagens.

Este código deve ir no main.lua. Apenas pegue os fontes acima e coloque em uma posição anterior à primeira vez que você chamar a função configuraButtons, que deve ser no método start do main.lua. Eu escolhi colocar no topo do main.lua, logo abaixo da variável personagem.

Note que as imagens para os botões são duas: a normal e a de botão pressionado. Além disso, os botões disparam uma função quando selecionados, funções estas definidas nas linhas anteriores ao carregamento dos mesmos.

Note também que na função de pulo mandamos uma variável personagem pular, sem nem mesmo saber qual personagem que estamos jogando, o que é indiferente para nós. Em orientação à objetos poderíamos dizer que emulamos uma interface ou classe abstrata.

Outra biblioteca importante que usaremos hoje é a biblioteca joystick.lua (que se encontra neste zip), que cria um direcional analógico assim como o visto em controles de Playstation e Xbox. Esse joystick usa duas imagens existentes em nossa pasta de imagens e dispara um evento onMove que incluiremos no final do arquivo character.lua. Isso porque independente de personagem, ele se moverá da mesma forma, não é mesmo?

Agora, para usar o joystick.lua, use o seguinte código no início do seu main.lua (antes de você chamar essa função configuraJoystick pela primeira vez):

Olhando este código não é muito difícil de imaginar para que servem as propriedades que configuramos, sendo que as principais delas são as imagens que formam o joystick e a função que vai ser executada toda vez que o joystick for utilizado (neste caso, a onMove do personagem).

Para que o joystick apareça na tela, basta chamar a função configuraJoystick na função start do main.lua, assim como você fez com a configuraButtons. No final, sua função start deve estar se parecendo com a abaixo:

Se você testar agora, verá que tudo parece estar funcionando, mas que o joystick não está. Isso porque falta fazermos um ajuste no topo do nosso main.lua. Adicione as seguintes duas linhas:

Primeiro escondemos a barra de status e depois permitimos multi-touch no app para que seja possível usar o joystick e os botões ao mesmo tempo.

[ESTE POST ESTÁ EM MANUTENÇÃO PARA ATUALIZAÇÃO COM A VERSÃO MAIS RECENTE DO CORONA SDK. AINDA NÃO ATUALIZEI O RESTANTE DO POST. A BIBLIOTECA DE JOYSTICK PAROU DE FUNCIONAR E ESTOU BUSCANDO OUTRA.]

Continuando:

Algumas variáveis e constantes para definir a linha base de onde o personagem inicia o jogo, a escala dos trilhos, a posição XY dos textos, trilhos, a velocidade do fundo, o número de milhas percorridas pelo cowboy (sim, um dia a fase irá acabar) o tamanho do percurso em milhas, a música de vitória, a música de fundo, o canal da música de fundo (para poder desligá-la depois), os dois botões e o tempo atual do sistema, para sincronizar as atualizações dos trilhos.

Mais código:

Aqui montamos os trilhos de maneira bem simples, assim como havíamos construído antes. A diferença é a função que evita repetição de código.

Aqui temos uma função para montar os textos que usaremos na aplicação. Use esta função para exibir quaisquer textos no formato padrão do nosso jogo, basta ter as imagens corretas n pata de imagens do projeto.

Agora vamos mover os elementos:

Este código é chamado a cada atualização de tela do dispositivo e redesenha o cenário conforme o tempo do sistema. Além disso, conta quantas vezes o cenário já foi desenhado por completo para contar as milhas do projeto e dizer se o cowboy já chegou no final do projeto ou não, neste caso tocando uma música de vitória.

No último comentário é onde deve ser colocado o código do joystick, sem o comando require que já foi chamado antes no início do arquivo. Onde diz botões, deve ser colocado o código dos botões sem o comando require. Vamos ao último trecho de código do main.lua:

Este trecho define a função de início de jogo, define a função de callback da tela de seleção de personagem (uma função de callback é uma função que será executada quando a tela de seleção de personagem for fechada) e o gatilho para o evento enterFrame, que é chamado uma vez para cada refresh da tela (o que depende do framerate do dispositivo, que pode variar de 30 a 60fps).

E por fim, configura a tela de seleção de personagem (desenha) e carrega a splash screen (desenha-a também). Note que as primeiras telas do jogo são as últimas a serem desenhadas.

Os arquivos build.settings e config.lua ainda são os mesmos do último projeto de exemplo e não serão repetidos.

Pronto, nosso jogo está pronto para ser executado e até mesmo ter uma versão compilada para ser instalado em um dispositivo de verdade. Você pode baixar o APK desse projeto para testar em seu Android usando este link!

Sunset Riders Lite 0.1 lançado!

Parte 4: Conclusões e Futuro

Espero que tenham gostado deste post que ficou muito extenso pois acabei me empolgando no desenvolvimento do game. Acredito que servirá para saciar a vontade do pessoal que queria ver coisas como botões e joysticks.

Para o futuro penso que você poderia adicionar uma barra de progresso enquanto o jogo está sendo inicializado, uma animaçãozinha de abertura, alguns efeitos sonoros adicionais, tiros e alguns obstáculos a serem saltados pelos personagens para dar algum sentido à fase.

Mas este é um desafio para você, cowboy!

* OBS: curtiu o post? Então dá uma olhada no meu livro de Corona SDK clicando no banner abaixo pra aprender a criar outros tantos apps incríveis!

Livro Corona SDK
Livro Corona SDK

Introdução à linguagem de programação Lua

lua
Lua é uma linguagem de programação de extensão desenvolvida para suportar programação procedural geral com facilidades de descrição de dados. Ela foi projetada para ser uma linguagem de scripting poderosa e leve para qualquer programador usar. Conheci Lua em 2010, quando comecei a programar para Corona SDK, o meu framework mobile multiplataforma favorito. É uma linguagem incrivelmente simples e poderosa para criar a lógica de jogos, sendo usada não apenas por desenvolvedores independentes como eu, mas por grandes empresas como EA e Blizzard em seus jogos.

Muitos desenvolvedores que compraram o livro que traduzi sobre Corona SDK me pediram um apêndice em Português sobre o básico de Lua, e como não tenho o cadastro de todos para enviar uma versão atualizada do livro, resolvi publicar aqui um post bem extenso que cobre todo o básico dessa linguagem de programação para que usem como referência de consulta. O próprio livro ensina esses conceitos ao longo de suas páginas, mas aqui agrupo todos os ensinamentos em um só lugar e espero que se torne mais fácil de tirar suas dúvidas.

Convenções

Em Lua os nomes (também chamados de identificadores) podem ser qualquer conjunto de letras, números e underscore, desde que não comece com número, coincidindo com a definição do que é um nome em muitas linguagens. Esses nomes e identificadores são usados em variáveis e campos de tables (que veremos mais adiante).

As seguintes palavras-chave são reservadas e não podem ser usadas como nomes:

Os seguintes caracteres denotam tokens, símbolos especiais da linguagem:

Lua é uma linguagem case-sensitive, ou seja, diferencia maiúsculas e minúsculas, ou seja, and e AND são coisas diferentes pra ela, então tome cuidado. Como convenção, nomes começando com um underscore seguido de letras maiúsculas (ex: _VERSION) são reservadas para variáveis globais internas usados pela própria linguagem.

Um comentário começa com dois traços (hífens) antes do texto que desejamos comentar e “comentam” a linha inteira. Você também pode comentar um bloco completo de código cercando-o com –[[ e –]]. Para descomentar o mesmo bloco, simplesmente adicione outro hífem no início do bloco de comentário, como em —[[.

Um valor numérico constante pode ser escrito com uma parte decimal opcional ou mesmo um expoente decimal. Lua também aceita constantes hexadecimais inteiras, prefixando-as com 0x. Exemplos de valores numéricos válidos são:

Tipos de Valores

Lua é uma linguagem de tipagem dinâmica. Isto significa que as variáveis não possuem tipos; somente valores possuem. Não existem definições de tipo nesta linguagem. Todos valores carregam seu próprio tipo.

Todos valores em Lua são valores de primeira classe. Isto significa que todos valores podem ser armazenados em variáveis, passadas como argumentos para outras funções e retornados como resultado.

Os tipos básicos que você deve ter em mente são:

  • nil — o tipo do valor nil, cuja principal diferença é ser diferente de qualquer outro valor; usualmente representa a ausência de um valor útil.
  • boolean — o tipo dos valores false e true. Os valores nil e false em uma condição tornam-a falsa; qualquer outro valor é true.
  • number — representa números reais (com ponto-flutuante de dupla precisão).
  • string — representa arrays de caracteres. Lua trabalha apenas com caracteres de 8-bits.
  • function — veja mais adiante.
  • table — o único mecanismo para estrutura dados em Lua. Veja mais adiante.
    Lua fornece conversão automática entre strings e números em tempo de execução. Qualquer operação aritmética aplicado a uma string tenta converter a string para um número, seguindo as regras normais de conversão. No entanto, toda vez que um número é usado onde uma string deveria ser usada, ele será convertido também. Para um controle maior de como os números se tornam strings, use a função string.format da biblioteca string.

Tables

Tables são a única estrutura de dados em Lua. Elas implementam arrays associativos, significando que os arrays podem ser indexados não somente pelas suas posições (índices), mas por qualquer valor exceto nil. Tables são heterogêneas e podem conter valores de qualquer tipo exceto nil.

Para representar propriedades, Lua usa o nome do campo como um índice. A linguagem suporta esta representação fornecendo a.nome como o “syntax sugar” a[“nome”].

Como índices, o valor de um campo de uma table pode ser de qualquer tipo exceto nil. Devido ao fato de functions serem valores de primeira-classe, você pode guardar functions em tables também, embora tables não possam carregar métodos (ver adiante).

Tables são objetos: variáveis que não verdade não contém seus valores, somente referências para eles. Associação, passagem de parâmetros e retorno de funções sempre manipulam referências como valores; estas operações não implicam em nenhum tipo de cópia.

Para inicializar uma variável como sendo uma table usando chaves: {}.

Acessando registros

No exemplo acima, o registro com o nome “x” foi acessado de duas maneiras: como uma propriedade usando o operador ‘ponto’ t.x e como um índice de array usando t[“x”].

Um erro comum é confundir t.x com t[x]. O primeiro é equivalente ao t[“x”]: uma table indexada pela string “x”. O segundo é uma table indexada pelo valor da variável x.

Importante

Muitas APIs do Corona retornam objetos. Você pode manipular as propriedades documentadas destes objetos assim como faria com qualquer propriedade de table. Você pode até mesmo adicionar suas próprias propriedades desde que não use um underscore no início delas, pois isso é reservado pelo Corona.

Variáveis

Variáveis são locais em memória para adicionar valores. Existem três tipos de variáveis em Lua: global, local e campo de table. Qualquer variável não-inicializada é por padrão nil.

Global
Variáveis globais não precisam de declaração. Você simplesmente associa um valor para criá-la:

Variáveis globais existem enquanto sua aplicação está rodando. Você pode excluir uma variável global atribuindo nil à ela.

Local
Variáveis locais são declaradas usando a palavra local:

Diferente de variáveis globais, variáveis locais são visíveis somente no bloco onde elas estão declaradas. O escopo de uma variável local começo logo após sua declaração e termina no final do bloco.

Campos de Table
Campos de table são somente elementos da tabela em si. Você indexa-os dentro de um array para atribuir valores à um campo.

Expressões

Operações Aritméticas
Lua suporta os operadores de aritmética binária básica:

Se os operandos são números ou strings que podem ser convertidos para números, todas operações fazem exatamente o que você imagina que elas façam.

Operadores Relacionais
Os operadores relacionais de Lua são

Estas operações sempre resultam em true ou false.

Quando temos strings e/ou números como operandos, o operador de igualdade (==) primeiro compara os tipos dos mesmos. Se forem diferentes, o resultado é false. Caso contrário, os valores são então comparados. Já objetos são comparados pela referência, ou seja, apenas resultam em true se ambos forem o mesmo objeto, afinal, cads novo objeto criado é completamente diferente de todos os outros, do ponto de vista de referência.

A regra de conversão automática citada mais cedo neste post não se aplica à comparações de igualdade, ou seja, “0” == 0 retorna false.

Operadores Lógicos
Os operadores lógicos em Lua são and, or, e not. Todos operadores lógicos consideram false e nil como false e qualquer outra coisa como true.

  • and — o operador de conjunção retorna seu primeiro argumento se ele for false ou nil, caso contrário retorna seu segundo argumento.
    or — o operador de disjunção retorna seu primeiro argumento se seu valor for diferente de nil e false; caso contrário retorna seu segundo argumento.
    not — o operador de negação retorna o oposto do valor do argumento.

Tanto and quanto or são curto-circuitados, ou seja, somente analisam o segundo argumento se necessário.

Concatenação
A concatenção de string em Lua é denotada por dois pontos seguidos (..). Se ambos operandos forem strings ou números, eles serão convertidos para strings de acordo com as regras de conversão.

Operador length
O operador length é denotado pelo operador unário #. O length de uma string é seu número de bytes – sendo que cada caracter tem o tamanho de 1 byte.

O length de uma table é definido por qualquer índice n tal que t[n] não seja nil e t[n+1] seja nil; mais do que isso, se t[1] é nil, n pode ser zero. Idealmente um array não deve conter valores nil, caso contrário, o operador length pode se comportar de maneiras inesperadas.

Precedência
A precedência de operadores em Lua segue a seguinte tabela, do menor para o de maior prioridade:

Como usual, você pode usar parênteses para mudar a precedência de uma expressão. Os operadores de concatenação (..) e exponenciação (^) computam primeiro o elemento da direita. Os demais operadores começam pela esquerda.

Funções

Funções simples funcionam exatamente como esperado: você fornece argumentos como entradas (entre parênteses), a função executa algumas tarefas, e os resultados podem ser retornados.

A seguir, maneiras comuns de declarar funções:

Functions podem ser variáveis, logo uma table pode armazená-las como propriedades. Isto permite usos muito flexíveis para tables. Ela pode ser usada pata agrupar logicamente uma família de funções, por exemplo a biblioteca math. Neste caso, para calcular o seno de 100, você pode escrever math.sin(100). Note que math é uma mera table, e a propriedade sin é a função na verdade.

Métodos em Objetos
Objetos em Lua são representados por tables. Objetos de tela (retângulos, imagens, etc) e o objeto global Runtime são todos objetos. Como a biblioteca math, estes objetos armazenam métodos de instância como propriedades. Uma diferença importante é a sintaxe de métodos vs functions: você usa dois pontos entre o objeto e seu método (:) ao invés do ponto tradicional das functions.

Regras de Escopo

Lua é uma linguagem com escopo léxico. O escopo da variáveis começam no primeiro comando depois da sua declaração e terminam no final do bloco mais interno que inclua a declaração da mesma.

Note que, em uma declaração como local x = x, o novo x sendo declarado não está no escopo ainda, e então o segundo x refere-se à variável de fora.

Devido às regras de escopo léxico, variáveis locais podem ser livremente acessada por functions definidas dentro do seu escopo. Uma variável local usada por uma função interna é chamada de upvalue, ou variável local externa, dentro da função interna.

Note que cada execução de um comando local define novas variáveis locais:

O loop cria dez instâncias de closures (funções anônimas). Cada um desses closures usam uma variável y diferente, enquanto todos compartilham o mesmo x.

Alocação de Memória

Dispositivos móveis possuem memória limitada disponível para uso, então tome muito cuidado com o total de memória alocada em sua aplicação.

Lua executa limpeza de memória automática. Isto significa que você não tem de se preocupar sobre alocar memória para novos objetos, nem mesmo precisa liberar objetos da memória manualmente. A própria linguagem executa um coletor de lixo de tempos em tempos e coleta todos objetos mortos (órfãos) e toda memória usada pelo Lua está sujeita ao coletor automático.

No entanto, cabe à você dizer ao Lua o que é lixo. Por exemplo, qualquer coisa armazenada em uma variável global não é considerado lixo, mesmo que sua aplicação não use mais essa variável. Similarmente, qualquer coisa armazenada em uma table jamais será considerada lixo também – independente se foi instanciado localmente ou não. Nestes casos, cabe à você atribuir nil às variáveis que não deseja mais usar.

Objetos de tela em Corona exigem um passo adicional. Você deve primeiro remover o objeto da hierarquia da tela usando object:removeSelf() ou display.remove( object ), e depois defini-lo como nil.

Diferenças de Sintaxe

Algumas diferenças entre a sintaxe de Lua e outras linguagens valem a pena ser citadas pois podem prevenir alguns erros de compilação:

  • ponto e vírgula — são opcionais no final de cada linha de código.
  • chaves — aqui elas são usadas apenas para inicializar tables. O escopo dos blocos de código são delimitados por do e end.
  • if – then – else — se você veio do C, Java, Javascript, eyc., um erro comum que você irá cometer seguidamente é o de esquecer de colocar then ao final da condição do if/elseif. Outro erro comum é o de escrever ‘else if’, quando o correto é elseif.
  • arrays — em Lua eles começam na posição 1, ou seja, a primeira posição de uma table t é t[1], não t[0].
  • múltiplos valores de retorno — um recurso não-convencional mas útil em Lua é a habilidade de uma function retornar mais de um resultado.
  • atribuição múltipla — esse recurso oferece uma maneira conveniente de trocar valores. O comando x,y = y,x irá trocar x por y e vice-versa.
  • operador ternário (? 🙂 — Lua não oferece um equivalente, embora uma expressão ‘(a and b) or c’ ofereça algo semelhante desde que b não seja false.

O conteúdo deste post foi retirado da referência em Português sobre Lua no site oficial, permitido pela própria licença da linguagem: http://www.lua.org/manual/5.1/pt/

* OBS: curtiu o post? Então dá uma olhada no meu livro de Corona SDK clicando no banner abaixo pra aprender a criar outros tantos apps incríveis!

Livro Corona SDK
Livro Corona SDK