• Nenhum resultado encontrado

Desenvolvimento de Jogos com AgenteJ

N/A
N/A
Protected

Academic year: 2021

Share "Desenvolvimento de Jogos com AgenteJ"

Copied!
13
0
0

Texto

(1)

Desenvolvimento de Jogos com AgenteJ

(Versão 0.1 – 02/08/2018) (Em construção)

Prof. Adilson Vahldick

2018-2

(2)

Revisões

(3)

SUMÁRIO

1. Usando o teclado ... 4

2. Desbloquear ... 4

3. Adicionando novos objetos no mundo ... 5

4. Remover objetos do mundo ... 5

5. Criando seus novos tipos de objetos do mundo ... 6

6. Configurando velocidades diferentes para os objetos... 6

7. Sorteio ... 7

8. Gestão de Vários Mundos ... 7

9. Compartilhar Dados ... 8

10. Uma pausa na execução ... 8

11. Executar um método após um tempo ... 9

12. Configurações de aparência no mundo ... 9

13. Acesso direto às células ... 9

14. Colisão de objetos ... 10

15. Animando os seus objetos ... 10

16. Energia ... 11

17. Imagem ... 12

18. Os demais métodos ... 12

(4)

1. Usando o teclado

Através do método getUltimaTeclaPress() é possível obter do sistema o código da última tecla pressionada. Cada tecla corresponde a um código diferente. Para você descobrir os códigos das teclas que pretende usar no jogo, construa as seguintes linhas de código em um na inteligência do AgenteJ.

public void inteligencia() throws Exception {

while (true) {

int tecla = getUltimaTeclaPress();

if (tecla > 0) { diga(tecla);

} }

}

Ao executar o agente, experimente pressionar teclas e observe a impressão na console do código dessas teclas.

Importante chamar a atenção que o método não fica esperando o usuário pressionar teclas, mas se ao executar esse método estiver sendo pressionada alguma tecla, então ela retorna um valor diferente de zero.

Já existem algumas teclas mapeadas no Agente: TECLACIMA, TECLABAIXO, TECLAESQUERDA, TECLADIREITA e TECLAESPACO. Com isso, o código retornado pelo método getUltimaTeclaPress() pode ser comparado com essas constantes.

while (true) {

int tecla = getUltimaTeclaPress();

switch (tecla) { case TECLACIMA:

andarAcima();

break;

case TECLABAIXO:

andarAbaixo();

break;

case TECLAESPACO:

diga("Atirei");

break;

} }

2. Desbloquear

Em muitos exercícios durante o semestre você conseguia fazer o agente ficar por cima de outros objetos, e em outras situações o agente explodia ao tentar passar por cima dos objetos (normalmente as paredes). Essa diferença entre poder ou não poder passar por cima dos objetos é considerada uma característica chamada bloqueio. Se um objeto está marcado como bloqueado, explodirá qualquer coisa que tentar passar por cima. Por padrão, todo objeto começa bloqueado, e você precisa explicitamente desbloquear o objeto.

No XML, basta usar o elemento <bloqueado>, ou no código use o método desbloquear().

Alien inim1 = new Alien();

inim1.desbloquear();

<objeto class="br.furb.furbot.Alien">

<random/>

<bloqueado>false</bloqueado>

</objeto>

(5)

3. Adicionando novos objetos no mundo

Até agora você viu que a única forma de adicionar objetos no mundo é indicando no XML. Existe outra forma que é através de métodos do AgenteJ:

 adicionarObjetoNoMundo(ObjetoDoMundo obj, Direcao dir): a partir da posição onde está o agente, adiciona o objeto referenciado no primeiro parâmetro, em uma das cinco direções (AQUIMESMO também pode ser usado) indicado no segundo parâmetro;

 adicionarObjetoNoMundoColLin(ObjetoDoMundo obj, int col, int lin): adiciona o objeto referenciado no primeiro parâmetros nas posições col e lin. Não esqueça que a primeira linha é lin=0 e a primeira coluna é col=0;

 adicionarObjetoNoMundoEmbaixo(ObjetoDoMundo obj, Direcao dir): a partir da posição onde está o agente, adiciona o objeto referenciado no primeiro parâmetro, em uma das cinco direções indicado no segundo parâmetro. A diferença desse método com os dois anteriores: se tiver outro objeto na mesma posição com os dois métodos anteriores o novo objeto sempre ficará por cima dos anteriores, e nesse método embaixo, ele será inserido por baixo.

