본문 바로가기

Coding_Study/Java_Code_Memo

JButton으로 슈팅게임 3-1 (충돌) 몬스터 죽이기

충돌을 하기에 앞서 각 버튼의 위치를 받아서 비교하는 알고리즘을 공부해야 했어요.

처음에는 하나하나 계산을 해서,  총알의 크기가 20,20이니까 19만큼의 여유를 양쪽으로 더 줘야겠다 해서 충돌 범위를 x축에 19, 69를 더해 그 안에 들어왔을 때 충돌로 인정한다, 라는 if문과 전쟁을 했습니다.

그런데 하다보니, 각 객체의 크기에 따라 제가 다 계산해서 붙이면 게임 회사 이미 문 닫았겠다는 생각이 확 들더라고요. 비효율적이라서.

 

그래서 찾아보니 역시 갓 개발자님들 사각형 충돌 올려놓으셨더라고요! (사실 강사님이 공수해주심 ㅎㅎ)

그걸 긁어다가 붙여도 되긴 하지만, 이해를 하지 않고 붙이면 나중에 조립만 하는 개발자가 될 수 있다는 말이 생각나서 한참을 씨름했습니다. 너무 힘들어서 구구절절 서론이 길었네요...

 

 

 

 

전제 : 총알과 몬스터가 충돌했을 때 몬스터와 총알이 사라진다. 

         (오늘은 몬스터만 지우는 것으로 한다)

         

오늘 할 일 : 알고리즘을 이해하고 몬스터 class에 method로 넣어 활용하기

                  총알의 위치받아오기

 

 

 

 

	private boolean isDead(int monX, int monY, JButton bullet){ //충돌 알고리즘
	      if (Math.abs((monX+this.getWidth()/2)-(bullet.getX()+bullet.getWidth()/2))  
	            < (bullet.getWidth()/2+this.getWidth()/2)
	       && Math.abs((monY+this.getHeight()/2)-(bullet.getY()+bullet.getHeight()/2))  
	            < (bullet.getHeight()/2+this.getHeight()/2)){
	         return true;
	      }else{
	         return false;
	      }          
	 }

비전공자에 수포자로서 이해하는 방법입니다.

같은 식을 봐도 사고의 흐름이 다르다는 게 확 느껴지시나요?

 

1. 연두랑 보라가 가까워질수록 숫자가 적어진다.

2. 연두는 별의 중앙점을 찾아내는 역할, 보라는 총알의 중앙점을 찾아내는 역할을 한다.

    둘 사이의 값을 빼주면 남는 건 바로 절반의 크기와 반대쪽 거리이다.

3. (반대쪽 거리의 값은 필요 없으니) 남은 절반들_총알, 몬스터_을 합친 수와 비교한다.

4. 합친 수 보다 값이 크다는 의미는 둘 사이에 남은 게 더 있다는 뜻으로 겹치지 않는다는 것이고

    반대라면 저 반쪽들 외에는 남은 값이 없다는 뜻이니 겹친다는 것과 같다.

 

제 의식의 흐름을 적어보았습니다. 

혹시나 저처럼 수학적 사고가 어려운 사람들에게 도움이 되었으면 합니다.

//더 효율적으로 이해하는 방법이 있다면 알려주세요!

 

 

 

 

알고리즘을 이해했다면 저 속에 필요한 것이 무엇인지 아시겠죠?

바로, 비교할 '기준'위치와 상대의 위치, 이 두 가지입니다.

 

문제는 메인(Play_Phase1)에 있는 스레드로 몬스터 class와 총알 class를 만드는데,

같은 버튼을 시간마다 계속해서 사용하기 때문에  ['그'버튼의 위치를 가지고 온다]는게 너무 어려웠습니다.

그래서 이름을 주기 위해 key값이 있는 덩어리 Map에 총알을 담기로 했습니다.

 

 

 

 

 

- 메인(Play_Phase1)에 전역 변수로 총알 Map을 만들어 해당 클래스를 담아줌

public HashMap<Integer,BtnBullet> listBullet = new HashMap<Integer,BtnBullet>(); //총알 묶음
private int idxBullet;

- 메인(Play_Phase1)에 있는 run()의 내용을 갱신

- key값이 무한정 늘어나지 않도록 넉넉히 값을 주되 재활용

/**Bullet count*/
if(countTime%500 == 0&& isFire == true) {
isFire = false;
idxBullet += 1; //Map의 Key값 설정
if (idxBullet == 30) { //key값이 너무 커지지 않게 재활용
idxBullet = 0;
}
listBullet.put(idxBullet, new BtnBullet(this, idxBullet));
(new Thread(listBullet.get(idxBullet))).start();
}

 

 

 

 

- 총알 class(BtnBullet)를 수정

- idx 값을 메인(Play_Phase1)에서 받아오도록 생성자를 수정

