JAVA

자바 54강. 버블게임 만들기(5) : 벽 충돌 감지

JJJAEOoni 2022. 2. 10. 14:58
반응형

이전에 점프까지 구현을 완료했다.

이번엔 벽 충돌 감지를 구현해보자.

 

지형지물은 무조건 색상으로 감지해야 한다.

좌표로 하는 건 말도 안 되는 짓이다.

 

스테이지가 조금이라도 바뀌면 좌표가 다 망가지기 때문이다.

 

이제 맵과 플레이어가 의존하게 된다.

맵도 하나의 오브젝트로 보고

플레이어와 관계시켜 준다.

 

플레이어가 어디 부딪힌다고 좌표를 계산하면

각각의 지형마다 모두 관계를 맺어야 한다.

 

한 명이 10개 오브젝트에 의존하는 것보다

10개의 오브젝트 각각이

한 명에게 의존하는 것이 좋다.

 

충돌은 누구에게도 책임이 없다.

좀 더 관계가 적은 쪽으로 의존하는 것이 편하다.

 

맵 서비스

: 백그라운드에서 계속 돌면서

자기에게 부딪히는 게 있는지 확인하여

충돌을 감지한다.

 

백그라운드 서비스는 죽을 때까지 멈추지 않는다.

계속해서 체크해줘야 하기 때문이다.

그렇기 때문에 체크를 위한 변수가 필요 없다.

제어할 필요가 없기 때문이다.

 

백그라운드 서비스를 돌리기 위해서

독립적인 스레드를 사용한다.

 

 

맵의 색깔로 충돌을 감지하기 위해

이미지를 넣는데,

이전에 이미지를 추가할 때처럼

이미지를 JLabel로 읽을 필요가 없다.

 

JLabel은 div 박스를 만들어

화면에 붙이기 위해 쓰는 것이기 때문에

버퍼로 이미지를 읽어 들인다.

 

이미지를 읽어서 이미지에 대한

분석을 해 계산하고 싶다면 버퍼를 사용한다.

 

이미지의 크기를 모르기 때문에 버퍼로 읽는 것이다.

 

버퍼로 읽으면 raw 하게 읽을 수 있다.

날 것 그대로 읽는다는 말이다.

 

그래야 색깔 계산이 가능하다.

 

색깔 계산은 RGB로 하기 때문에