A utilização desses métodos é muito útil quando o agente precisa disparar um tiro, por exemplo. O código abaixo apresenta o uso dos dois primeiros métodos: cria dois novos Aliens (observe a sintaxe usando a palavra reservada new) e adiciona-os no mundo.

Alien alien1 = new Alien();

adicionarObjetoNoMundo(alien1, DIREITA);

Alien alien2 = new Alien();

adicionarObjetoNoMundoColLin(alien2, 1, 4);

A execução do jogo é interrompida com uma exceção se tentar incluir o mesmo objeto duas vezes. Por exemplo, o código abaixo dispara uma exceção.

Alien alien1 = new Alien();

adicionarObjetoNoMundo(alien1, DIREITA);

adicionarObjetoNoMundo(alien1, ACIMA);

A execução do jogo também pode ser interrompida com uma exceção se tentar incluir objetos diferentes na mesma posição que estejam bloqueados. Veja a seção anterior como desbloquear os objetos. Por exemplo, o código abaixo dispara uma exceção.

Alien alien1 = new Alien();

adicionarObjetoNoMundo(alien1, DIREITA);

Alien alien2 = new Alien();

adicionarObjetoNoMundo(alien2, DIREITA);

4. Remover objetos do mundo

Também é possível remover um objeto do mundo (esse é um recurso muito útil quando algum elemento precisa sumir). O código abaixo apresenta o exemplo de como remover um objeto que o agente encontrou.

ObjetoDoMundo obj = getObjeto(AQUIMESMO);

removerObjetoDoMundo(obj);

(6)

5. Criando seus novos tipos de objetos do mundo

O AgenteJ tem uma grande flexibilidade que permite que você faça seus próprios tipos de objetos do mundo, permitindo atribuir uma imagem a ele e programar um comportamento para esse objeto. Assim como até agora você programava cada novo agente através de uma classe, é o mesmo que vai acontecer para criar esses novos objetos.

Entretanto, existem algumas diferenças que você precisa prestar muita atenção. Nos quadros abaixo estão parte do código de um agente e de um objeto do mundo.