BtnBullet(Play_Phase1 play1, int idx){
		this.play1 = play1;
		this.idx = idx;
		
		//btnMe의 위치에 따라 총알의 초기값을 조정해주기
		if(play1.poMe != null) {//btnMe의 중간에서 총알이 나가게 함 
			bulletX = play1.poMe.x+15;
			bulletY = play1.poMe.y;
		} else{ //만약 움직이지 않으면 Point 메소드에 값이 전달되지 않아 null이 됨
			bulletX = 240;
			bulletY = 720;
		}
		
		ImageIcon icon = new ImageIcon("총알.png"); //이미지 처리
		this.setIcon(icon);
		this.setBorderPainted(false);
		this.setFocusPainted(false);
		this.setContentAreaFilled(false);
		this.setBounds(bulletX, bulletY, 20,20);
		
		/**add Bullet*/
		play1.add(this);
}
	

- run()에 약간의 변화를 줌

   >> 당장 총알을 없애지는 않겠지만 배열에 맞춰 해당 총알이 없어질 수 있도록 idx값을 가진 버튼을 사라지게 작업.

public void run() {
		while(bulletY > 30) { //끝에 닿을 때 까지 움직이기
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			/**run Bullet*/
			this.setLocation(bulletX, bulletY-= 30);
			//System.out.println(this.getLocation());//테스트 코드
		}
		play1.remove(this);
		play1.listBullet.remove(idx);
		play1.repaint();
		play1.revalidate();
}

 

 

 

 

 

자 이제, 사전 작업은 끝났습니다. 끝이 보입니다.

2020/12/11 - [Java_Study/Code_Memo] - JButton으로 슈팅게임 2-3 몬스터 1 만들기

 

JButton으로 슈팅게임 2-3 몬스터 1 만들기

이번에는 하늘에서 떨어지는 몬스터를 만들어볼 생각입니다. 당연히 몬스터도 JButton이겠죠? 저는 몬스터1의 이미지를 붉은 별로 했어요. 배경이 너무 마음에 드는데 적은 노란별로 했더니 안보

mintpearl-story.tistory.com

2020/12/11 - [분류 전체보기] - JButton으로 슈팅게임 2-4 몬스터 2,3 만들기

 

JButton으로 슈팅게임 2-4 몬스터 2,3 만들기

이번에는 좀 쉬어가는 타임으로, 이전에 만들었던 몬스터 1번을 복붙해서 2,3을 만들겠습니다. 왜냐하면 다음에 할 일이 '충돌'인데, 벌써 무섭거든요 ... 전제 : 몬스터 1은 1.5초당 x 는 랜덤, y는 -

mintpearl-story.tistory.com

이전에 만들었던 몬스터 버튼을 변경해주겠습니다.

 

 

- isDead 메소드를 추가한다.

- for문을 돌려 map에 있는 총알을 긁어옴

- isDead Method에 현재 JButton의 위치와 긁어온 총알의 위치를 하나하나 비교해줌

- true값이 나오는 총알이 있다면 break를 사용

  break를 만나면 무조건 나오기 때문에 while문 break까지 두 번을 연달아 사용할 수 없음

  거쳐가는 값 boolean타입의 inMonsterOver를 이용해 충돌하면 true값을 넣고,

  if문을 바로 넣어서 while문을 break시킴

package Game1;

import java.util.Map;

import javax.swing.ImageIcon;
import javax.swing.JButton;

public class Monster1 extends JButton implements Runnable {
	Play_Phase1 play1;
	
	private int monX, monY;
	private boolean isMonsterOver;
	
	Monster1(Play_Phase1 play1){
		this.play1 = play1;
		
		monX =(int)((Math.random()*420)+20); //위치 설정
		monY = -50;
		
		ImageIcon icmon = new ImageIcon("별_빨강.png"); //이미지 설정
		this.setIcon(icmon);
		this.setBorderPainted(false);
		this.setFocusPainted(false);
		this.setContentAreaFilled(false);
		this.setBounds(monX, monY, 50, 50);
		
		/**add Monster*/
		play1.add(this);
	}
	
	private boolean isDead(int monX, int monY, JButton bullet){ //충돌 알고리즘
	      if (Math.abs((monX+this.getWidth()/2)-(bullet.getX()+bullet.getWidth()/2))  
	            < (bullet.getWidth()/2+this.getWidth()/2)
	       && Math.abs((monY+this.getHeight()/2)-(bullet.getY()+bullet.getHeight()/2))  
	            < (bullet.getHeight()/2+this.getHeight()/2)){
	         return true;
	      }else{
	         return false;
	      }          
	 }

	
	@Override
	public void run() {
		while(monY<700){
			try {
				Thread.sleep(100);//적이 떨어지는 시간
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			/**set Location*/
			this.setLocation(monX, monY+=15);
			
			/**'Monster' is crashed with 'Bullet'*/
			for(Map.Entry<Integer, BtnBullet> bullet:play1.listBullet.entrySet()){
	            if(isDead(monX,monY,play1.listBullet.get(bullet.getKey()))){
	            	//System.out.println("맞췄다!Monster1"); //테스트 코드
	            	isMonsterOver=true;
	            	break;
	            }
	        }
			if(isMonsterOver){
	            break;
	        }
			
		}
		play1.remove(this);
		play1.repaint();
		play1.revalidate();
	}

}
package Game1;

import java.util.Map;

import javax.swing.ImageIcon;
import javax.swing.JButton;

public class Monster2 extends JButton implements Runnable {
	Play_Phase1 play1;
	
