복습

자바 18일차 - 소코반 만들기 본문

자바

자바 18일차 - 소코반 만들기

ykm1256 2020. 4. 10. 17:29

1. 기본 창 열기

package game_001;
import javax.swing.*;

public class GameTest002 extends JFrame {  //프레임을 상속받아서 만듦
	JButton button = new JButton("ㅋㅋㅋ");
	
	public GameTest002(){
		this.getContentPane().setLayout(null); //패널 설정
		button.setBounds(20,20,100,50); // 버튼 좌표 (x,y) 크기 (가로,세로)
		//프레임에 컴포넌트 추가
		this.add(button);		
		//프레임 크기 지정	
		this.setSize(300, 600);
		//프레임 보이기
		this.setVisible(true);
		//swing에만 있는 X버튼 클릭시 종료
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	public static void main(String[] args) {
		GameTest002 frameExam = new GameTest002();
	}
}​

"ㅎㅎㅎ"로 타이틀을 지정하고 JFrame클래스를 생성한다. 그리고 "ㅋㅋㅋ"로 타이틀 지정 후 버튼을 생성한다. 

createFrame()이라는 프레임을 생성하는 메서드를 구현하는데 setLayout을 null 값을 주어 레이아웃은 따로 지정하지 않았다. 그리고 각각의 메서드를 이용하여 프레임 속성을 지정하고 버튼을 추가하여 생성했다.

메인에서 GameTest001 클래스 생성 후 메서드를 호출하여 창을 띄운다.

 

 

2.  JFrame 클래스를 상속받아 사용하기.

package game_001;
import javax.swing.*;

public class GameTest002 extends JFrame {  //프레임을 상속받아서 만듦
	JButton button = new JButton("ㅋㅋㅋ");
	
	public GameTest002(){
		this.getContentPane().setLayout(null); //패널 설정
		button.setBounds(20,20,100,50); // 버튼 좌표 (x,y) 크기 (가로,세로)
		//프레임에 컴포넌트 추가
		this.add(button);		
		//프레임 크기 지정	
		this.setSize(300, 600);
		//프레임 보이기
		this.setVisible(true);
		//swing에만 있는 X버튼 클릭시 종료
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	public static void main(String[] args) {
		GameTest002 frameExam = new GameTest002();
	}
}

1번에서는 메인클래스 안에 jFrame 클래스를 따로 생성하여서 메서드도 따로 구현했다. 하지만 메인클래스인 GameTest002 JFrame을 상속받아서 클래스 기본생성자에 메서드에서 구현한 코드를 적어 따로 메서드를 만들지 않고 메인 클래스만으로 구현하였다.

 

3. 사진파일 출력하기

package game_001;

import java.awt.*;
import javax.swing.*;

public class GameTest003 extends JFrame {
	
	Image IPerson;
	
	public GameTest003(){
		IPerson = Toolkit.getDefaultToolkit().getImage("ogu.gif");
		this.setSize(600, 200);
		this.setTitle("GameTest003");
		this.setVisible(true);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);		
	}
	@Override
	public void paint(Graphics g) {  // 화면을 다시 그려야 할 때 사용
		g.drawImage(IPerson, 150, 50, 100, 100, this);
	}

	public static void main(String[] args) {
		GameTest003 game3 = new GameTest003();
	}
}

 위의 코드에서 Image 타입으로 IPerson을 선언한다. 그리고 생성자안에서 

IPerson = Toolkit.getDefaultToolkit().getImage("ogu.gif");로 IPerson에 ogu.gif라는 이미지 파일을 불러와 대입한다.

그리고 paint()메서드를 재정의하여 IPerson에 있는 사진을 출력하는데,  x좌표 150, y좌표 50, 너비 100, 높이 100로 출력하고 this를 사용하여 현재 인스턴스에서 출력한다.

 

4. 기본적인 틀, 동작 구현하기

package game_001;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;

public class GameTest008 extends JFrame implements KeyListener {
	public static final int SIZE_TITLE = 23;
	public static final int SIZE_LINETHICK = 8;
	public static final int SIZE_TILE_X = 20;
	public static final int SIZE_TILE_Y = 15;
	public static final int SIZE_IMAGE_X = 60;
	public static final int SIZE_IMAGE_Y = 60;
	public static final int MV_OFFSET = SIZE_IMAGE_X;
	Image IPerson;
	int iPersonX = SIZE_LINETHICK;
	int iPersonY = SIZE_LINETHICK + SIZE_TITLE;
	int iPersonOldX = 0;
	int iPersonOldY = 0;
	