public class MeuRobo extends AgenteJ {

public void inteligencia() throws Exception { // aqui eh a inteligencia do agente }

public static void main(String[] args) { MundoVisual.iniciar("MeuMundo.xml");

} }

public class Inimigo extends ObjetoDoMundoAdapter {

public ImageIcon buildImage() {

return LoadImage.getInstance().getIcon("inimigo.png");

}

public void executar() throws Exception {

// aqui eh a inteligencia dos demais objetos }

}

O agente você já está acostumado a programar, e conhece suas características. Você já sabe que precisa estender de AgenteJ e implementar o método inteligencia. Já quando você faz um novo tipo para o mundo, precisa estender de ObjetoDoMundoAdapter e implementar o método executar. Nesse método você pode usar todos os métodos que já vinha usando (andarDireita(), diga(), etc), variáveis e estruturas de controle (if, while, for, etc).

O segundo quadro ilustra como podes definir uma imagem para o novo tipo. O mesmo procedimento poderias fazer para o AgenteJ se você desejar usar outra imagem diferente do nosso detetive. São aceitas imagens nos formatos PNG, GIF e JPG. As imagens devem ter tamanho de 50 X 50, por ser a dimensão padrão de cada célula. O caminho relativo para o arquivo da imagem é considerado a partir da raiz do projeto.

Veja um exemplo abaixo como colocar no mundo um objeto da sua nova classe.

<objeto class="Inimigo ">

<random/>

<bloqueado>false</bloqueado>

</objeto>

Uma última observação importante: normalmente colocaremos o método main() somente na classe do agente, apesar de que você pode colocar em qualquer classe. Um jogo tem somente um AgenteJ.

6. Configurando velocidades diferentes para os objetos

Num jogo normalmente temos vários objetos animados com velocidades independentes. Você sabe que a velocidade no AgenteJ é baseada numa barra de rolagem presente na janela. Mas também é possível que se especifiquem velocidades diferentes para cada objeto no mundo. Para isso utilizamos o método setTempoEspera (int), que recebe como parâmetro o tempo de espera (em milissegundos) entre executar um comando e outro. Logo, quanto menor o valor passado como parâmetro, menor o tempo de espera, e consequentemente, mais rápido será a execução de seus comandos. No código a seguir, podemos observar que o tempo de espera entre executar um comando e outro do

(7)

agente é 0 e do inimigo são 100 milissegundos. Assim você consegue fazer o agente se mover independente do que o usuário especificou na barra.

public void inteligencia() throws Exception { setTempoEspera(0);

Inimigo inim1 = new Inimigo();

adicionarObjetoNoMundo(inim1, AQUIMESMO);

inim1.setTempoEspera(100);

}

7. Sorteio

Para deixar um jogo interessante, existem momentos em que os objetos precisam sortear algo, por exemplo, uma direção ou a velocidade do tiro. Para isso, você utilizará o método sortear(). Esse método retorna um int. Por exemplo, o código abaixo sorteia um número e faz o resto da divisão por quatro. De acordo com o resultado, que fica entre 0 e 3, o agente toma uma decisão.

int sorteio = sortear() % 4;

switch (sorteio) { case 0:

andarAcima();

break;

case 1:

andarAbaixo();

break;

case 2:

andarEsquerda();

break;

case 3:

andarDireita();

break;

}

8. Gestão de Vários Mundos

Essa seção explicará como fazer quando você desenvolver um jogo com várias fases, e cada fase tem uma configuração diferente de mundo. Para começar, você vai precisar de um arquivo XML diferente para cada mundo. Feito isso, você usará um método do MundoVisual, como exemplificado abaixo.

MundoVisual.iniciarSequencia(Jogo.class, "Fase1.xml", "Fase2.xml", "Fase3.xml");

Nesse exemplo não estamos mais utilizando o método iniciar que você usou em todos os exercícios. No primeiro parâmetro você especifica que implementação do AgenteJ será usada. A quantidade de parâmetros, a partir do segundo, é variável: para cada mundo que tiver no seu jogo, você adiciona um parâmetro com o nome do XML.

Durante a execução do método inteligencia, pode ser que você precise saber qual é o mundo atual para, por exemplo, mudar a velocidade de algum inimigo. No exemplo acima haviam três mundos. O primeiro mundo sempre tem índice zero. O quadro abaixo demonstra o método para saber qual é o mundo atual.

int faseAtual = MundoVisual.getMundo();

Para finalizar essa seção, falta ainda aprendermos como fazer para mudar de mundo. Existem dois métodos para isso.

O primeiro, está exemplificado abaixo. Esse método sinaliza para trocar de mundo. Mas atenção: a troca do mundo

(8)

só acontecerá quando sair do método inteligencia. Enquanto isso, permanecerá executando o mundo atual. Logo após a saída do método inteligencia, o agente já vai carregar o próximo mundo, e executa novamente a mesma inteligencia.

MundoVisual.proxMundo();

Outra forma de mudar de mundo é especificar o índice do novo mundo. Se por exemplo, o jogo pode evoluir para qualquer mundo, em vez de seguir na sequência, esse novo método será mais útil.

MundoVisual.proxMundo(novaFase);

Não esqueça: a troca do mundo só acontecerá após sair da inteligencia. Se houver sinalizado a troca através do método proxMundo, um novo mundo será carregado, e a inteligencia recomeçará a sua execução.

9. Compartilhar Dados

Para garantir o máximo de independência entre os objetos do mundo, e ao mesmo permitir que eles possam se comunicar, o AgenteJ disponibiliza uma forma de guardar e acessar dados entre os objetos, que chamaremos de atributos do MundoVisual. No quadro abaixo está exemplificado como guardar o valor em um atributo. O primeiro parâmetro é o nome do atributo e o segundo é um valor qualquer. É possível armazenar qualquer coisa nesse atributo, por exemplo, inteiros, booleanos, String, até Aliens e outros objetos !!! Os valores serão sobrescritos sempre que usar o mesmo nome de atributo. Esse recurso pode ser usado para, por exemplo, em um inimigo guardar como um inteiro a vida dele, e no agente consultar quanto ele tem de vida.

MundoVisual.setAtributo("umNumero", 130);

Para consultar os atributos, existem dois métodos que podem ser usados, conforme o quadro abaixo. O primeiro método serve para verificar se existe o atributo com o nome passado como parâmetro. O segundo método serve para obter o valor do atributo. Aqui está exemplificado o retorno como uma variável inteira. Porém, como já mencionado no parágrafo anterior, é possível armazenar qualquer valor, e assim ao obtê-lo, deve ser usado o mesmo tipo de variável, senão ocorre uma exceção. Por exemplo, se for armazenado um inteiro, e tentar obter e guardar em uma variável do tipo boolean, então vai disparar uma exceção que não consegue converter um int em um boolean.

boolean b = MundoVisual.temAtributo("umNumero");

int n = MundoVisual.getAtributo("umNumero");

Para remover um atributo utilize o método removerAtributo, conforme o exemplo abaixo.

MundoVisual.removerAtributo("umNumero");

10. Uma pausa na execução

Em alguns momentos é útil que um objeto aguarde alguns segundos durante sua execução. Por exemplo, uma bomba que explode após 3 segundos. O quadro abaixo apresenta um exemplo de código que após esperar 3 segundos mostra uma mensagem na console.

esperar(3);

diga("Agora vai");

(9)

11. Executar um método após um tempo

Na seção anterior conhecemos uma forma de executar um código baseado no tempo. Com o método esperar o objeto (seja o agente ou qualquer outro) aguarda um tempo (como se congelasse) até voltar a vida e executar qualquer código. Porém, existem situações em que não se deseja que o objeto congele enquanto espera algo acontecer. Nesses casos vamos usar o método invoqueApos. Observe no quadro abaixo o uso desse método: o primeiro parâmetro indica após quantos segundos deve ser executado o método descrito no segundo parâmetro. Nesse parâmetro deve ser informada uma String com o nome do método. Observe a assinatura do método: ele precisa ser público, sem retorno e sem parâmetros. No exemplo, após 5 segundos o AgenteJ executa o método acabouEnergia.

public void inteligencia() throws Exception { ...

invoqueApos(5, "acabouEnergia");

...

}

public void acabouEnergia() { ...

}

12. Configurações de aparência no mundo

Nessa seção vamos apresentar dois elementos no XML que fazem modificar a aparência (devem ficar dentro do elemento <mundo>):

