Ponto V!

Home Java Informações gerais Utilizando scripts nos jogos
Marcos Vasconcelos
Utilizando scripts nos jogosPDFImprimir
Escrito por Marcos Vasconcelos

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.

  1. Programa envia para o script o estado atual do jogo (A posição do nosso personagem distraído).
  2. Programa pede para o script calcular o novo estado.

Dentro do script como podemos fazer o seguinte:

  1. Verificar se o jogador esta perto, se estiver muda o estado da armadilha para ativá-la;
  2. O programa verifica o estado atual da armadilha após o script processar e muda o estado no jogo com esse novo valor;
  3. 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#.

Comentários (0)
Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2