	public GameTest008(){
		Container containerObj = this.getContentPane();
		containerObj.setLayout(null);
		containerObj.addKeyListener(this);
		containerObj.setFocusable(true);
		containerObj.requestFocus();
		
		IPerson = Toolkit.getDefaultToolkit().getImage("bird.jpg");
		this.setSize(SIZE_IMAGE_X*SIZE_TILE_X+SIZE_LINETHICK*2, 
				SIZE_IMAGE_Y*SIZE_TILE_Y+SIZE_LINETHICK*2+SIZE_TITLE);
		this.setTitle("GameTest008");
		this.setVisible(true);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
	}
	@Override
	public void paint(Graphics g) {
		g.clearRect(iPersonOldX, iPersonOldY, SIZE_IMAGE_X, SIZE_IMAGE_Y);
		g.drawImage(IPerson, iPersonX, iPersonY, SIZE_IMAGE_X, SIZE_IMAGE_Y, this);
	}

	public static void main(String[] args) {
		GameTest008 game = new GameTest008();
	}
	@Override
	public void keyTyped(KeyEvent e) {
	}	
	@Override
	public void keyPressed(KeyEvent e) {
		iPersonOldX = iPersonX;
		iPersonOldY = iPersonY;
        
		switch(e.getKeyCode()){
			case KeyEvent.VK_UP : iPersonY = iPersonOldY - MV_OFFSET;
				break;
			case KeyEvent.VK_DOWN : iPersonY = iPersonOldY + MV_OFFSET;
				break;
			case KeyEvent.VK_LEFT : iPersonX = iPersonOldX - MV_OFFSET;
				break;
			case KeyEvent.VK_RIGHT : iPersonX = iPersonOldX + MV_OFFSET;
				break;
		}		
		repaint();
	}
	@Override
	public void keyReleased(KeyEvent e) {
	}
}

위의 예제를 토대로 게임 캐릭터를 움직이게 하는 것을 구현했다. 먼저 상수로  제목 표시줄과 상하좌우 틀의 크기를 측정하여 선언했고, 캐릭터가  x축, y축 방향으로 갈 수 있는 칸 개수를 선언했다. 그리고 캐릭터 이미지의 크기, 한 칸 움직였을 때 얼마만큼 이동할 것인지를 설정했다.

 

5. 배열을 이용하여 맵 구현하기.

package game_001;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;

public class GameTest013 extends JFrame implements KeyListener {
	public static final int SIZE_TITLE = 23;
	public static final int SIZE_LINETHICK = 3;
	public static final int SIZE_TILE_X = 20;
	public static final int SIZE_TILE_Y = 15;
	public static final int SIZE_IMAGE_X = 60;
	public static final int SIZE_IMAGE_Y = 60;
	public static final int MV_OFFSET = SIZE_IMAGE_X;
	Image Road 				= Toolkit.getDefaultToolkit().getImage("road.png");
	Image Wall 				= Toolkit.getDefaultToolkit().getImage("Wall.jpg");
	Image IPersonFront 		= Toolkit.getDefaultToolkit().getImage("pigF.jpg");
	Image IPersonBack 		= Toolkit.getDefaultToolkit().getImage("pigB.jpg");
	Image IPersonLeft 		= Toolkit.getDefaultToolkit().getImage("pigL.jpg");
	Image IPersonRight 		= Toolkit.getDefaultToolkit().getImage("pigR.jpg");
	Image IPerson 			= IPersonFront;
	int iPersonX 			= 0;
	int iPersonY 			= 0;
	int iPersonOldX 		= 0;
	int iPersonOldY 		= 0;
	char[][] Map 			= new char[SIZE_TILE_Y][SIZE_TILE_X];
	String[] Stage 			= { "####################",    // 0 
								"#                  #",    // 0 
								"#                  #",    // 0 
								"#                  #",    // 0 
								"#     #########    #",    // 0 
								"#     #########  # #",    // 0 
								"#       ##      ## #",    // 0 
								"#  @    #    ####  #",    // 0 
								"#       #   ####   #",    // 0 
								"#       #   #      #",    // 0 
								"#     ###   #      #",    // 0 
								"#              ### #",    // 0 
								"#    ###########   #",    // 0 
								"#                  #",    // 0 
								"####################"	   // 14		
								};
	
	void LoadMap() {
		for(int i = 0 ; i < SIZE_TILE_Y; ++i) {
		Map[i] = Stage[i].toCharArray();
		}
	}
	