이미지도 빨강, 초록, 파랑으로 만들어준다.

 

	// 컴포지션을 위한 기술
	// => 의존성 주입(생성자를 통해서 주입) = DI(Dependency Injection)
	public BackgroundMapService(Player player) {
		this.player = player;
		try {
			// raw하게 읽음 : 날 것 그대로 읽는 것
			image = ImageIO.read(new File("image/test.png"));
			// System.out.println(image);

			// System.out.println(image.getRGB(10, 10)); // 빨간색(255, 0, 0)

			// 색상 계산(while)
			while (true) {
				Color color = new Color(image.getRGB(player.getX() + 50, player.getY()));
				System.out.println("빨 : " + color.getRed());
				System.out.println("파 : " + color.getBlue());
				System.out.println("초 : " + color.getGreen());
				Thread.sleep(10);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

Color color = new Color(image.getRGB(player.getX(), player.getY()));

player의 크기를 50, 50으로 지정해주었기 때문에

getX( ), getY( )는 아래 사진 네모의 가장 왼쪽 위를 가리킨다.

 

 

package site.metacoding.bubble.ex07;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

/**
 * 
 * @author jaewon 목적 : 프로덕션(실제) 맵 테스트
 *
 */

public class BubbleFrame extends JFrame {

	private JLabel backgroundMap;
	private Player player; // JLabel

	public BubbleFrame() {
		initObject();
		initSetting();
		initListener();
		initService(); // 리스너 바로뒤에 써주면 좋음
		setVisible(true); // 내부에 paintComponent() 호출 코드가 있다.

		// 테스트
		// new BackgroundMapService(player);
	}

	// new 하는 것
	private void initObject() {
		backgroundMap = new JLabel(new ImageIcon("image/backgroundMap.png"));
		setContentPane(backgroundMap); // 백그라운드 화면 설정

		player = new Player(); // 플레이어 추가
		add(player); // Frame에 추가
	}

	private void initService() {
		new Thread(new BackgroundMapService(player)).start();
	}

	// 각종 모든 세팅
	private void initSetting() {
		setSize(1000, 640);
		getContentPane().setLayout(null); // null을 줘야 absolute 레이아웃이 됨
		setLocationRelativeTo(null); // 가운데 배치
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // x버튼 클릭시 JVM 같이 종료하기
	}

	// 키보드
	private void initListener() {
		addKeyListener(new KeyListener() {

			@Override
			public void keyTyped(KeyEvent e) {

			}

			@Override
			public void keyReleased(KeyEvent e) { // 누른걸 떼면
				// System.out.println("키보드 릴리즈");

				if (e.getKeyCode() == KeyEvent.VK_RIGHT) { // int니까 ==으로 비교
					// isRight를 false
					player.setRight(false);
				} else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
					// isLeft를 false
					player.setLeft(false);
				}
			}

			// 조이스틱
			@Override
			public void keyPressed(KeyEvent e) { // 누르면
				// 왼 37, 오 39, 위 38, 아래 40
				// System.out.println("키보드 프레스 : " + e.getKeyCode());

				// 그러기 위해선 누르고 있는지 누르고 있지 않은지 확인하는 상태 변수가 필요하다.
				if (e.getKeyCode() == KeyEvent.VK_RIGHT) { // int니까 ==으로 비교
					// 키보드를 누르고 있는 동안 right() 메서드는 한번만 실행하고 싶다.
					if (!player.isRight() && player.isRightWallCrash() == false) { // == false 와 같음
						player.right(); // 이동은 플레이어의 책임
					}
				} else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
					if (!player.isLeft() && player.isLeftWallCrash() == false) {
						player.left();
					}
				} else if (e.getKeyCode() == KeyEvent.VK_UP) { // if 자체를 막으면 이벤트 루프 등록을 안함
					// System.out.println("체크");
					if (!player.isUp() && !player.isDown()) {
						player.up(); // 메서드 내부에서 if 분기 처리는 이벤트 루프에 등록은 되는데 실행이 안되는 것
					}
				}
			}
		});
	}

	public static void main(String[] args) {
		new BubbleFrame();
	}
}
package site.metacoding.bubble.ex07;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;

// 백그라운드 서비스
// 서비스는 죽을때까지 안멈춤
// 계속 체크해야하니까!
// 체크 변수 필요없음, 제어할 필요 없기 때문
// 독립적인 스레드 필요
public class BackgroundMapService implements Runnable {

	// 의존해야하는 애들을 컴포지션(결합) 해야함 : Player
	private Player player;
	// 문자열이 아닌 이미지로 읽어버림
	// 사진을 분석하기 위해서는 버퍼로 읽어서 분석해야함
	private BufferedImage image;

	// 컴포지션을 위한 기술
	// => 의존성 주입(생성자를 통해서 주입) = DI(Dependency Injection)
	public BackgroundMapService(Player player) {
		this.player = player;
		try {
			// raw하게 읽음 : 날 것 그대로 읽는 것
			image = ImageIO.read(new File("image/backgroundMapService.png"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		// 색상 계산(while)
		while (true) {
			try {
				/*
				 * Color color = new Color(image.getRGB(player.getX() + 50, player.getY()));
				 * System.out.println("빨 : " + color.getRed()); System.out.println("초 : " +
				 * color.getGreen()); System.out.println("파 : " + color.getBlue());
				 * System.out.println("==============");
				 */
				Color leftColor = new Color(image.getRGB(player.getX() - 10, player.getY() + 25));
				Color rightColor = new Color(image.getRGB(player.getX() + 50 + 15, player.getY() + 25));

				// System.out.println("leftColor : " + leftColor);
				// System.out.println("rightColor : " + rightColor);

				// System.out.println(image.getRGB(player.getX(), player.getY() + 50 + 5));
				// -2일때만 바닥에 아무것도 없는 상태!(-1, -1 양쪽 끝)
				int bottomColor = image.getRGB(player.getX() + 10, player.getY() + 50 + 5) // -1 흰색
						+ image.getRGB(player.getX() + 50 - 10, player.getY() + 50 + 5); // -1

				if (bottomColor != -2) { // 바텀 충돌 상태! -> down이 멈춰야함
					player.setDown(false);
				} else if (bottomColor == -2) {
					if (!player.isDown() && !player.isUp()) {
						player.down();
					}
				}

				// 상태값이 있어야 행위를 제어할 수 있음
				if (leftColor.getRed() == 255 && leftColor.getGreen() == 0 && leftColor.getBlue() == 0) {
					// System.out.println("왼쪽 벽에 충돌함");
					player.setLeftWallCrash(true);
					player.setLeft(false);

				} else if (rightColor.getRed() == 255 && rightColor.getGreen() == 0 && rightColor.getBlue() == 0) {
					// System.out.println("오른쪽 벽에 충돌함");
					player.setRightWallCrash(true);
					player.setRight(false);
					// else if(bottomColor.getRed() == 255 && bottomColor.getGreen() == 0 &&
					// bottomColor.getBlue() == 0) {
				} else {
					player.setRightWallCrash(false);
					player.setLeftWallCrash(false);
				}

				Thread.sleep(10); // 충돌감지를 미세하게 하는 조절법
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
package site.metacoding.bubble.ex07;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

/**
 * 
 * @author jaewon 플레이어는 좌우이동이 가능하다. 점프가 가능하다. 방울 발사를 한다.(나중에 생각하자) x, y 좌표 필요
 *         x좌표로 좌우 이동 y좌표로 점프
 *
 */

public class Player extends JLabel {

	private int x;
	private int y;

	private ImageIcon playerR; // 오른쪽을 보고있는 pR
	private ImageIcon playerL; // 왼쪽을 보고있는 pL

	private boolean isRight; // 보통 boolean 변수 이름에는 is가 붙음
	private boolean isLeft;
	// private boolean isJump; // 점프 상태(up, down)
	private boolean up;
	private boolean down;

	private boolean leftWallCrash; // 왼쪽 벽에 부딪힌 상태
	private boolean rightWallCrash; // 오른쪽 벽에 부딪힌 상태

	private static final int JUMPSPEED = 2;
	private static final int SPEED = 4;

	public boolean isUp() {
		return up;
	}

	public void setUp(boolean up) {
		this.up = up;
	}

	public boolean isDown() {
		return down;
	}

	public void setDown(boolean down) {
		this.down = down;
	}

	public boolean isLeftWallCrash() {
		return leftWallCrash;
	}

	public void setLeftWallCrash(boolean leftWallCrash) {
		this.leftWallCrash = leftWallCrash;
	}

	public boolean isRightWallCrash() {
		return rightWallCrash;
	}

	public void setRightWallCrash(boolean rightWallCrash) {
		this.rightWallCrash = rightWallCrash;
	}

	// 자바의 특징 : is가 붙은 boolean 변수는 getIsRight가 아닌 isRight라고 이름이 붙음
	public boolean isRight() {
		return isRight;
	}

	// 자바의 특징 : is가 붙은 boolean 변수는 setIsRight가 아닌 setRight라고 이름이 붙음
	public void setRight(boolean isRight) {
		this.isRight = isRight;
	}

	public boolean isLeft() {
		return isLeft;
	}

	public void setLeft(boolean isLeft) {
		this.isLeft = isLeft;
	}

	public Player() {
		initObject();
		initSetting();
	}

	private void initObject() {
		playerR = new ImageIcon("image/playerR.png");
		playerL = new ImageIcon("image/playerL.png");
	}

	// 생성자에서 초기화, 생성자에서 호출되어있으니까 얘도 생성자 !
	private void initSetting() {
		x = 70;
		y = 535;
		setIcon(playerR);
		setSize(50, 50);
		setLocation(x, y); // paintComponent 호출해줌, 부분 새로고침

		isRight = false;
		isLeft = false;
		up = false;
		down = false;

		leftWallCrash = false;
		rightWallCrash = false;
	}

	// 하나의 단일 책임을 가진 메서드, 메서드 모듈
	public void right() {
		isRight = true;
		// leftWallCrash = false;

		System.out.println("오른쪽 이동");

		setIcon(playerR);

		new Thread(() -> {
			while (isRight) {
				x = x + SPEED;
				setLocation(x, y);
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();

	}

	// 외부에서 호출 가능하게
	public void left() {
		isLeft = true;
		// rightWallCrash = false;

		System.out.println("왼쪽 이동");

		setIcon(playerL);

		new Thread(() -> {
			while (isLeft) {
				x = x - SPEED;
				setLocation(x, y);
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();

	}

	// 키보드 윗방향키
	// up() down() 메서드 나누기!
	public void up() {
		up = true;

		System.out.println("위로 업");
		// 점프는 for문을 돌려야함
		// up 이때는 sleep(5), down 이때는 sleep(3)
		new Thread(() -> {
			// up
			for (int i = 0; i < 130 / JUMPSPEED; i++) { // JUMPSPEED에 따라 높이가 달라지면 안됨!
				y = y - JUMPSPEED;
				setLocation(x, y);
				// up = true; // 더블점프 안됨 ! 키보드 입력을 한번만 받아야해

				try {
					Thread.sleep(5);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			up = false; // 무조건 스레드 안에 !!
			down();
		}).start();

	}

	// 점프하지 않았을 때도 다운해야하기 때문에 분리시킴
	// 낙하
	public void down() {
		down = true;

		System.out.println("아래로 다운");
		// 점프는 for문을 돌려야함
		// up 이때는 sleep(5), down 이때는 sleep(3)
		new Thread(() -> {
			// down
			// while로 바닥에 떨어지기
			while (down) {
				y = y + JUMPSPEED;
				setLocation(x, y);

				try {
					Thread.sleep(3);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			// down = false; //는 down()메서드에서 제어 불가능
		}).start();

	}
}

 

 

 

[출처]

 

https://cafe.naver.com/metacoding

 

메타코딩 : 네이버 카페

코린이들의 궁금증

cafe.naver.com

 

메타 코딩 유튜브

https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9

 

메타코딩

문의사항 : getinthere@naver.com 인스타그램 : https://www.instagram.com/meta4pm 깃헙 : https://github.com/codingspecialist 유료강좌 : https://www.easyupclass.com

www.youtube.com

 

반응형