• Cenário

    Muitas vezes queremos construir jogos mais complicados. Mas montar um jogo de labirinto por exemplo com o que você leu até agora, não seria uma tarefa fácil. Para isso nós usamos um cenário.

    No Jplay existe uma classe chamada Scene que é utilizada para a construção de cenários.

    1 - Antes de começar a construir o código

    Antes de escrever qualquer linha de código pense em como o seu cenário é, quais são as imagens estáticas (não animadas), quais as imagens dinâmicas como sprites e animations. As imagens estáticas serão as que estarão inseridas no arquivo que compõe a cena.

    Tenha em mente que todas as imagens do cenário devem ter a mesma dimensão, se você tem imagens que são retângulos então todas devem ser retângulos.

    Feito isso, você está pronto para começar a construção do arquivo do seu cenário. As seções 2 e 3 cuidam dessa construção.

    2 - Organização do arquivo do cenário

    No Jplay as informações para a construção do cenário são quardadas em um arquivo com a extensão '.scn'.

    O arquivo é organizado da seguinte forma:

    linha 1 - número de imagens usadas para a construção do cenário (as imagens estáticas).
    linha 2 - caminho da primeira imagem e sua extensão
    linha 3 - caminho da segunda imagem e sua extensão
    linha 4 - caminho da terceira imagem e sua extensão
    linha n+1 - caminho da n-ésima imagem e sua extensão
    linha n+2 - começo da construção da matriz
    linha m - fim da construção da matriz que é representada pelo caracter '%'.
    linha m+1 - caminho do backdrop e sua extensão.

    Obs.: Não deixe nenhuma linha em branco pois, isso gerará um erro de execução.

    Exemplo de uma arquivo '.scn'.

    				3
    				wall.jpg
    				floor.jpg
    				star.jpg
    				0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    				0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
    				0,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0
    				0,1,2,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,1,0
    				0,1,2,1,2,2,2,1,2,2,2,2,2,2,2,1,2,2,2,2,2,2,1,0
    				0,1,2,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,2,2,2,2,1,0
    				0,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0
    				0,1,2,1,1,1,1,2,2,2,1,1,1,1,1,1,2,2,1,1,1,2,1,0
    				0,1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,0
    				0,1,2,1,2,2,2,1,2,2,2,2,2,2,2,1,1,1,1,1,2,2,1,0
    				0,1,2,1,2,2,2,1,1,1,1,1,1,1,2,1,2,2,2,1,2,2,1,0
    				0,1,2,1,2,2,2,2,2,2,1,2,2,2,2,1,2,2,2,1,1,1,1,0
    				0,1,2,1,2,2,2,2,2,2,1,2,2,2,2,1,2,2,2,2,2,2,1,0
    				0,1,2,1,1,1,2,2,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,0
    				0,1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,3,0
    				0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
    				0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    				%
    				backdrop.jpg			
    			

    3 - Construindo a matriz

    A matriz é composta por números inteiros separados por virgula e sem espaços entre eles.

    O número 0 (zero) sempre corresponde ao backdrop (a imagem que será desenhada por baixo do cenário).

    Já os outros número seguem a seguinte lógica:
    1 - representa a imagem wall.jpg
    2 - representa a imagem flor.jpg
    3 - representa a imagem star.jpg

    Colocamos o número 0 (zero) nas bordas sinalizando que essa é uma área do backdrop.

    O número 1 é usado como 'wall' e delimita passagens.
    O número 2 é usado como lugares pecorríveis.
    O número 3 é usado como ponto final de chegada.

    4 - Construindo o código para mostrar um cenário

    Para construir o código nós usaremos os seguintes métodos da classe Scene:

    				//Construtor da classe
    				public Scene();
    				
    				//Ccarrega as informações armazenadas no arquivo '.scn'.
    				public void loadFromFile(String fileName);
    				
    				//Informa ao Jplay quais são as coordenadas iniciais onde o cenário deverá ser desenhado inicialmente.
    				public void setDrawStartPos(int drawStartX, int drawStartY);
    				
    				//Desenha o cenário na tela
    				public void draw();
    				 
    			

    Exemplo: Mostrando um cenário na tela.

    				package cenario;
    
    				import java.awt.Point;
    				import java.util.Vector;
    				import jplay.Keyboard;
    				import jplay.Scene;
    				import jplay.TileInfo;
    				import jplay.Window;
    
    				/**
    				 * @author Gefersom Cardoso Lima
    				 * Federal Fluminense University
    				 * Computer Science
    				 */
    				 
    				public class Cenario {
    
    					private Window window;
    					private Scene scene;
    					
    					public Cenario()
    					{
    							window = new Window(800,600);
    							scene = new Scene();
    							scene.loadFromFile("scene.scn");
    							scene.setDrawStartPos(15, 30);
    
    					}
    
    					public void run()
    					{
    							while(true)
    							{
    								scene.draw();
    								window.update();							
    							}
    					}
    					
    					/**
    					 * @param args the command line arguments
    					 */
    					public static void main(String[] args) {
    						Cenario cen = new Cenario();
    						cen.run();
    					}		
    				}
    				 
    			

    5 - Adicionando um objeto ao cenário.

    Para adicionar um objeto que será controlado por nós ao cenário usamos o método abaixo:

    				//Adiciona qualquer GameObject do Jplay ao cenário mas, não modifica a 
    				//matriz que é usada para a construção do cenário.
    				public void addOverlay(GameObject overlay);
    				 
    			

    Com o método acima podemos adicionar um GameImage, uma Animation ou um Sprite ao cenário.

    6 - A classe auxiliar TileInfo.

    Quando o Jplay lê o arquivo de entrada do cenário ele cria para cada valor inteiro da matriz armazenada no arquivo '.scn' um TileInfo.

    As informações armazenadas no TileInfo são: 'id' - valor numérico que representa a imagem e uma referência para a imagem.

    Todos os TileInfos são armazenados em um matriz usada internamente pela classe Scene.

    Exemplo:

    A imagem 'wall.jpg' é presentando pelo número 1.
    O Jplay cria um TileInfo com a ID = 1 e associa esse TileInfo a imagem 'wall.jpg' que é armazenada em outra estrutura interna na classe Scene.

    É por meio do TileInfo que podemos saber onde um determinado overlay está localizado e quais são as imagens sobrepostas por ele ou em volta dele.

    7 - Delimitando áreas pecorríveis.

    Método da classe Scene usados nessa seção:

    				//Retorna os objetos que estão entre os ponto passados como parâmetros.
    				public Vector getTilesFromRect(Point min, Point max)
    			

    Se não delimitarmos áreas em que o GameObject (adicionado com o método addOverlay()) pode percorrer ele se moverá por todo o cenário.

    Para fazer o GameObject se mover somente por algumas áreas seguimos os seguinte passos:

    1 - Capturamos os valores mínimos e máximos para as coordenadas do GameObject.

    			//Lembre-se que as coordenadas de todo GameObject no Jplay são números no formato 'double'
    			//logo, algumas vezes precisaremos fazer um cast para um número inteiro.
    			
    			//Posicao min é a posição (x,y) do GameObject			
    			Point playerMin = new Point ((int)gameObject.x, (int)gameObject.y);
    			
    			//Posicao max é a posição (x + largura, y + altura) do GameObject
    			Point playerMax = new Point((int)(gameObject.x + player.width), (int)(gameObject.y + gameObject.height));
    			

    2 - Capturamos as imagens que estão na mesma área do GameObject.

    		
    				//Retorna as imagens que estiverem na mesma área do GameObject (x,y), (x + largura, y + altura)
    				Vector tiles = scene.getTilesFromRect(playerMin, playerMax);			
    			

    3 - Para cada tile retornado verificamos qual é o tile a partir da sua id e se o player colidiu com ele.

    Depois do teste de colisão e de verificação de id o que será feito é uma escolha do programador.

    No trecho abaixo o GameObject somente é reposicionado alguns pixels. Já que para haver uma colisão alguns pixels têm que ser sobrepostos. Caso não fizessemos isso o GameObject ficaria colidindo infinitamente com o TileInfo.

    			
    				//para todos os tiles
    				for(int i = 0 ; i < tiles.size() ; i++)
    				{
    					TileInfo tile = (TileInfo)tiles.elementAt(i);
    					
    					//se o tile é parede e o player colidiu com ele
    					if((tile.id == Constante.TILE_WALL) && player.collided(tile))
    					{
    							//se o player está se movendo na diagonal
    							if (player.moveuNaHorizontal())
    							{
    									//o player está atrás da parede?
    									if(player.x  <= tile.x -1)
    										player.x = tile.x - player.width;//Reposiciona o player
    									else
    										//o player está na frente da parede
    										player.x = tile.x + tile.width;//Reposiciona o player
    							}
    							else
    							{       //o player está abaixo da parede?
    									if(player.y >= tile.y  + tile.height -1)
    										player.y = tile.y + tile.height;//Reposiciona o player
    									else
    										//o player está acima da parede
    										player.y = tile.y - player.height ;//Reposiciona o player
    							}
    							return false;
    					}
    					else
    						//se o player encontrou a estrela
    						if(tile.id == Constante.TILE_STAR)
    							return true;
    				}
    				return  false;
    			}			
    			

    Exemplo:

    		
    				import jplay.Keyboard;
    				import jplay.Sprite;			
    				import java.awt.Point;
    				import java.util.Vector;
    				import jplay.Keyboard;
    				import jplay.Scene;
    				import jplay.TileInfo;
    				import jplay.Window;
    
    				/*----------------------------------------------------------------------------------------------*/
    				
    				public class Constante 
    				{
    						public static int TILE_WALL = 1;
    						public static int TILE_STAR = 3;
    				}			
    
    				/*----------------------------------------------------------------------------------------------*/
    				
    				public class Player extends Sprite
    				{
    						private boolean horizontalMoveKeyPressed;
    						
    						public Player()
    						{
    								super("player.png",8);
    						}
    
    						public boolean moveuNaHorizontal()
    						{
    								return horizontalMoveKeyPressed;
    						}
    						
    						public void move(Keyboard keyboard)
    						{
    								horizontalMoveKeyPressed = true;
    								if(keyboard.keyDown(Keyboard.LEFT_KEY) == true)
    								{
    									this.x -= 1;
    									setCurrFrame(2);
    								}
    								else
    									if(keyboard.keyDown(Keyboard.RIGHT_KEY) == true)
    									{
    										this.x += 1;
    										setCurrFrame(3);
    									}
    									else
    										horizontalMoveKeyPressed = false;
    
    								if (!horizontalMoveKeyPressed)
    								{
    									if(keyboard.keyDown(Keyboard.UP_KEY) == true)
    									{
    										this.y -= 1;
    										setCurrFrame(0);
    									}
    									else
    										if(keyboard.keyDown(Keyboard.DOWN_KEY) == true)
    										{
    											this.y += 1;
    											setCurrFrame(1);
    										}
    								}
    						}
    				}			
    
    				/*----------------------------------------------------------------------------------------------*/
    				
    				public class Cenario 
    				{
    					private Window window;
    					private Keyboard keyboard;
    					private Scene scene;
    					private Player player;
    					
    					public Cenario()
    					{
    							window = new Window(800,600);
    							keyboard = window.getKeyboard();
    							scene = new Scene();
    							scene.loadFromFile("scene.scn");
    							scene.setDrawStartPos(15, 30);
    
    							player = new Player();
    							player.x = 454;
    							player.y =  140;
    							scene.addOverlay(player);
    					}
    
    					public void run()
    					{
    							boolean loop = true;
    							while(loop)
    							{
    								draw();
    								controlarCaminho();
    								player.move(keyboard);
    
    								if (keyboard.keyDown(Keyboard.ESCAPE_KEY))
    									loop = false;
    								window.delay(10);
    							}
    							window.exit();
    					}
    
    					public void draw()
    					{
    							scene.draw();
    							window.update();
    					}
    
    					boolean controlarCaminho()
    					{
    							//Posicao min é a posição (x,y) do player
    							//Posicao max é a posição (x + largura, y + altura) do player
    							Point playerMin = new Point ((int)player.x, (int)player.y);
    							Point playerMax = new Point((int)(player.x + player.width), (int)(player.y + player.height));
    
    							//Retorna as imagens que estiverem na mesma área do player (x,y), (x + largura, y + altura)
    							Vector tiles = scene.getTilesFromRect(playerMin, playerMax);
    
    							//para todos os tiles
    							for(int i = 0 ; i < tiles.size() ; i++)
    							{
    								TileInfo tile = (TileInfo)tiles.elementAt(i);
    								
    								//se o tile é parede e o player colidiu com ele
    								if((tile.id == Constante.TILE_WALL) && player.collided(tile))
    								{
    										//se o player está se movendo na diagonal
    										if (player.moveuNaHorizontal())
    										{
    												//o player está atrás da parede?
    												if(player.x  <= tile.x -1)
    													player.x = tile.x - player.width;//Reposiciona o player
    												else
    													//o player está na frente da parede
    													player.x = tile.x + tile.width;//Reposiciona o player
    										}
    										else
    										{       //o player está abaixo da parede?
    												if(player.y >= tile.y  + tile.height -1)
    													player.y = tile.y + tile.height;//Reposiciona o player
    												else
    													//o player está acima da parede
    													player.y = tile.y - player.height ;//Reposiciona o player
    										}
    										return false;
    								}
    								else
    									//se o player econtrou a estrela
    									if(tile.id == Constante.TILE_EXIT)
    										return true;
    							}
    							return  false;
    					}
    					
    					/**
    					 * @param args the command line arguments
    					 */
    					public static void main(String[] args) {
    							Cenario cen = new Cenario();
    							cen.run();
    					}
    				}	
    				 
    			

    8 - Todos os métodos da classe Scene.

    				
    				//Construtor da classe
    				public Scene();
    				
    				//Carrega as informações armazenadas no arquivo '.scn'.
    				public void loadFromFile(String sceneFile);
    				
    				//Adiciona qualquer GameObject do Jplay ao cenário mas, não modifica a 
    				//matriz que é usada para a construção do cenário.
    				public void addOverlay(GameObject overlay);
    				
    				//Informa ao Jplay quais são as coordenadas iniciais onde o cenário deverá ser desenhado inicialmente.
    				public void setDrawStartPos(int drawStartX, int drawStartY);
    				
    				//Desenha o cenário na tela
    				public void draw();
    				
    				//Retorna um tile da matrix de tiles especificado pela sua linha e coluna na matrix, 
    				//caso a linha ou a coluna não existam retorn null.
    				public TileInfo getTile(int row, int colunm);
    				
    				//Retorna os objetos que estão entre os ponto passados como parâmetros.
    				public Vector getTilesFromRect(Point min, Point max);
    
    				//Retorna os objetos que estão entre os ponto passados como parâmetros 
    				//(deve ser usado em substituição à função anterior, em jogos do tipo side scrolling).
    				public Vector getTilesFromPosition(Point min, Point max);
    				
    				//Remove um tile da matrix de tiles especificado pela sua linha e coluna na matrix.
    				public void removeTile(int row, int colunm);
    				
    				//Troca a ID do tile especificado pela sua linha e coluna pela ID de um outro tile.
    				public void changeTile(int row, int colunm, int newID);
    				
    				//Salva o estado corrente da Scene em arquivo para posterior recuperação, os overlays não serão salvos.
    				public void saveToFile(String fileName);
    
    				//Move o cenario de forma a centralizar o object passado como parâmetro.
    				public void moveScene(GameObject object);
    
    				//Retorna a última movimentação do cenário no eixo x.
    				public double getXOffset();
    
    				//Retorna a última movimentação do cenário no eixo y.
    				public double getYOffset();
    				 
    			

    UFF - Universidade Federal Fluminense - Institudo de Computação - Ciência da Computação