	public GameTest013(){
		LoadMap();
		Container containerObj = this.getContentPane();
		containerObj.setLayout(null);
		containerObj.addKeyListener(this);
		containerObj.setFocusable(true);
		containerObj.requestFocus();
		
		this.setSize(SIZE_IMAGE_X*SIZE_TILE_X+SIZE_LINETHICK*2, 
				SIZE_IMAGE_Y*SIZE_TILE_Y+SIZE_LINETHICK*2+SIZE_TITLE);
		this.setTitle("GameTest012");
		this.setVisible(true);
		this.setResizable(false);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
	}
	@Override
	public void paint(Graphics g) {		
		Image ITemp;
		for(int i = 0; i < SIZE_TILE_Y ; ++i) {
		      for(int j = 0; j < SIZE_TILE_X; ++j) {
		    	  
		    	  System.out.print(Map[i][j]);	  
		    	  switch(Map[i][j]) {
		    	  case '#' : 
		    		  	  ITemp = Wall;
		    	  break;
		    	  case ' ' : 
		    		  	  ITemp = Road;
		    	  break;
		    	  case '@' : 
		    		  	  ITemp = IPerson;
		    		  iPersonX 	= j;
		    		  iPersonY 	= i;
		    	  break;
		    	  default  : 
		    	  continue;
		    	  }	  		    	  
		    	  
		        /*if('#' == Stage[i].charAt(j)) {
		        	ITemp = IPerson;		        	
		        }else if(' ' == Stage[i].charAt(j)) {
		        	ITemp = Road;		        	
		        }else if('@' == Stage[i].charAt(j)) {
		        	ITemp = IPerson;      	
		        }*/    		        
		        
		        g.drawImage(ITemp, 
	        			SIZE_LINETHICK       +		  j*SIZE_IMAGE_X, 
	        			SIZE_LINETHICK + SIZE_TITLE + i*SIZE_IMAGE_Y, 
	        				SIZE_IMAGE_X, SIZE_IMAGE_Y, 
	        				this);		        
		      }
		      System.out.println();
		    }
	}
	
	void movePerson() {
		Map[iPersonOldY][iPersonOldX] = ' ';
		Map[iPersonY][iPersonX] = '@';
		
	}

	public static void main(String[] args) {
		GameTest013 game = new GameTest013();		
	}
	@Override
	public void keyTyped(KeyEvent e) {
	}	
	@Override
	public void keyPressed(KeyEvent e) {
		iPersonOldY = iPersonY;
		iPersonOldX = iPersonX;

		    switch(e.getKeyCode()){
			case KeyEvent.VK_UP : 
				--iPersonY;
				IPerson = IPersonBack;
				break;
			case KeyEvent.VK_DOWN : 
				++iPersonY;
				IPerson = IPersonFront;
				break;
			case KeyEvent.VK_LEFT : 
				--iPersonX;
				IPerson = IPersonLeft;
				break;
			case KeyEvent.VK_RIGHT : 
				++iPersonX;
				IPerson = IPersonRight;
				break;
		}
		movePerson();
		repaint();
	}
	@Override
	public void keyReleased(KeyEvent e) {
	}
}

기존의 코드에서 이미지 변수를 추가하여 벽, 길, 그리고 캐릭터의 앞, 옆 ,뒷모습을 나타내는 이미지를 가져왔다. 그리고 char형으로 2차원 배열을 선언하고, String형으로 1차원 배열을 선언한다. String 배열에 맵의 정보가 담겨있는데 위와 같이 문자열이 여러개가 담겨있다. 이를 2차원 배열에서 문자를 하나씩 받아와서 대입한다. 여기서 #은 벽, @는 캐릭터 위치이다. 그리고 Load()메서드를 만들어 String 배열에 있는 맵정보를 받아온다. 그리고 이를 paint메서드에서 이중 for문을 이용하여 Map의 값을 받아온다. 그리고 Switch-case문으로 받아온 문자 값이 #이면 벽이미지, @이면 캐릭터이미지, 공백이면 길 이미지를 출력하도록 한다. 그리고 movePerson()메서드에서는 캐릭터가 이동하게 되면 이동 전의 좌표에

' ' (공백)을 넣어 길이미지가 출력되도록(잔상이 남지않도록) 한다. 그 후 메인 메인메서드에서 각 키를 눌렀을 때 각각의 배열 행 또는 열 값을 변화시켜 캐릭터의 위치가 바뀌도록 하였다. 배열을 사용하기 전에는 이미지의 크기를 계산하여 그만큼 좌표를 옮겨줘야 했지만 배열을 사용하게 되면 배열의 위치만 바꿔주면 되므로 코드가 더 간단해졌다.