	private int monX, monY;
	private boolean direction;
	private boolean isMonsterOver;
	
	Monster2(Play_Phase1 play1){
		this.play1 = play1;
		
		monX =(int)((Math.random()*420)+20); //위치 설정
		monY = -50;
		
		ImageIcon icmon = new ImageIcon("별_빨강.png"); //이미지 설정
		this.setIcon(icmon);
		this.setBorderPainted(false);
		this.setFocusPainted(false);
		this.setContentAreaFilled(false);
		this.setBounds(monX, monY, 50, 50);
		
		/**add Monster*/
		play1.add(this);
	}
	
	private boolean isDead(int monX, int monY, JButton bullet){ //충돌 알고리즘
	      if (Math.abs((monX+this.getWidth()/2)-(bullet.getX()+bullet.getWidth()/2))  
	            < (bullet.getWidth()/2+this.getWidth()/2)
	       && Math.abs((monY+this.getHeight()/2)-(bullet.getY()+bullet.getHeight()/2))  
	            < (bullet.getHeight()/2+this.getHeight()/2)){
	         return true;
	      }else{
	         return false;
	      }          
	 }

	
	@Override
	public void run() {
		while(monY<700){
			try {
				Thread.sleep(100);//적이 떨어지는 시간
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			/**set Location*/
			
			if (direction == false) { //오른쪽으로
				this.setLocation(monX+=10, monY+=15);
				if (monX > 430) {
					direction = true;
				}
			} else if (direction == true) { //왼쪽으로
				this.setLocation(monX-=10, monY+=15);
				if (monX < 20) {
					direction = false;
				}
			} 
			
			/**'Monster' is crashed with 'Bullet'*/
			for(Map.Entry<Integer, BtnBullet> bullet:play1.listBullet.entrySet()){
	            if(isDead(monX,monY,play1.listBullet.get(bullet.getKey()))){
	            	//System.out.println("맞췄다!Monster2"); //테스트 코드
	            	isMonsterOver=true;
	            	break;
	            }
	         }
			if(isMonsterOver){
	            break;
	         }
			
			
		}
		play1.remove(this);
		play1.repaint();
		play1.revalidate();
	}
		

}
package Game1;

import java.util.Map;

import javax.swing.ImageIcon;
import javax.swing.JButton;

public class Monster3 extends JButton implements Runnable {
	Play_Phase1 play1;
	
	private int monX, monY;
	private boolean isMonsterOver;
	
	Monster3(Play_Phase1 play1){
		this.play1 = play1;
		
		monX = -50;
		monY = (int)((Math.random()*500)+20); //위치 설정
		
		ImageIcon icmon = new ImageIcon("별_주황.png"); //이미지 설정
		this.setIcon(icmon);
		this.setBorderPainted(false);
		this.setFocusPainted(false);
		this.setContentAreaFilled(false);
		this.setBounds(monX, monY, 50, 50);
		
		/**add Monster*/
		play1.add(this);
	}
	
	private boolean isDead(int monX, int monY, JButton bullet){ //충돌 알고리즘
	      if (Math.abs((monX+this.getWidth()/2)-(bullet.getX()+bullet.getWidth()/2))  
	            < (bullet.getWidth()/2+this.getWidth()/2)
	       && Math.abs((monY+this.getHeight()/2)-(bullet.getY()+bullet.getHeight()/2))  
	            < (bullet.getHeight()/2+this.getHeight()/2)){
	         return true;
	      }else{
	         return false;
	      }          
	 }
	
	@Override
	public void run() {
		while(monX<500){
			try {
				Thread.sleep(100);//적이 떨어지는 시간
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			/**set Location*/
			this.setLocation(monX+=30, monY);
			
			/**'Monster' is crashed with 'Bullet'*/
			for(Map.Entry<Integer, BtnBullet> bullet:play1.listBullet.entrySet()){
	            if(isDead(monX,monY,play1.listBullet.get(bullet.getKey()))){
	            	//System.out.println("맞췄다!Monster3"); //테스트 코드
	            	isMonsterOver=true;
	            	break;
	            }
	         }
			if(isMonsterOver){
	            break;
	         }
			
		}
		play1.remove(this);
		play1.repaint();
		play1.revalidate();
	}
		

}

 

이렇게 하면 무적 총알로 몹들을 다 죽일 수 있게 됩니다.

오늘 여러 class를 왔다 갔다 하며 수정 작업을 하느라 내용이 누락됐을 수도 있다는 생각이 들어요.

문제가 있거나 더 나은 방법이 있다면 언제든지 조언 부탁드립니다.