 tamanhoCel: utilizar esse elemento quando o mundo tem muitas células, e a alternativa é diminuir o tamanho das células. Por padrão as células têm o tamanho de 50X50. As alternativas são M (40X40), P (30X30), A (20X20) e B (10X10):

 usarLinhasNaGrade: essa opção é para esconder as linhas verticais e horizontais na grade.

<mundo>

...

<usarLinhasNaGrade>false</usarLinhasNaGrade>

<tamanhoCel>P</tamanhoCel>

</mundo>

13. Acesso direto às células

Durante o semestre, você praticou vários exercícios em que tinha que o agente precisa se mover até a célula (ou nas proximidades) para então obter o seu valor. A primeira possibilidade que você vai conhecer é mover o próprio agente para uma determinada posição no mundo. O quadro abaixo apresenta o método a ser utilizado, onde no primeiro parâmetro vai o número da coluna e no segundo o número da linha.

mudarPosicao(coluna, linha);

Assim como você conheceu o método getObjeto(Direcao) que retornava um objeto na direção especificada, e se espera que o tipo da variável coincida com o mesmo tipo do próprio objeto naquela posição, o quadro abaixo ilustra o método para obter um objeto diretamente de uma posição.

Alien alien1 = getObjetoColLin(coluna, linha);

(10)

14. Colisão de objetos

A colisão de objetos acontece quando dois objetos estão na mesma célula. Existe um método que pode ser implementado no seu agente ou mesmo nos outros objetos, que é executado sempre que acontece uma colisão. Basta implementar o método abaixo, chamado colidido. Por exemplo, se esse método for implementado no agente, todo objeto que ele colidir será executado esse método. O exemplo ilustra que se o objeto que colidir for do tipo Inimigo então o objeto é removido do mundo. Na seção 4 você aprendeu uma forma de remover um objeto do mundo. Nesse quadro você pode observar a outra maneira usando o método removerMe().

public void colidido(ObjetoDoMundo objetoMundo) { if (objetoMundo.getSouDoTipo().equals("Inimigo")) { objetoMundo.removerMe();

} }

Existe algo que preciso lhe chamar atenção: um objeto removido não é interrompida sua execução. Por exemplo, vamos supor que o método abaixo seja do Inimigo. Se com o código acima, o Inimigo fosse removido, a sua execução não seria interrompida. Inclusive, em algum momento vai disparar uma exceção por estar executando comandos de um objeto removido.

public void executar() throws Exception {

while (true) { ...

}

}

Existe um método que pode ser usado para resolver isso: jahRemovido() que retorna true caso ele tinha sido removido do mundo. Veja abaixo a refatoração do código usando esse método. A execução continua enquanto o objeto não for removido do mundo.

public void executar() throws Exception {

while (!jahRemovido()) { ...

}

}

15. Animando os seus objetos

Imagine que você gostaria de dar uma animada nas imagens de seu agente e seus objetos. Como não são suportados GIFs animados, então terá que você mesmo ser responsável por essa animação. No quadro abaixo está exemplificado o método criarImagem() que carrega uma imagem dependendo de um atributo num. O método criarImagem, por padrão, é chamado uma única vez. Porém, existe uma forma de forçar que o mundo do agente chame outra vez esse método. Para isso, você pode usar o método troqueImagem(). Veja no exemplo abaixo, que em algum momento dentro do método executar() é chamado o método troqueImagem(). Como a implementação da imagem, dentro do criarImagem depende do valor do atributo num, é ilustrado que o mesmo tem seu valor trocado imediatamente antes de chamar o método.

(11)

public ImageIcon criarImagem() {

return LoadImage.getInstance().getIcon("ini"+num+".png");

}

public void executar() throws Exception { ...

num = 2;

troqueImagem();

...

}

16. Energia

Existe a possibilidade de definir uma quantidade de energia para o agente. Assim, a cada comando executado uma quantidade de energia é consumida. Se não houver energia suficiente para executar o comando, então a execução é interrompida com uma exceção avisando que acabou a energia.

A quantidade de energia é especificada no XML através do bloco <energia>valor</energia>. O quadro abaixo exemplifica que o agente tem no máximo 30 pontos de energia. Com isso, é exibida uma barra de energia vermelha ao lado da imagem do agente.

<agente>

<x>0</x>

<y>0</y>

<energia>30</energia>

</agente>

Existem alguns métodos úteis para se informar sobre o uso de energia:

 ehDependenteEnergia():boolean. Retorna true se foi configurada a energia para o agente;

 getMaxEnergia():int. Retorna o valor configurado de energia:

 getEnergia():int. Retorna a energia atual.

O gasto de energia entre os comandos difere:

 5 pontos: ehVazio(Direcao), ehVazio(col, lin), ehFim

 10 pontos: diga

 15 pontos: getObjeto(Direcao)

 20 pontos: métodos para andar

 30 ponto: getObjeto(col, lin)

 100 pontos: mudarPosicao(col, lin)

(12)

17. Imagem

Você viu como criar novas classes na seção 5. Algumas vezes a única coisa que precisamos é um objeto na tela muito simples que represente somente uma coisa estática, que o agente sequer vai querer saber o que é. Por exemplo, na imagem abaixo tem o coração (para representar a quantidade de vidas) e uma estrela (para representar a quantidade de pontos). Esses dois objetos o agente (Pacman) jamais vai alcançar.

Uma forma de definir esses objetos que nada mais representam uma imagem na tela. No XML basta definir um elemento <img> e o nome da imagem através do atributo source. Veja um exemplo no quadro abaixo.

<img source="vidas.png">

<x>1</x>

<y>14</y>

</img>

18. Os demais métodos

Para que você tenha noção do que existe disponível, tanto no agente quanto nos demais objetos, vamos listar abaixo todos os métodos com uma descrição explicando eles. Os métodos mais elementares como para andar, dizer e outras coisas do tipo, foram eliminados nessa lista.

br.udesc.lagentj.AgenteJ

Método Descrição

ehVazio(col, lin):boolean Retorna true se existe algum objeto na posição (col, lin).

getQtdadeLinMundo():int Retorna a quantidade de linhas no mundo.

getQtdadeColMundo():int Retorna a quantidade de colunas no mundo.

pararMundo():void Pára a execução do mundo.

getInt():int Se a célula atual tiver um Numero, então retorna o seu valor, senão retorna 0 (zero).

(13)

br.udesc.lagentj.ObjetoDoMundoAdapter (todos os métodos abaixo também são válidos para o AgenteJ)

Método Descrição

criarImagem():ImageIcon Retorna uma imagem para ser usada pelo objeto no mundo getObjetoColLin(col, lin): T Retorna o objeto que estiver na posição (col, lin), ou null caso não

exista.

ehDependenteEnergia(): boolean Retorna true se o objeto foi marcado para ter energia.

getEnergia():int Retorna o valor da energia atual.

getMaxEnergia():int Retorna o valor da energia especificado no XML.

esperar(segundos):void Aguarda a quantidade de segundos antes de executar a próxima linha.

sortear():int Sorteia um número inteiro (negativo ou positivo).

ehObjetoDoMundoTipo(classe, direcao):boolean

Verifica se o objeto que está na posição indicada em direcao é do tipo indicado no primeiro parâmetro.

ehBloqueado():boolean Retorna true se o objeto está bloqueado.

bloquear():void Bloqueia o objeto.

desbloquear():void Desbloqueia o objeto.

getSouDoTipo():String Retorna o nome da classe do objeto.

troqueImagem():void Força para que o mundo execute novamente o método criarImagem.

colidido(ObjetoDoMundo):void Método chamado quando outro objeto entrou na mesma célula do objeto atual.

invoqueApos(segundos, metodo):void

Chama o método com o nome passado no segundo parâmetro após uma quantidade de segundos especificado no primeiro parâmetro.

Referências

Documentos relacionados

A prova do ENADE/2011, aplicada aos estudantes da Área de Tecnologia em Redes de Computadores, com duração total de 4 horas, apresentou questões discursivas e de múltipla

17 CORTE IDH. Caso Castañeda Gutman vs.. restrição ao lançamento de uma candidatura a cargo político pode demandar o enfrentamento de temas de ordem histórica, social e política

O Museu Digital dos Ex-votos, projeto acadêmico que objetiva apresentar os ex- votos do Brasil, não terá, evidentemente, a mesma dinâmica da sala de milagres, mas em

nhece a pretensão de Aristóteles de que haja uma ligação direta entre o dictum de omni et nullo e a validade dos silogismos perfeitos, mas a julga improcedente. Um dos

Equipamentos de emergência imediatamente acessíveis, com instruções de utilização. Assegurar-se que os lava- olhos e os chuveiros de segurança estejam próximos ao local de

Tal será possível através do fornecimento de evidências de que a relação entre educação inclusiva e inclusão social é pertinente para a qualidade dos recursos de

6 Consideraremos que a narrativa de Lewis Carroll oscila ficcionalmente entre o maravilhoso e o fantástico, chegando mesmo a sugerir-se com aspectos do estranho,

Na sua qualidade de instituição responsável pela organização do processo de seleção, o Secretariado-Geral do Conselho garante que os dados pessoais são tratados nos termos do