, A linguagem Lua e suas aplicações em jogos
A Linguagem Lua e suas Aplicacoes em Jogos ¸˜ Waldemar Celes, Luiz Henrique de Figueiredo, Roberto Ierusalimschy 1 Introducao ¸˜ Uma pesquisa realizada em setembro de 2003 pela gamedev.net - um importante site para progra´ madores de jogos - revelou que a grande maioria dos jogos (72%) e desenvolvida com o aux´lio de ı uma linguagem de script. Embora as linguagens de script n˜ o sejam definidas muito precisamente, elas apresentam um a conjunto de caracter´sticas comuns t´picas. Em geral, as linguagens de script s˜ o linguagens inı ı a terpretadas, tˆ m tipagem dinˆ mica e gerˆ ncia autom´ tica de mem´ ria, e fornecem facilidades para e a e a o construcao de estruturas de dados dinˆ micas e manipulacao de cadeias de caracteres. Tipicamente, ¸˜ a ¸˜ essas linguagens funcionam acopladas a programas hospedeiros implementados em linguagens compiladas tradicionais como C e C++. Uma outra caracter´stica importante de linguagens de ı ´ script e que elas devem ser seguras, n˜ o sendo poss´vel acessar servicos n˜ o autorizados do proa ı ¸ a grama hospedeiro. A combinacao dessas caracter´sticas resulta numa excelente ferramenta para o ¸˜ ı desenvolvimento de jogos. Acoplar uma linguagem de script em um jogo traz v´ rios benef´cios. A linguagem de script pode a ı ser usada para efetivamente implementar o script do jogo, para definir objetos e seus comportamentos, para gerenciar os algoritmos de inteligˆ ncia artificial e controlar os personagens, e ainda e para tratar os eventos de entrada e descrever a interface com o usu´ rio. Uma linguagem de script a tamb´ m desempenha um papel importante nas etapas de prototipacao, teste, depuracao e an´ lise e ¸˜ ¸˜ a de adequacao do jogo. A escolha de uma linguagem de script simples permite ainda que seja dado a ¸˜ roteiristas e artistas acesso program´ vel ao jogo, a fim de que eles que possam experimentar novas a id´ ias e variacoes. Esses profissionais conduzem a maior parte do desenvolvimento real do jogo e ¸˜ mas n˜ o s˜ o em geral programadores profissionais e n˜ o est˜ o familiarizados com t´ cnicas sofistia a a a e cadas de programacao. ¸˜ ´ A mesma pesquisa mencionada acima revelou que Lua e atualmente a linguagem de script mais utilizada no desenvolvimento de jogos (20% dos jogos s˜ o desenvolvidos com Lua, enquanto soa mente 7% usam Python, a segunda linguagem de script mais citada na pesquisa). De fato, devido ao seu pequeno tamanho, bom desempenho, portabilidade e facilidade de integracao, Lua tem sido ¸˜ ´ amplamente utilizada na industria de jogos. Empresas com LucasArts, BioWare, Microsoft, Relic Entertainment, Absolute Studios e Monkeystone Games desenvolvem jogos usando Lua. ´ Lua e uma linguagem de script extens´vel, projetada para oferecer meta-mecanismos que possiı ´ a ` bilitam a construcao de mecanismos mais espec´ficos. Com isso, e f´ cil adequar Lua as necessida¸˜ ı des da aplicacao, sem comprometer as suas caracter´sticas b´ sicas, mantidas desde a sua criacao, ¸˜ ı a ¸˜ tais como portabilidade, pequeno tamanho e simplicidade. Em particular, os programadores dos jogos podem fornecer abstracoes adequadas para roteiristas e artistas, simplificando as tarefas des¸˜ ses. Neste tutorial, discutiremos o uso da linguagem Lua no desenvolvimento de jogos. Vamos apresentar os principais mecanismos de programacao oferecidos pela linguagem e a interface para a ¸˜ integracao com programas hospedeiros escrito em C ou C++. Discutiremos ainda como estes me¸˜ canismos podem ser usados para a construcao de jogos mais flex´veis, que fornecem acesso pro¸˜ ı gram´ vel aos servicos implementados pelo programa hospedeiro. a ¸ 1 2 Linguagens de extens˜ o a Vamos classificar as linguagens de extens˜ o ou de script segundo a sua complexidade: a • Linguagens de configuracao servem para selecionar preferˆ ncias e s˜ o tipicamente uma lista ¸˜ e a de valores associados a vari´ veis. Um exemplo t´pico s˜ o os arquivos .ini do Windows. a ı a • Linguagens de macros servem para automacao de tarefas e s˜ o tipicamente uma lista de acoes ¸˜ a ¸˜ primitivas, com muito pouco ou nenhum controle de fluxo. Um exemplo t´pico s˜ o os arquiı a vos de automacao de conex˜ es internet via modem. ¸˜ o • Linguagens embutidas permitem acesso program´ vel aos servicos da aplicacao, com fluxo de a ¸ ¸˜ controle completo e funcoes definidas pelo usu´ rio a partir de primitivas exportadas pela ¸˜ a aplicacao. Essas linguagens s˜ o linguagens completas, muitas vezes variantes simplificadas ¸˜ a de linguagens tradicionais como Lisp ou C. Independente da complexidade da linguagem, a adocao de uma linguagem de extens˜ o e uma ¸˜ a ´ t´ cnica poderosa de desenvolvimento de software, pois permite que muitos aspectos da aplicacao e ¸˜ sejam controlados externamente, a partir de arquivos textos facilmente editados pelo programador ou pelo usu´ rio, sem necessidade de recompilar a aplicacao. Isso torna o desenvolvimento mais a ¸˜ ´ agil, pois permite que partes importantes da aplicacao sejam desenvolvidas por outros membros ¸˜ da equipe que n˜ o s˜ o programadores profissionais (no caso de jogos, animadores e artistas). a a Al´ m disso, a adocao de uma mesma linguagem para v´ rias tarefas na aplicacao permite um e ¸˜ a ¸˜ aumento importante de produtividade global, pois n˜ o e necess´ rio definir, documentar e manter a ´ a v´ rios formatos diferentes. Para o usu´ rio da aplicacao, isso se traduz no re-aproveitamento aua a ¸˜ tom´ tico (e inconsciente) dos conceitos aprendidos para realizar as v´ rias tarefas, encorajando a a a exploracao. ¸˜ 3 A linguagem Lua ´ A linguagem Lua e uma linguagem de programacao poderosa e leve, projetada para estender aplica¸˜ coes. Isso quer dizer que ela foi projetada para ser acoplada a programas maiores que precisem ler ¸˜ ´ e executar programas escritos pelos usu´ rios. Na classificacao da Secao 2, Lua e uma linguagem a ¸˜ ¸˜ ` embutida, com sintaxe semelhante a de Pascal mas com construcoes modernas, como funcoes ¸˜ ¸˜ anˆ nimas, inspiradas no paradigma funcional, e poderosos construtores de dados. Isso faz com o que Lua seja uma linguagem de grande express˜ o com uma sintaxe familiar. a 3.1 Hist´ ria o Lua foi projetada e implementada no Tecgraf, o Grupo de Computacao Gr´ fica da PUC-Rio. A pri¸˜ a ´ ´ ´ meira vers˜ o de Lua (1.0) e de julho de 1993. A primeira vers˜ o publica (1.1) e de julho de 1994. a a ´ A vers˜ o atual e a 5.0.2, lancada em marco de 2004 para corrigir pequenas falhas na vers˜ o 5.0, de a ¸ ¸ a abril de 2003. A vers˜ o 5.1 est´ em desenvolvimento no momento. Este texto trata da vers˜ o oficial a a a atual (5.0.2). A motivacao para a criacao de Lua no Tecgraf foi a necessidade crescente das suas aplicacoes ¸˜ ¸˜ ¸˜ serem configur´ veis externamente pelos usu´ rios. Isso quer dizer que diversos aspectos essenciais a a das aplicacoes podem ser modificados sem recompilar a aplicacao. Desde o in´cio, nas aplicacoes ¸˜ ¸˜ ı ¸˜ criadas no Tecgraf, esse tipo de configuracao era muito mais do que simplesmente poder esco¸˜ lher a cor da janela ou o tipo de fonte de texto: era necess´ rio poder tomar decis˜ es em tempo de a o execucao que somente os usu´ rios sabiam quais eram. Sendo assim, era necess´ rio fornecer al¸˜ a a gum tipo de programacao para os usu´ rios finais. Um outro tipo de configuracao era a descricao de ¸˜ a ¸˜ ¸˜ 2 complexos relat´ rios e an´ lises feitas pela Petrobras por encomenda ao Tecgraf. Mais uma vez, essa o a descricao n˜ o podia estar congelada dentro da aplicacao pois cada usu´ rio tinha uma necessidade ¸˜ a ¸˜ a diferente e que mudava a cada tarefa. O Tecgraf tinha portanto (e ainda tem) forte demanda para aplicacoes que fossem configur´ veis externamente, tanto descrevendo que decis˜ es deveriam ser ¸˜ a o tomadas quanto descrevendo quais dados seriam usados e como eles seriam usados. Ap´ s projetar e usar com sucesso duas pequenas linguagens espec´ficas para cada uma dessas o ı ´ tarefas, o Tecgraf decidiu investir na criacao de uma linguagem unica que pudesse atender a todas ¸˜ as necessidades de configuracao das suas aplicacoes. Assim nasceu Lua: uma linguagem procedu¸˜ ¸˜ ral simples com poderosas construcoes para descricao de dados. Desde ent˜ o Lua tem sido usada ¸˜ ¸˜ a ´ em inumeros projetos no Tecgraf. A vers˜ o seguinte de Lua (2.1) foi lancada em fevereiro de 1995 e trouxe maior poder de exa ¸ press˜ o, introduzindo a nocao de semˆ ntica extens´vel: passou a ser poss´vel programar o que fazer a ¸˜ a ı ı em casos excepcionais, como quando um campo n˜ o existe numa tabela. Ter uma semˆ ntica exa a ´ tens´vel e desde ent˜ o uma caracter´stica marcante de Lua. ı a ı A vers˜ o 2.1 tamb´ m foi a primeira a ser completamente livre; as vers˜ es anteriores eram livres a e o ` ` somente para aplicacoes acadˆ micas. Aliada a portabilidade e a eficiˆ ncia da implementacao, a ¸˜ e e ¸˜ ´ falta de restricoes de uso foi um dos fatores importantes na adocao de Lua em inumeros projetos ¸˜ ¸˜ no mundo todo. A primeira not´cia que tivemos do uso de Lua em jogos foi em 1997 quando a LucasArts adotou ı Lua como a sua linguagem de script no lugar de SCUMM no jogo "Grim Fandango". A adocao de ¸˜ Lua nesse jogo foi uma consequˆ ncia direta da publicacao de um artigo de divulgacao sobre Lua na e ¸˜ ¸˜ revista Dr. Dobb's Journal, em junho de 1996. Desde ent˜ o, Lua tem sido cada vez mais usada em a ´ ´ jogos, uma area longe das areas que motivaram a sua criacao! ¸˜ Atualmente, Lua tem uma comunidade ativa de programadores. A lista de discuss˜ o tem mais a de 750 assinantes do mundo todo. Al´ m do site oficial de Lua (lua.org), h´ tamb´ m um ativo site e a e mantido por usu´ rios (lua-users.org), uma sala de bate-papo, um web forum e um reposit´ rio (luaa o forge.net). 3.2 Caracter´sticas b´ sicas ı a ´ ` Lua e uma linguagem de extens˜ o projetada para dar suporte a programacao procedural, oferea ¸˜ cendo facilidades para descricao de dados. No contexto da programacao de jogos, isso significa que ¸˜ ¸˜ Lua possibilita combinar a descricao de objetos e a programacao de seus comportamentos num ¸˜ ¸˜ ´ mesmo contexto. Lua e uma biblioteca implementada em C, podendo ser compilada em qualquer plataforma que tenha um compilador C padr˜ o. Lua tamb´ m pode ser compilada sem alteracoes a e ¸˜ como uma biblioteca C++. No que se segue, toda referˆ ncia a C deve ser entendida como uma e referˆ ncia a C++ tamb´ m. Em alguns poucos lugares trataremos exclusivamente de C++. e e Por ser uma linguagem de extens˜ o, Lua trabalha acoplada a uma aplicacao hospedeira (host). a ¸˜ Essa aplicacao pode criar e ler valores armazenados em Lua, executar funcoes de Lua e registrar ¸˜ ¸˜ funcoes C no ambiente de Lua. As funcoes C registradas em Lua, por sua vez, podem ser invocadas ¸˜ ¸˜ de programas Lua. Dessa forma, podemos conciliar as facilidades de uma linguagem de script oferecidas por Lua com a eficiˆ ncia das linguagens C e C++. A distribuicao da linguagem Lua inclui um e ¸˜ programa hospedeiro, lua.c, que pode ser usado para executar scripts Lua interativamente ou em batch. Neste tutorial, no entanto, focaremos no uso de Lua acoplada a programas de jogos. Para que uma aplicacao tenha acesso a Lua, precisamos abrir a biblioteca conforme ser´ discu¸˜ a ˜ o 3.9. Ao final, a aplicacao deve fechar a biblioteca. Ap´ s a abertura da biblioteca, a ˜ tido na Seca ¸ ¸ o aplicacao pode usar os recursos oferecidos por Lua, como por exemplo, executar scripts Lua e aces¸˜ sar dados armazenados em Lua. Antes de detalharmos como isso pode ser feito dentro do c´ digo da o aplicacao, temos que aprender como escrever scripts em Lua. Programas ou scripts Lua s˜ o arma¸˜ a zenados em arquivos (usualmente com extens˜ o .lua) ou em strings da aplicacao. Um arquivo ou a ¸˜ 3 ´ ¨e string com c´ digo Lua caracteriza o que chamamos de chunk, que e simplesmente uma sequˆ ncia o de comandos Lua. Daremos a seguir uma breve introducao as principais caracter´sticas da lingua¸˜ ` ı gem Lua. 3.3 Vari´ veis e tipos a Em Lua, as vari´ veis n˜ o tˆ m tipos associados a elas: os tipos est˜ o associados aos valores armazea a e a nados nas vari´ veis. Dessa forma, uma mesma vari´ vel pode num momento armazenar um valor de a a um tipo e depois passar a armazenar o valor de outro tipo (naturalmente, a vari´ vel deixa de armaa zenar o valor inicial). O trecho de c´ digo abaixo ilustra o uso de vari´ veis armazenando diferentes o a valores: a = b = ... b = a = "Exemplo" 1.23 nil 3 -- a armazena string -- b armazena n´mero u -- b armazena nil -- a armazena n´mero u (Note a forma dos coment´ rios em Lua: comecam com -- e v˜ o at´ o final da linha.) a ¸ a e ` O fato de o tipo estar associado ao valor, e n˜ o a vari´ vel, d´ grande flexibilidade a linguaa ` a a gem. Podemos, por exemplo, criar conjuntos de valores heterogˆ neos naturalmente, pois o polie ´ ` formismo e intr´nseco a linguagem. Por outro lado, a verificacao de tipo s´ pode ser feita em tempo ı ¸˜ o ´ de execucao. Assim, se tentarmos somar duas vari´ veis cujos valores n˜ o s˜ o numericos, um erro ¸˜ a a a ser´ reportado, mas somente quando a soma for executada. Como pretendemos, ao acoplar Lua a ao jogo, exportar servicos da aplicacao, devemos prever um tratamento adequado desses erros de ¸ ¸˜ execucao. Um usu´ rio de um jogo pode codificar uma configuracao inv´ lida e isso deve ser tratado ¸˜ a ¸˜ a de maneira adequada, sem necessariamente abortar o jogo. Em Lua, vari´ veis globais n˜ o precisam ser declaradas. Quando escrevemos a = 3, a vari´ vel a a a a ´ e, por default, uma vari´ vel global. Se desejarmos que uma vari´ vel tenha escopo local (a um bloco a a ou chunk), devemos declar´ -la previamente usando a palavra local. Por exemplo: a local a ... a = 3 Os valores em Lua podem ser de oito tipos: • nil: o valor nil indica ausˆ ncia de valor. Vari´ veis n˜ o inicializadas contˆ m o valor nil. O valor e a a e nil tamb´ m e interpretado como falso numa express˜ o booleana. e ´ a • boolean: valor booleano, podendo ser falso (false) ou verdadeiro (true); • number: valor num´ rico. Lua n˜ o diferencia valor inteiro de valor real; s´ existe um tipo para e a o ´ representar numeros; • string : valor cadeia de caracteres. Uma constante string pode ser delimitada por aspas duplas ("..."), aspas simples ('...'), ou duplo colchetes ([[...]]). • table: vetor associativo (a ser detalhado na Secao 3.6); ¸˜ • function: funcao escrita em Lua ou escrita em C e registrada em Lua; ¸˜ • userdata: dado do host, representado por um ponteiro void*; • thread: linha de execucao, que ser´ apresentado na Secao 3.8, quando descrevermos o uso de ¸˜ a ¸˜ co-rotinas em Lua. 4 3.4 Operadores e controladores de fluxo A linguagem Lua oferece os operadores comumente encontrados em linguagens de programacao. ¸˜ Os operadores aritm´ ticos s˜ o os usuais: + (adicao), - (subtracao), * (multiplicacao), / (divis˜ o), e a ¸˜ ¸˜ ¸˜ a ^ (exponenciacao) e - un´ rio (negacao). Os operadores relacionais resultam num valor booleano ¸˜ a ¸˜ e incluem: < (menor que), > (maior que), <= (menor ou igual que), >= (maior ou igual que), == (igualdade), ~= (diferenca). Os operadores l´ gicos servem para combinar valores booleanos e s˜ o ¸ o a dados por: and (e), or (ou), not (negacao). Existe ainda o operador .. para concatenacao de strings. ¸˜ ¸˜ Os operadores l´ gicos and e or s˜ o uteis tamb´ m na avaliacao de express˜ es. Esses operadores o a ´ e ¸˜ o combinam duas express˜ es e fazem a avaliacao da segunda express˜ o apenas quando necess´ rio. o ¸˜ a a ´ ´ ´ Al´ m disso, o resultado de um and ou or e ultimo valor usado na sua avaliacao. Dessa forma, e e ¸˜ ´ v´ lido e muito util usar construcoes como as seguintes: a ¸˜ x = v or w y = a and b or c Na primeira atribuicao acima, x passa a armazenar o valor de v, se esse for diferente de falso (false ¸˜ ou nil); caso contr´ rio, passa a armazenar o valor de w. A segunda atribuicao, que deve ser lida a ¸˜ ´ ` como (a and b) or c, e equivalente a express˜ o y = a ? b : c em C, desde que b n˜ o resulte a a em falso. ` A linguagem Lua oferece os controladores de fluxo comuns as linguagens procedurais. As construcoes para tomada de decis˜ o s˜ o as variantes usuais de if ... then ... else: ¸˜ a a if expr then ... end if expr then ... else ... end if expr1 then ... elseif expr2 then ... else ... end As construcoes para execucao iterativa podem ter o seu teste no in´cio (while) ou no fim (repeat): ¸˜ ¸˜ ı while expr do ... end repeat ... until expr Lua oferece ainda a construcao de lacos com for. O for num´ rico tem a seguinte forma: ¸˜ ¸ e for var = expr_inicial, expr_final, expr_incremento do ... end ´ ´ a Nessa construcao, a vari´ vel var e autom´ tica e local ao laco, isto e, n˜ o precisa ser explicitamente ¸˜ a a ¸ declarada e s´ existe dentro do laco. As express˜ es inicial, final e de incremento s˜ o avaliadas uma o ¸ o a ´ unica vez, antes do in´cio da execucao do bloco de comandos do laco. A express˜ o de incremento, ı ¸˜ ¸ a se omitida, vale 1. Um laco de for pode ainda aparecer na sua forma gen´ rica, que permite a construcao de diver¸ e ¸˜ sos tipos de iteradores especializados. O trecho de c´ digo abaixo, por exemplo, usa um for gen´ rico o e para ler e imprimir cada linha do arquivo de entrada corrente, usando funcoes pr´ -definidas na bi¸˜ e blioteca de entrada e sa´da (uma discuss˜ o de como se constr´ i iteradores foge do escopo desse ı a o tutorial). 5 for line in io.lines() do io.write(line,"\n") end A execucao de lacos while, repeat e for pode ser interrompida usando o comando break. ¸˜ ¸ 3.5 Funcoes ¸˜ Funcoes em Lua s˜ o valores de primeira classe. Isso significa que, como qualquer outro valor, uma ¸˜ a funcao pode ser criada, armazenada em uma vari´ vel (local ou global) ou campo de tabela e pas¸˜ a sada adiante como parˆ metro ou valor de retorno de uma outra funcao. Uma funcao pode receber a ¸˜ ¸˜ ´ zero ou mais valores. A lista de parˆ metros e especificada da maneira usual: entre os parˆ nteses a e ´ que seguem o nome da funcao. Como exemplo simples (tradicional, mas util somente para fins ¸˜ did´ ticos), consideremos a definicao da funcao recursiva abaixo para o c´ lculo do fatorial de um a ¸˜ ¸˜ a ´ numero inteiro: function fat (n) if n==0 then return 1 else return n*fat(n-1) end end ´ As funcoes em Lua n˜ o tˆ m nome; elas s˜ o sempre anˆ nimas. O c´ digo acima e apenas uma ¸˜ a e a o o ´ maneira conveniente de definir uma funcao e atribu´-la a uma vari´ vel global, e e equivalente a ¸˜ ı a fat = function (n) ... end -- fun¸~o an^nima atribu´da ` vari´vel fat ca o ı a a Para testar essa funcao, podemos usar a funcao print da biblioteca padr˜ o de Lua que imprime ¸˜ ¸˜ a um valor na tela (nesse caso, 120): local a = 5 print(fat(a)) ´ a ´ Lua permite atribuicoes multiplas. E v´ lido, por exemplo, escrever x,y = y,x, que troca os va¸˜ ´ a lores de x e y sem a necessidade de usar uma vari´ vel tempor´ ria, da mesma forma que e v´ lido a a ´ a escrever a,b = 1,2. Ainda de forma an´ loga, e v´ lido escrever a,b = f() - os valores retornados a ` ´ por f ser˜ o atribu´dos as vari´ veis a e b. (Sim, Lua permite que uma funcao retorne multiplos valoa ı a ¸˜ res, escrevendo return expr1, expr2, ... . Isso evita em grande parte a necessidade de passagem de parˆ metros por referˆ ncia; em Lua todos os parˆ metros s˜ o passados por valor.) a e a a ´ ´ ´ Se o numero de vari´ veis numa atribuicao multipla for maior que o numero de valores resula ¸˜ ` ´ tantes a direita do sinal de igualdade, as vari´ veis excedentes recebem o valor nil. Se o numero a de valores for maior, os valores excedentes s˜ o descartados. Esse mesmo ajuste ocorre ao se chaa mar funcoes: argumentos ausentes recebem o valor nil; argumentos extras s˜ o ignorados (exceto ¸˜ a ´ quando a funcao aceita um numero vari´ vel de argumentos). ¸˜ a Funcoes podem ser criadas localmente dentro de outras funcoes, e depois retornadas ou arma¸˜ ¸˜ zenadas em uma tabela. Uma funcao pode ainda acessar vari´ veis locais do escopo acima. Consi¸˜ a dere por exemplo o trecho de c´ digo abaixo: o 6 function counter () local i = 0 return function () i = i+1 return i end end local c = counter() print(c()) print(c()) A vari´ vel c armazena uma instˆ ncia da funcao anˆ nima criada (e retornada) em counter. Essa a a ¸˜ o funcao usa a vari´ vel local i declarada em counter. Assim, cada vez que executamos a funcao ¸˜ a ¸˜ armazenada em c, recebemos o valor da vari´ vel i incrementado de uma unidade. Se fizermos um a paralelo com C, a vari´ vel da funcao counter funciona como uma vari´ vel est´ tica para a funcao a ¸˜ a a ¸˜ c. Se executarmos a funcao counter novamente, teremos como retorno uma outra funcao que, se ¸˜ ¸˜ chamada, retornar´ da primeira vez o valor 1, depois 2, e assim por diante. Essa segunda funcao e a ¸˜ ´ diferente da primeira: embora ambas facam a mesma coisa, elas o fazem de maneira independente. ¸ ´ ´´ ´ ´ E importante observar que a funcao anˆ nima e unica e seu c´ digo e gerado uma unica vez. A funcao ¸˜ o o ¸˜ counter retorna o que chamamos de closure, que guarda uma referˆ ncia para a funcao anˆ nima e e ¸˜ o uma lista com os valores das vari´ veis do escopo superior usadas, entre outras coisas. A existˆ ncia a e de closures com escopo l´ xico (acesso a vari´ veis do escopo superior) permite que Lua seja usada e a como linguagem funcional, o que d´ grande flexibilidade de programacao. a ¸˜ Por fim, considere o trecho de c´ digo abaixo: o a = 2 function f ( ) ... end local b = 3 function g ( ) local x = b ... end ´ ´ Todo chunk representa o corpo de uma funcao anˆ nima que e retornada quando o chunk e carre¸˜ o gado. No exemplo acima, atribui-se trˆ s vari´ veis globais (a, f e g) e declara-se uma vari´ vel local b. e a a Fazendo uma analogia com programacao em C, temos que a vari´ vel b funciona como uma vari´ vel ¸˜ a a ´ est´ tica do m´ dulo. Na verdade, em Lua, b e uma vari´ vel local da funcao anˆ nima caracterizada a o a ¸˜ o pelo chunk, acessada de dentro da funcao que foi armazenada na vari´ vel global g. ¸˜ a 3.6 Tabelas e objetos O tipo table representa um vetor associativo, implementado internamente com o uso de uma efici´ ente combinacao de array e hash (tabela de dispers˜ o). As tabelas s˜ o a unica forma de estruturacao ¸˜ a a ¸˜ de dados em Lua. Todas as estruturas de dados comumente encontradas em programacao (tais ¸˜ 7 como vetores, listas, filas, conjuntos e hash) podem ser eficientemente (e facilmente) implementa´ das com o uso de tabelas. Uma tabela em Lua e criada pela express˜ o { }. Se uma vari´ vel armazena a a um valor do tipo tabela, essa vari´ vel pode ser indexada por qualquer outro valor (exceto nil ). O a valor armazenado em cada ´ndice da tabela tamb´ m pode ser de qualquer tipo (incluindo nil ). O ı e valor associado a um ´ndice da tabela n˜ o inicializado tem o valor nil. O trecho de c´ digo abaixo ı a o ilustra a criacao de uma tabela e a atribuicao de alguns campos: ¸˜ ¸˜ local t = {} t[1] = 4 t[2] = "alo" t["alo"] = 5 t[t[2]] = 0 -----cria nova tabela armazena 4 no ´ndice 1 ı armazena "alo" no ´ndice 2 ı armazena 5 no ´ndice "alo" ı armazena 0 no ´ndice "alo" (sobrescrevendo) ı ´ Lua oferece uma sintaxe simplificada quando o ´ndice e uma string simples (desde que a string ı n˜ o seja uma palavra reservada na sintaxe de Lua). Assim, a atribuicao acima t["alo"] = 5 pode a ¸˜ ser escrita simplesmente por t.alo = 5. Lua permite ainda que campos da tabela sejam inicializados na criacao. Dessa forma, as trˆ s primeiras linhas do c´ digo acima podem ser substitu´das ¸˜ e o ı por: local t = {4,"alo"; alo=5} A biblioteca padr˜ o de Lua oferece duas funcoes que permitem iterar sobre os elementos armaa ¸˜ zenados na tabela. A funcao ipairs itera sobre todos os ´ndices num´ ricos armazenados na tabela. ¸˜ ı e Assim, for i,v in ipairs(t) do ... end itera sobre os pares (1,t[1]), (2,t[2]), . . . , at´ que o primeiro ´ndice com valor associado igual a e ı nil seja encontrado. A funcao pairs permite iterar sobre todos os pares armazenados na tabela, independente do ¸˜ ` tipo associado a chave: for k,v in pairs(t) do ... end ´ Nesse caso, a ordem em que os pares k,v s˜ o reportados e indefinida. a Como ´ndices (chaves) e valores de uma tabela podem ser valores quaisquer, podemos natuı ralmente usar tabelas como ´ndices e valores. Com isso, podemos ter tabelas aninhadas (inclusive ı com ciclos). Considere a tabela abaixo, que especifica os parˆ metros para a criacao de uma janela a ¸˜ de di´ logo numa aplicacao gr´ fica. Observe que funcoes podem tamb´ m ser armazenadas em taa ¸˜ a ¸˜ e belas. local w = { width = 640, height = 480, menu = { {label="Load",action=function () ... end}, {label="Save",action=function () ... end}, }, ... } Lua permite ainda a especificacao de um "construtor" na criacao da tabela. Consideremos, por ¸˜ ¸˜ exemplo, a especificacao de um ponto em 3D, dado pelas suas coordenadas. Em Lua, podemos ¸˜ escrever: 8 local p = Point{x=3.0,y=1.3,z=3.2} ´ O c´ digo acima e equivalente a o local p = Point({x=3.0,y=1.3,z=3.2}) ´ ´ isto e, uma tabela e criada e chama-se a funcao Point passando a nova tabela como parˆ metro. ¸˜ a Essa funcao pode ser o construtor do objeto sendo criado. Por exemplo, podemos usar a funcao ¸˜ ¸˜ para validar e inicializar campos do objeto. function Point (self) self.x = tonumber(self.x) or 0.0 self.x = tonumber(self.y) or 0.0 self.x = tonumber(self.z) or 0.0 return self end Assim, se na criacao do objeto n˜ o forem especificados valores das coordenadas (ou se forem espe¸˜ a cificados valores n˜ o num´ ricos), a funcao inicializa esses valores com zero. a e ¸˜ Lua oferece ainda um eficiente mecanismo para estendermos a sua semˆ ntica atrav´ s do uso a e de eventos. Esse mecanismo permite, por exemplo, adotarmos uma programacao orientada a ob¸˜ jetos. Para estender a semˆ ntica de um objeto (tabela), devemos associar a ele uma outra tabela, a chamada de metatable. Na metatable, podemos programar a acao que deve ser tomada quando ¸˜ ocorre um determinado evento. Por exemplo, a operacao de soma n˜ o e especificada para tabelas; ¸˜ a ´ no entanto, podemos fazer com que dois objetos do nosso tipo Point acima possam ser somados, gerando um terceiro novo objeto do tipo Point. Para isso, devemos primeiro criar uma metatable com o comportamento da operacao de soma definido: ¸˜ local Point_metatable = { __add = function (p1,p2) return Point(p1.x+p2.x,p1.y+p2.y,p1.z+p2.z} end } Devemos reescrever o construtor de Point para definir a metatable de cada objeto criado: function Point (self) self.x = tonumber(self.x) or 0.0 self.x = tonumber(self.y) or 0.0 self.x = tonumber(self.z) or 0.0 setmetatable(self,Point_metatable) return self end Assim, definimos um objeto Point e podemos us´ -lo de maneira transparente: a local p = Point{x=3.0,y=1.3,z=3.2} local q = Point{x=4.2,y=1.0} local r = p+q -- r.x=7.2, r.y=2.3, r.z=3.2 Al´ m de add, podemos (re-)definir o comportamento quando da ocorrˆ ncia dos seguintes evene e tos de operacao aritm´ tica: sub (subtracao), mul (multiplicacao), div (divis˜ o), pow (exponeciacao), ¸˜ e ¸˜ ¸˜ a ¸˜ unm (negacao), concat (concatenacao), eq (igualdade), lt (menor que), le (menor ou igual que). ¸˜ ¸˜ 9 ´ Basta criar o campo adequado na metatable. (O nome do campo e o nome do evento precedido de __.) Existem ainda dois eventos especiais cujos comportamentos podem ser programados: index, gerado quando tentamos acessar um ´ndice n˜ o existente na tabela, e newindex, gerado quando ı a tentamos atribuir um valor a um ´ndice ainda n˜ o existente na tabela. Esses eventos podem ser ı a usados para programar diferentes comportamentos. Por exemplo, podemos usar o evento index para delegar a uma outra tabela a busca do valor associado ao ´ndice. Dessa forma, podemos proı gramar nosso pr´ prio mecanismo de heranca. Se o objeto n˜ o tem o campo, retornamos o campo o ¸ a ` associado a sua "classe": local Point_methods = { Print = function (self) print(self.x, self.y, self.z) end, ... } Na metatable, associamos a tabela acima ao campo __index: local Point_metatable = { __index = Point_methods, __add = function (p1,p2) return Point(p1.x+p2.x,p1.y+p2.y,p1.z+p2.z} end } Podemos ent˜ o acessar o "m´ todo" Print de nosso tipo: a e local p = Point{x=3.0,y=1.3,z=3.2} local q = Point{x=4.2,y=1.0} local r = p+q r.Print(r) ´ Para facilitar o uso e dar clareza ao c´ digo, a ultima linha do c´ digo acima pode ser escrita o o r:Print(), como se espera de uma chamada de m´ todo em C++ ou Java. Em Lua, a chamada de e ´ funcao da forma t:meth(...) e equivalente a t.meth(t,...). ¸˜ Se a delegacao n˜ o for direta, podemos atribuir ao campo __index da metatable uma funcao que ¸˜ a ¸˜ deve ser executada quando o evento ocorrer. Isto nos d´ flexibilidade para, por exemplo, buscarmos a o valor do campo num objeto em C ! 3.7 Biblioteca padr˜ o a A distribuicao oficial de Lua inclui um conjunto de bibliotecas que implementam diversas funcoes ¸˜ ¸˜ importantes para a construcao de programas. Com excecao das funcoes que pertencem ao que ¸˜ ¸˜ ¸˜ chamamos de biblioteca b´ sica, as funcoes de cada biblioteca s˜ o agrupadas em tabelas. Assim, a ¸˜ a a tabela string agrupa as funcoes para manipulacao de strings, a tabela table agrupa as funcoes ¸˜ ¸˜ ¸˜ para manipulacao de tabelas e assim por diante. Listamos abaixo as bibliotecas padr˜ o inclu´das ¸˜ a ı na distribuicao. O manual de referˆ ncia cont´ m uma descricao detalhada das funcoes oferecidas. ¸˜ e e ¸˜ ¸˜ Al´ m da biblioteca b´ sica, que oferece funcoes b´ sicas para a programacao em Lua (como print, e a ¸˜ a ¸˜ setmetatable, pairs, que usamos acima), a distribuicao inclui as seguintes bibliotecas: ¸˜ • string: oferece funcoes para manipulacao de strings. Destacamos o poderoso mecanismo ¸˜ ¸˜ de casamento de padr˜ es (pattern matching ) oferecido atrav´ s das funcoes string.find, que o e ¸˜ 10 permite buscar a ocorrˆ ncia de um padr˜ o numa string, e string.gsub, que permite substie a tuirmos ocorrˆ ncia de um padr˜ o por uma sequˆ ncia de caracteres dentro de uma string. e a e • table: oferece funcoes para manipulacao de tabelas, tais como funcoes para inserir um novo ¸˜ ¸˜ ¸˜ elemento (associado a um ´ndice num´ rico) na tabela (table.insert), remover um elemento ı e da tabela (table.remove) e ordenar os elementos armazenados em ´ncides num´ ricos de uma ı e tabela (table.sort). ` • math: oferece funcoes semelhantes as funcoes oferecidas pela biblioteca matem´ tica de C, tais ¸˜ ¸˜ a como math.sqrt, math.sin, math.log, etc. • io: oferece funcoes para operacoes de entrada e sa´da, tais como abertura (io.open), fecha¸˜ ¸˜ ı mento de arquivos (io.close), leitura (io.read) e escrita (io.write). A biblioteca de io tra´ balha com o conceito de objeto. Um arquivo aberto e um objeto ao qual temos associado m´ todos. Assim, ap´ s o comando f = io.open("entrada.txt","r"), a vari´ vel f cont´ m e o a e um objeto do tipo arquivo. De posse do objeto, podemos usar funcoes (io.read(f,...)) ou ¸˜ m´ todos (f:read(...)) para manipularmos o arquivo. e ` • os: oferece funcoes relacionadas ao sistema operacional, tamb´ m an´ logas as funcoes ofere¸˜ e a ¸˜ cidas pela biblioteca C, tais como os.clock, os.date, os.execute (an´ loga a system de C). a • debug: oferece funcoes para depuracao de c´ digos Lua. As funcoes oferecidas permitem, por ¸˜ ¸˜ o ¸˜ exemplo, consultar o estado corrente da pilha de execucao de Lua e os valores de vari´ veis ¸˜ a locais em todos os n´veis da pilha. Essa biblioteca oferece ainda mecanismos para cadastrar ı acoes a serem tomadas a cada execucao de uma linha de c´ digo, a cada chamada de funcao, ¸˜ ¸˜ o ¸˜ etc., viabilizando a construcao de interfaces de depuracao. Assim, em vez de oferecer uma fer¸˜ ¸˜ ramenta de depuracao, Lua oferece mecanismos para que tais ferramentas sejam facilmente ¸˜ constru´das, direcionadas para o dom´nio da aplicacao em quest˜ o. ı ı ¸˜ a Lua oferece ainda a biblioteca de co-rotinas, que discutiremos na pr´ xima secao, dada a sua o ¸˜ especial importˆ ncia para a programacao de jogos. a ¸˜ 3.8 Co-rotinas ´ Co-rotinas s˜ o um poderoso mecanismo de programacao para jogos. Uma co-rotina e semelhante a ¸˜ a um thread num sistema de multithreading, no sentido de que temos uma linha de execucao com ¸˜ seu pr´ prio ambiente local (pilha de execucao) compartilhando o ambiente global com outras coo ¸˜ rotinas. A grande diferenca entre uma co-rotina e uma funcao e que a execucao de uma co-rotina ¸ ¸˜ ´ ¸˜ pode ser suspensa e retomada posteriormente (no ponto em que foi suspensa). A diferenca en¸ ´ tre co-rotinas e threads e que, conceitualmente, diferentes threads executam simulataneamente, enquanto que num sistema com co-rotinas, apenas uma co-rotina executa por vez. As funcoes que manipulam co-rotinas est˜ o agrupadas na tabela coroutine. Criamos uma co¸˜ a rotina passando uma funcao (em geral, anˆ mina) para a funcao de criacao, que retorna um valor ¸˜ o ¸˜ ¸˜ do tipo thread: local c = coroutine.create(function () ... end) print(type(c)) --> "thread" Uma co-rotina pode estar em trˆ s diferentes estados: suspensa, executando e inativa. Imediae tamente ap´ s a sua criacao, uma co-rotina est´ no estado "suspensa". Para executar uma co-rotina, o ¸˜ a invocamos a funcao coroutine.resume. A execucao de uma co-rotina comeca pela execucao da ¸˜ ¸˜ ¸ ¸˜ funcao passada como parˆ metro na sua criacao. Dentro do c´ digo da co-rotina, podemos suspen¸˜ a ¸˜ o der sua execucao invocando a funcao coroutine.yield. Ao executar essa funcao, o controle volta ¸˜ ¸˜ ¸˜ 11 para o c´ digo que tinha dado coroutine.resume na co-rotina, restaurando todo o ambiente local. A o co-rotina pode voltar a ser executada com uma outra chamada de coroutine.resume, e a execucao ¸˜ ´ ´ e retomada logo ap´ s o ultimo comando coroutine.yield executado. Do ponto de vista da coo ´ rotina, uma chamada a coroutine.yield retorna quando a execucao da co-rotina e retomada (via ¸˜ coroutine.resume). Lua oferece um mecanismo simples e vers´ til para troca de dados (mensagens) a entre co-rotinas. Os argumentos de uma chamada a coroutine.yield s˜ o passados como valores a de retorno da chamada a coroutine.resume. Simetricamente, os argumentos de coroutine.resume s˜ o passados como valores de retorno da funcao coroutine.yield. a ¸˜ ´ Co-rotinas s˜ o muito uteis quando queremos implementar um procedimento de maneira increa mental. Em jogos, onde temos um tempo limitado para executarmos nossas simulacoes, podemos ¸˜ implementar as simulacoes de forma incremental, executando os passos que s˜ o poss´veis entre ¸˜ a ı quadros da animacao. Para ilustrar, vamos considerar um exemplo hipot´ tico: um jogo tem que ¸˜ e fazer a simulacao do comportamento de diversos personagens. Para n˜ o favorecer um personagem ¸˜ a em relacao aos outros, podemos pensar em implementar a simulacao de forma incremental, cri¸˜ ¸˜ ando co-rotinas e suspendendo sua execucao ap´ s um passo da simulacao. Podemos prever ent˜ o ¸˜ o ¸˜ a uma funcao que gerencia a execucao das diversas simulacoes, executando cada uma passo a passo. ¸˜ ¸˜ ¸˜ ´ Note que o uso de co-rotinas aqui e muito apropriado, pois cada simulacao pode ser retomada a ¸˜ qualquer instante - a linguagem garante a restauracao do seu ambiente local. ¸˜ Comecamos pela programacao da simulacao de cada personagem (ou grupo de personagens) ¸ ¸˜ ¸˜ encapsulada por co-rotina. Agrupamos as co-rotinas numa tabela e passamos essa tabela para um gerenciador das simulacoes. O gerenciador chama uma co-rotina por vez. Conforme ilustrado ¸˜ abaixo, o gerenciador pode, por sua vez, ser uma co-rotina gerenciada por um controle externo. local simulators = { coroutine.create(function () coroutine.create(function () coroutine.create(function () ... } ... end), ... end), ... end), -- simula¸~o 1 ca -- simula¸~o 2 ca -- simula¸~o 3 ca function manager () while true do for i,v in pairs(simulators) do coroutine.resume(v) end coroutine.yield() -- repassa para controlador externo end end 3.9 Interface com C ´ Como Lua e uma linguagem para estender aplicacoes, ela n˜ o tem somente uma sintaxe e uma ¸˜ a semˆ ntica: ela tem tamb´ m uma API para comunicacao com a aplicacao. Essa API est´ descrita em a e ¸˜ ¸˜ a ´ ´ ´ lua.h e e formada por aproximadamente 80 funcoes C. (N˜ o se assuste com esse numero! A API e ¸˜ a razoavelmente simples.) ´ O primeiro conceito na API e o estado Lua: a execucao de um programa Lua e a comunicacao ¸˜ ¸˜ de C com Lua se d˜ o atrav´ s de um estado Lua, que cont´ m todas as vari´ veis e seus valores correna e e a tes. A aplicacao pode criar mais de um estado Lua. Eles s˜ o todos completamente independentes ¸˜ a uns dos outros. Por isso, cada funcao da API recebe como primeiro parˆ metro o estado Lua sobre ¸˜ a ´ ´ o qual ela deve operar. A unica excecao a essa regra e a funcao lua_open, que cria um estado novo. ¸˜ ` ¸˜ 12 Um estado Lua existe at´ que ele seja fechado, com lua_close. Nesse momento, toda a mem´ ria e o ´ usada pelo estado e liberada, e suas vari´ veis e valores desaparecem. a ´ O principal mecanismo de comunicacao entre Lua e C e uma pilha virtual. Nela, C p˜ e valores ¸˜ o a serem usados por Lua e vice-versa. A pilha pode armazenar valores Lua de qualquer tipo (nil, ´ booleano, numero, string, tabela, funcao, userdata e thread). H´ portanto funcoes da API para por ¸˜ a ¸˜ na pilha valores de cada um desses tipos. H´ tamb´ m funcoes da API para consultar o tipo de um a e ¸˜ valor que est´ na pilha e para convertˆ -lo para um valor C, quando isso faz sentido. (N˜ o faz sentido a e a ´ converter uma tabela Lua para C porque C n˜ o tem tabelas. Mas faz sentido converter um numero a ou string para C.) ´ Como Lua tem coleta autom´ tica de lixo, e necess´ rio estar atento para n˜ o usar valores obtidos a a a ´ de uma pilha inativa. O erro mais comum e guardar um string Lua em C como simplesmente o ponteiro que Lua retorna via lua_tostring: quando a pilha ficar inv´ lida, esse ponteiro pode n˜ o mais a a ´ apontar para o string correspondente (nem para nenhum outro string ou qualquer area v´ lida). A a ´ pilha fica inv´ lida quando a funcao C retorna ou quando o estado e fechado. a ¸˜ 4 Uso de Lua em jogos Nesta secao, discutiremos o uso de Lua em jogos, desde um n´vel mais simples at´ um n´vel sofisti¸˜ ı e ı cado. 4.1 Lua como linguagem de configuracao ¸˜ Como discutimos na Secao 2, no n´vel mais simples uma linguagem de configuracao e uma ma¸˜ ı ¸˜ ´ neira de associar valores a vari´ veis. N˜ o h´ controle de fluxo nem funcoes definidas pelo usu´ rio, a a a ¸˜ a ´ somente uma sequˆ ncia de atribuicoes. Um exemplo t´pico e: e ¸˜ ı -- come¸ar no meio do jogo, usando Mickey... c LEVEL = 13 HERO = "Mickey" ` Mesmo uma linguagem simples como essa j´ d´ uma grande flexibilidade a aplicacao, pois permite a a ¸˜ ao usu´ rio controlar a aplicacao externamente, bastando editar um arquivo texto. a ¸˜ Vejamos como usar Lua nessa situacao do ponto de vista do programador da aplicacao. Estamos ¸˜ ¸˜ portanto agora falando de c´ digo C. (Do ponto de vista do usu´ rio da aplicacao, para usar a linguao a ¸˜ gem de configuracao basta ler a documentacao da aplicacao para saber que vari´ veis existem, quais ¸˜ ¸˜ ¸˜ a os seus poss´veis valores e o que eles significam para a aplicacao. O usu´ rio nem precisa saber que ı ¸˜ a est´ escrevendo na verdade um programa Lua.) a ´ ´ A primeira coisa e carregar essa configuracao de dentro da aplicacao. Antes disso, e preciso ¸˜ ¸˜ inicializar Lua, criando um estado, que vai existir at´ ser fechado: e #include "lua.h" #include "lauxlib.h" ... lua_State *L=lua_open(); ... lua_close(L); Uma vez criado um estado Lua, podemos carregar um arquivo de configuracao, digamos init.lua: ¸˜ luaL_loadfile(L,"init.lua"); lua_pcall(L,0,0,0); 13 Note que a carga da configuracao e feita em dois passos: leitura com luaL_loadfile e execucao com ¸˜ ´ ¸˜ lua_pcall. Isso permite o tratamento separado de erros de sintaxe e erros de execucao. Entretanto, ¸˜ o c´ digo acima n˜ o trata erros. Na pr´ tica, usa-se o c´ digo abaixo ou algo parecido: o a a o if (luaL_loadfile(L,"init.lua") || lua_pcall(L,0,0,0)) error(lua_tostring(L,-1)); ´ onde error e uma funcao que trata erro. A mensagem de erro vinda de Lua est´ no topo da pilha e ¸˜ a ´ portanto e obtida com lua_tostring(L,-1). Assumindo que n˜ o houve erros na carga da configuracao, a execucao de init.lua criou no a ¸˜ ¸˜ ´ estado L as vari´ veis com seus valores dados em init.lua. E hora portanto da aplicacao usar esses a ¸˜ ´ valores. Note que os valores est˜ o em Lua, mas ainda n˜ o em C; e necess´ rio lˆ -los de Lua para C. a a a e Tipicamente, a aplicacao est´ interessada nos valores de algumas vari´ veis espec´ficas, como LEVEL ¸˜ a a ı no exemplo inicial. Podemos ler o valor de LEVEL com lua_getglobal(L,"LEVEL"); ´ Isso lˆ o valor da vari´ vel LEVEL de Lua e deixa esse valor na pilha, que e o mecanismo de comunicacao e a ¸˜ entre Lua e C e vice-versa. Basta agora copiar essa valor para uma vari´ vel C: a level=lua_tonumber(L,-1); assumindo claro que level esteja declarada corretamente em C. Note que n˜ o h´ nenhuma relacao a a ¸˜ entre a vari´ vel C e a vari´ vel Lua. Nesse exemplo, elas nem tˆ m o mesmo nome, somente um nome a a e parecido. Mas mesmo que tivessem o mesmo nome, seriam vari´ veis em mundos separados, sem a ´ nenhuma relacao autom´ tica entre elas. (E poss´vel programar uma tal relacao autom´ tica entre os ¸˜ a ı ¸˜ a mundos C e Lua usando mecanismos avancados de Lua.) ¸ A mesma coisa se aplica para HERO, exceto que agora queremos um string : lua_getglobal(L,"HERO"); hero=lua_tostring(L,-1); ´ E isso e tudo. A aplicacao n˜ o precisa mais de Lua e pode agora fazer o que ela tem que fazer, ¸˜ a usando os valores de level e hero fornecidos pelo usu´ rio. Um c´ digo completo seria ent˜ o algo a o a como: #include "lua.h" #include "lauxlib.h" static int level=0; const char* hero="Minnie"; ... int main(void) { lua_State *L=lua_open(); luaL_loadfile(L,"init.lua"); lua_pcall(L,0,0,0); lua_getglobal(L,"LEVEL"); level=lua_tonumber(L,-1); lua_getglobal(L,"HERO"); hero=lua_tostring(L,-1); play(level,hero); lua_close(L); return 0; } 14 ´ Note que n˜ o podemos fechar o estado Lua antes de chamar play, pois play usa hero, que e a um string obtido de Lua. Para poder fechar o estado Lua antes de chamar play, seria necess´ rio a duplicar o valor de hero antes. ´ Mais uma vez, o c´ digo acima n˜ o trata erros. Isso e feito somente para simplificar a exposicao. o a ¸˜ ´ Na pr´ tica, o tratamento de erros e obrigat´ rio (como em toda aplicacao de qualidade), principala o ¸˜ mente quando se carrega arquivos escritos por usu´ rios: n˜ o se pode exigir que os usu´ rios n˜ o a a a a cometam enganos! (A aplicacao tamb´ m precisa se proteger contra usu´ rios mal intencionados. . . ) ¸˜ e a ´ o O uso de Lua nessa situacao simples pode parecer um exagero. E c´ digo demais para ler dois ¸˜ valores fornecidos pelo usu´ rio. Seria bem mais simples lˆ -los da linha de comando ou mesmo a e de um arquivo, mas sem a necessidade de nomes de vari´ veis. Na pr´ tica, s˜ o necess´ rios muito a a a a mais do que somente dois valores. De qualquer modo, note como usar uma linguagem tem grandes vantagens: coment´ rios, linhas em branco, indentacao, aspas e espacos dentro de aspas s˜ o todos a ¸˜ ¸ a tratados automaticamente e funcionam da maneira como o usu´ rio espera inconscientemente que a eles funcionem. Fazer isso manualmente na aplicacao seria sim uma grande complicacao! ¸˜ ¸˜ Esse n´vel simples de configuracao tamb´ m permite coisas mais complicadas, como definir ı ¸˜ e vari´ veis em funcao de outras: a ¸˜ -- come¸ar no meio do jogo, usando Mickey... c LEVEL = 13 HERO = "Mickey" GREET = "Bom dia " .. HERO .. "! Como vai" SCORE = 1.2 * LEVEL ´ Embora o arquivo continue sendo uma lista de atribuicoes de valores a vari´ veis, e poss´vel ¸˜ a ı usar express˜ es do lado direito das atribuicoes. Entender e executar express˜ es e uma das tarefas o ¸˜ o ´ principais de uma linguagem de programacao. Note aqui a vantagem de termos uma linguagem ¸˜ embutida completa! ´ O usu´ rio pode n˜ o saber que e poss´vel fazer isso, mas assim que ele souber ou descobrir, vai a a ı provavelmente usar atribuicoes complicadas sem ter que pensar muito na sua forma, pois a sintaxe ¸˜ ´ das express˜ es em Lua e a mesma da maioria das linguagens (com a poss´vel excecao do operador o ı ¸˜ de combinacao de strings). ¸˜ 4.2 Lua como linguagem de extens˜ o a ´ O uso de Lua como linguagem de configuracao mostrado na secao anterior ainda e muito simples. ¸˜ ¸˜ Lua oferece facilidades para estruturacao de dados que podemos explorar quando descrevemos os ¸˜ objetos de um jogo. Para ilustrar a discuss˜ o, vamos considerar que precisamos descrever diferentes a armas que podem ser usadas por nossos personagens. Para cada arma, devemos informar seu "fator de agressividade", "alcance de ataque" e "precis˜ o". O conjunto de armas pode ser agrupado numa a tabela, onde os elementos especificam as caracter´sticas de cada arma: ı weapons = { knife = { aggression = 0.3, attackrange = 0.5, accuracy = 1.0, }, sword = { aggression = 0.5, attackrange = 1.5, accuracy = 0.8, 15 }, ... } ´ a Com os dados estruturados, e f´ cil estender o jogo, incluindo, por exemplo, um novo tipo de arma. ´ Dessa forma, a "precis˜ o" de uma espada e obtida consultando o valor de weapons.sword.accuracy. a ´ De C, assumindo que weapons e uma vari´ vel global de Lua, esse valor seria obtido pelo seguinte a trecho de c´ digo: o double accuracy; lua_getglobal(L,'weapons'); lua_pushstring(L,'sword'); lua_gettable(L,-2); lua_pushstring(L,'accuracy'); lua_gettable(L,-2); accuracy = lua_tonumber(L,-1); lua_pop(L,2); /* /* /* /* /* /* /* push weapons on stack */ push string 'sword' */ get weapons.sword */ push string 'accuracy' */ get weapons.sword.accuracy */ convert value to C */ restore Lua stack */ ´ Conforme mencionado na secao anterior, e fundamental que tenhamos verificacao de erros. ¸˜ ¸˜ A verificacao de erros em C seria tediosa. Fica bem mais simples escrever c´ digo Lua que faca a ¸˜ o ¸ verificacao de erros nos scripts escritos pelos usu´ rios (roteiristas, artistas, programadores, ou os ¸˜ a ´ pr´ prios usu´ rios finais dos jogos). Uma maneira simples de fazer a verificacao de erro e incluir o a ¸˜ construtores de tabelas. No exemplo acima, podemos incluir o construtor Weapon para cada arma descrita: weapons = { knife = Weapon{ aggression = 0.3, attackrange = 0.5, accuracy = 1.0, }, sword = Weapon{ aggression = 0.5, attackrange = 1.5, accuracy = 0.8, }, ... } O construtor Weapon pode ent˜ o verificar erros e preencher valores defaults: a funciton Weapon (self) if not self.aggression then self.aggression = 0.5 -- default aggression value elseif self.aggression <> 1.0 then ReportError("Invalid aggression value") ... return self end Podemos ir mais longe, por exemplo, especificando o comportamento dos personagens. Em Lua, como funcoes s˜ o tratadas como valores de primeira classe, esses comportamentos e acoes ¸˜ a ¸˜ 16 podem ser facilmente integrados na descricao de tabelas. Como exemplo, vamos imaginar o mo¸˜ mento em que o personagem encontra uma nova arma. As caracter´sticas da arma encontrada ı podem enriquecer o di´ logo: a weapons = { knife = Weapon{ aggression = 0.3, attackrange = 0.5, accuracy = 1.0, getit = function (person) if person:HasEnoughWeapon() then person:Speak("N~o preciso dessa faca") a return false else person:Speak("Essa faca me ser´ muito ´til") a u return true end end, }, ... } 4.3 Lua como linguagem de controle Nesse terceiro n´vel de utilizacao da linguagem Lua em jogos, invertemos os pap´ is: Lua passa a ı ¸˜ e ser o controlador do jogo, o cliente, e o c´ digo C funciona apenas como servidor, implementando o de forma eficiente os servicos demandados por Lua. Nesse caso, ganhamos uma grande flexibi¸ lidade com o uso de uma linguagem de script. Os programadores C ficam respons´ veis por ima plementar algoritmos eficientes e os "programadores" Lua ficam respons´ veis por criar o roteiro, a a hist´ ria, o comportamento dos personagens, etc. Dessa forma, em C codificamos as engines do o jogo (estruturacao e rendering de cenas, simulacao f´sica, algoritmos de inteligˆ ncia artificial, ge¸˜ ¸˜ ı e renciamento de sons, etc.) e, em Lua, escrevemos o script, decidindo que arma usar, que som tocar, ´ que algortimo de inteligˆ ncia artificial usar, etc. Essa e uma divis˜ o natural para o desenvolvimento e a ´ dos jogos. A vantagem de se usar uma linguagem como Lua e que os profissionais envolvidos com a programacao do roteiro n˜ o s˜ o, em geral, profissionais com experiˆ ncia em programacao. No ¸˜ a a e ¸˜ ´ entanto, aprender a programar um ambiente Lua onde os erros s˜ o automaticamente verificados e a muito simples. Al´ m disso, como n˜ o h´ necessidade de compilacao da aplicacao - que pode ser e a a ¸˜ ¸˜ demorada - o desenvolvimento do jogo fica muito mais r´ pido. a Para que de Lua tenhamos acesso aos servicos oferecidos por C temos que exportar as funcio¸ nalidades de C para Lua. Isso pode ser feito utilizando a API de Lua diretamente ou atrav´ s de ferrae mentas que fazem o mapeamento de forma autom´ tica. No site de Lua, encontram-se dispon´veis a ı algumas dessas ferramentas. A disponibilizacao dos servicos implementados em C para "programadores" Lua pode ser feita ¸˜ ¸ em duas etapas: mapeamento direto das funcoes e classes, e c´ digo de interface em Lua. ¸˜ o Na primeira etapa, usando a API ou uma ferramenta de mapeamento, obtemos um c´ digo C que o ´ exporta as funcoes e m´ todos para Lua. Em geral, isso e feito escrevendo-se funcoes C que, usando ¸˜ e ¸˜ a API de Lua, recebem os parˆ metros de Lua, chamam as funcoes e m´ todos de C e mapeiam os a ¸˜ e valores retornados para Lua. Na segunda etapa, podemos encapsular as chamadas das funcoes e m´ todos de C atrav´ s de ¸˜ e e construtores e funcoes escritos em Lua, elevando o n´vel de abstracao para acesso aos servicos das ¸˜ ı ¸˜ ¸ 17 engines. Dessa forma, fazemos com que a programacao em Lua seja feita de forma simples, facili¸˜ tando o acesso program´ vel a artistas, roteiristas, etc. a Para exemplificar, vamos considerar a implementacao em C++ da classe 'CPerson' que estrutura ¸˜ as caracter´sticas de uma personagem do jogo. A cada personagem associamos uma s´ rie de atriı e butos: nome, energia inicial, listas das armas que sabe manusear, etc. Em C++, esses atributos s˜ o a definidos atrav´ s de chamadas de m´ todos. Podemos tamb´ m prever a implementacao de acoes e e e ¸˜ ¸˜ simples como andar, correr, pular, atacar. A interface da classe em C++ poderia ser dada ent˜ o por: a class CPerson { ... public: CPerson (char* model_file); void SetName (char* name); void SetEnergy (double value); AddSkill (Weapon* w); double GetEnergy (); Walk (); Run (); Jump (); Attack (); ... }; Com o uso de uma ferramenta (ou fazendo o mapeamento diretamente via API), podemos ter acessos a esses m´ todos em Lua. No entanto, n˜ o queremos que o roteirista do jogo tenha que fazer e a chamadas de m´ todos de C++. O quanto poss´vel, devemos dar preferˆ ncias a interfaces descritivas, e ı e como j´ vinhamos fazendo nas secoes anteriores. Um roteirista poderia, por exemplo, instanciar a ¸˜ um novo personagem de forma descritiva: Hero = Person { name = "Tarzan", model = "models/tarzan.mdl", energy = 1.0, skills = {knife, axe} } O construtor, previamente codificado em Lua, seria respons´ vel por instanciar o objeto em C++ a e definir seus atributos iniciais (al´ m de fazer verificacao de erros, que ser´ omitida aqui): e ¸˜ a function Person (self) local cobj = CPerson:new(self.model) cobj:SetName(self.name) cobj:SetEnergy(self.energy) for i,v = ipairs(self.skills) do cobj:AddSkill(v) end return cobj end -- create instance Numa segunda etapa, o roteirista pode programar as acoes associadas ao personagem: ¸˜ 18 ... if Hero:GetEnergy() > 0.5 then Hero:Attack() else Hero:Run() end ... 5 Conclus˜ o a A linguagem Lua tem sido amplamente utilizada no desenvolvimento de jogos. A Lucasarts, por exemplo, usou a vers˜ o 3.1 de Lua para desenvolver os t´tulos "Grim Fandango" e "Scape from Mona ı key Island". A vers˜ o 3.1 de Lua foi por eles modificada para tratar co-rotinas. Hoje, como vimos, a suporte para co-rotinas est´ presenta na vers˜ o 5.0. a a Double Fine utilizou Lua em "Psychonauts" para controlar toda a l´ gica do jogo. Basicamente, o a engine carrega um mundo est´ tico e os scripts em Lua tomam o controle, dando vida e interatia ` vidade as cenas. Em "Baldur's Gate", Bioware usou Lua para prover uma ferramenta de depuracao ¸˜ em tempo-real. Relic utilizou Lua em "Impossible Creatures" para controlar a IA, as aparˆ ncias dos e objetos e personagens, para definir as regras do jogo e tamb´ m como ferramenta de depuracao em e ¸˜ tempo-real. Em "FarCry", Crytek tamb´ m utilizou Lua para controlar diversos aspectos do jogo e e para permitir a criacao de modificadores atrav´ s da codificacao de scripts Lua. ¸˜ e ¸˜ 6 Agradecimentos Os usos de Lua em jogos listados na conclus˜ o foram levantados por Marcio Pereira de Araujo como a parte do trabalho da disciplina "Linguagem Lua", ministrada por Roberto Ierusalimschy, oferecida nos programas de graduacao e p´ s-graduacao do Departamento de Inform´ tica da PUC-Rio. ¸˜ o ¸˜ a 7 Referˆ ncias e Para saber mais sobre Lua, leia o livro "Programming in Lua", o manual de referˆ ncia e os artie gos abaixo. Todos esses documentos e muitas outras informacoes est˜ o dispon´veis no site de Lua ¸˜ a ı (lua.org). • R. Ierusalimschy, Programming in Lua. Lua.org, December 2003. ISBN 85-903798-1-7. • R. Ierusalimschy, L. H. de Figueiredo, W. Celes. "Lua 5.0 Reference Manual". Technical Report MCC-14/03, PUC-Rio, 2003. • R. Ierusalimschy, L. H. de Figueiredo, W. Celes. The evolution of an extension language: a history of Lua, Proceedings of V Brazilian Symposium on Programming Languages (2001) B14-B-28. • R. Ierusalimschy, L. H. de Figueiredo, W. Celes. Lua-an extensible extension language. Software: Practice & Experience 26 #6 (1996) 635-652. • L. H. de Figueiredo, R. Ierusalimschy, W. Celes. Lua: an extensible embedded language. Dr. Dobb's Journal 21 #12 (Dec 1996) 26-33. • L. H. de Figueiredo, R. Ierusalimschy, W. Celes. The design and implementation of a language for extending applications. Proceedings of XXI Brazilian Seminar on Software and Hardware (1994) 273-83. 19