|
Um dos recursos mais importantes e mais comuns para a IA em jogos é o uso de scripts. Eles se fazem presentes desde jogos antigos, como Day of the Tentacle ou Quake, até os mais modernos, como Dragon Age e em praticamente todos os gêneros de jogos.
Nesse artigo, vamos ver uma introdução sobre o que são scripts, e como integrá-los na linguagem Java.
Sobre scripts
Scripts são linguagens de programação projetadas com o intuito de serem simples de se codificar. Geralmente, são embarcáveis, ou seja, seu compilador ou interpretador pode ser acoplado ao jogo, o que nos permite compila-los em tempo de execução. A grande vantagem é que com scripts é possível extrair a lógica para fora do código.
Scripts também podem ser usados para criar aplicações extensíveis, criando mods que podem ser plugáveis mesmo após o release do game bons exemplos disso são os jogos: Neverwinter Nights e Oblivion.
Casos comuns e uteis que se usam scripts
Controle de diálogos: Scripts são ótimas opções para controlar o diálogo entre personagens. Muitos diálogos exigirão que o jogador tenha tomado determinadas ações, ou exigirão testes, muitas vezes complexos, para que a opção apareça. Nessas horas, os scripts caem como uma luva, dando ao game designer muita flexibilidade.
Direção de cena: Um dos principais usos de scripts é fazer a direção de “cutscenes”, onde os NPCs guiam os jogadores. Damos à equipe de produção uma ferramenta poderosa, onde ele pode controlar a câmera, mover personagens, mudar parâmetros do ambiente, de maneira a fazer aquela tomada de tirar o fôlego. Em jogos como Dragon Age, scripts foram usados para fazer, por exemplo, a seqüência final de certos golpes que, quando acontecem, arrancam um sorriso de felicidade na cara do jogador;
Lógica da IA: Boa parte do trabalho de IA consiste em ajustar parâmetros. Scripts permitem mover esses parâmetros para fora do código compilado, tornando mais simples essa configuração e manutenção da IA.
Vantagens no uso scripts
Como as linguagens de script são feitas para serem simples, elas oferecem varias facilidades para programação. A maioria delas tem suporte à sobrecarga de operadores, iteradores simplificados, closures ou tipagem fraca.
Uma outra vantagem é que é possível distribuir a criatividade pelo time de desenvolvimento. Como são linguagens mais simplificadas e legíveis, game designers, produtores e até os jogadores podem escrever e modificar nosso jogo. Para os programadores, isso também representa um grande alívio, já que não terão mais que codificar coisas relacionadas à produção do jogo. Honestamente, quem é que gostaria do game designer no pé, impedindo de fazer aquela rotina complexa de IA, porque a opção de diálogo 2 da personagem X precisa trocar de lugar com a opção 3?
Finalmente, scripts também poupam tempo. É possível mudar facilmente o script em qualquer programa de edição de texto sem precisar recompilar sua aplicação, o que pode ser significativo se você estiver desenvolvendo em C++. Sem falar que isso também evita que o programador perca o “fio da meada”, uma vez que o tempo entre fazer um novo ajuste da IA e ver o seu resultado reduz a quase 0.
Desvantagens
Infelizmente usar scripts não é também um mar de rosas. Existem duas grandes desvantagens:
A primeira é que dificilmente você irá contar com os recursos modernos das IDEs na escrita dos seus scripts. Nada de depuradores, ou verificação imediata de sintaxe. A segunda desvantagem é que o código quase sempre será aberto. Portanto, é importante pensar bem sobre quais rotinas realmente irão se tornar scripts modificáveis.
Como funciona
Linguagens de script são executadas dentro dos programas e devem seguir algum protocolo definido pelo programador.
Imaginem um jogo onde o usuário precisa apenas correr e desviar das armadilhas, no script de uma armadilha seria como o seguinte.
- Programa envia para o script o estado atual do jogo (A posição do nosso personagem distraído).
- Programa pede para o script calcular o novo estado.
Dentro do script como podemos fazer o seguinte:
- Verificar se o jogador esta perto, se estiver muda o estado da armadilha para ativá-la;
- O programa verifica o estado atual da armadilha após o script processar e muda o estado no jogo com esse novo valor;
- Se o jogador caso estiver perto ele é apenas mais um infeliz viajante morto por sua armadilha escondida (Sacana você, hein?).
Scripts e Java
Antes de começar
Como exemplo para esse artigo vou usar o Groovy. Os binários podem ser baixados em http://groovy.codehaus.org/ . Para facilitar apenas adicione o groovy-all-(version).jar no classpath da sua aplicação.
Let’s code
O mais interessante é carregar os scripts dentro do nosso programa, então vamos escrever e carregar um.
Salve em um arquivo nomeado Script.groovy com o seguinte conteúdo (Usar um editor de texto simples mesmo).
class Hello {
void salute(){
System.out.println(“Hello World!”);
}
}
Repare que usamos código java dentro do script, que no caso mostra a mensagem ‘Hello World!’ na saída padrão.
Agora criemos uma classe no Java (pode usar sua IDE favorita pra isso) e vamos carregar o script que fizemos.
import java.io.*;
import javax.script.*;
public class ScriptTest {
public static void main(String[] args) throws FileNotFoundException, ScriptException {
//Arquivo com nosso script
File scriptFile = new File("C:/Script.groovy");
//Contém todas ScriptEngines disponiveis
ScriptEngineManager engines = new ScriptEngineManager();
//Pega a ScriptEngine do groovy.
ScriptEngine engine = engines.getEngineByName("groovy");
Class> clazz = (Class>) engine.eval(new FileReader(scriptFile));
System.out.println(clazz);//imprime "class Hello"
}
}
Repare que ScriptEngineManager e ScriptEngine faz parte do pacote padrão do java: javax.script, sendo assim é fácil mudar de uma engine para outra apenas sendo necessário mudar o parâmetro do getEngineByName. Esse suporte existe desde o Java 6.
O método eval(evaluate) que faz a mágica de carregar o script para nós e transformá-lo em um objeto Class. Ou seja, a partir desse momento, nosso script tornou-se uma classe padrão do Java, e poderemos instanciar objetos dela como faríamos com qualquer classe, usando reflexão.
Para facilitar os exemplos criaremos um método que faz essa rotina para nós e retorna o objeto Class, o resultado é o seguinte:
public static Class> getScript(String file) throws FileNotFoundException,
ScriptException {
File scriptFile = new File(file);
ScriptEngineManager engines = new ScriptEngineManager();
ScriptEngine engine = engines.getEngineByName("groovy");
return (Class>) engine.eval(new FileReader(scriptFile));
}
PS: Essa não é uma boa maneira de tratar as exceções, utilize try/catch.
Agora um exemplo de como criar um objeto e invocar o método da nossa classe.
public static void main(String[] args) throws Exception {
Class> clazz = (Class>) getScript("C:/Script.groovy");
Method salute = clazz.getMethod("salute");// Metodo salute() da classe
Object obj = clazz.newInstance();// Um objeto instanciado de Hello
salute.invoke(obj);
}
Usando Reflection acessamos o método salute() da classe Hello. Repare que não é possível fazer:
Hello obj = (Hello) clazz.newInstance();
Pois até o momento seu programa nem sabia que existia essa classe, essa é a mágica do Script. Reflection é interessante, mas podemos fazer algo que facilite muito mais nosso processo, primeiro vamos escrever uma interface no nosso programa java.
package test;
public interface Saluter {
public void salute();
}
E vamos alterar nosso script para o seguinte.
import test.Saluter
class Hello implements Salute {
void salute(){
System.out.println(“Hello World!”)
}
}
Repare que importamos a interface do nosso pacote e mandamos esse código implementa-la, e o método salute() pertence a interface.
E agora nós podemos fazer um cast do nosso objeto para essa interface no Java.
public static void main(String[] args) throws Exception {
Class> clazz = (Class>) getScript("D:/Script.groovy");
Saluter obj = (Saluter) clazz.newInstance();
obj.salute();
}
Mais simples não? E é claro, é possível fazer essa interface com métodos que recebam parâmetros ou retornem algum valor.
E é isso que permite que façamos scripts para jogos.
Jogo da velha
Como exemplo, vamos pensar em como seria um script para jogar (de maneira bem burra) o jogo da velha. Bem a proposta do jogo é bem simples, marcar 3 sinais iguais em linha, coluna ou diagonal em um tabuleiro de 3x3 lugares.
Para nosso tabuleiro vamos criar uma classe chamada Matrix, ela simplesmente guarda em um array[3][3] os valores correspondentes as jogadas, sendo 0=vazio, 1 =jogador1, 2=jogador2.
Para o jogador humano, simplesmente utilizaremos um MouseListener que percebe onde o jogador pressionou o mouse e verificaremos se é possível marcar naquele lugar. Após isso nosso programa pedirá pede ao jogador da IA a posição da próxima jogada. Nossa IA precisa retornar a próxima jogada levando em consideração os lugares marcados na Matrix.
Utilizaremos para isso um script. Vamos convencionar como nosso protocolo, que o programa chamará um método chamado getJogada, passará a ele a situação atual do tabuleiro, e esperará um retorno retorno, que deve ser um array de duas posições onde na posição 0 temos o índice da coluna e na posição 1 o índice da linha.
Podemos codificar esse protocolo na forma de uma interface java:
package player;
import game.Matrix;
public interface Player {
public int[] getJogada(Matrix matrix);
}
Então escrevemos um script que respeita essa interface.
Vamos criar uma lógica simples, que simplesmente faz uma jogada num ponto aleatório (até mesmo IAs randômicas são IA). Caso o ponto esteja ocupado, ele tentará novamente em outra posição, até que seja possível marcar.
import game.Matrix
import player.Player
import java.util.Random
class ScriptPlayer implements Player{
def rdm = new Random()
int[] getJogada(Matrix m){
int i = -1 ,j = -1;
while(!m.isEmpty(i,j)){
i = rdm.nextInt(3)
j = rdm.nextInt(3)
}
return [i,j]
}
}
Voltando ao nosso jogo devemos carregar esse script como vimos antes como uma instancia de Player e utilizar seus métodos para verificar qual a jogada do nosso player AI.
Concluindo
Vimos nesse artigo o quão importante é o uso dos scripts em jogos e um exemplo prático, em Java. Você pode baixar os exemplos de código aqui. Lembre-se que para rodá-lo, também será necessário baixar o groovy.
Você também poderá se interessar por outras linguagens de script. Como o javascript ou o ruby, no caso do Java, as linguagens Lua, TCL ou Python, para o C++, ou ainda as linguagens Boo e a VBScript, para o C#.










