JAVA

자바 56강. 버블게임 만들기(6) : 버블 발사하기

JJJAEOoni 2022. 2. 13. 21:49
반응형

각각의 오브젝트가 프로그래밍 관리

벽에 충돌하면 리스너(서비스)가

불리언 값을 push 해준다.

 

행위를 제어할 때는 메서드를 호출하고,

행위를 멈추게 할때는 불리언값을 변경해준다.

 

객체지향 프로그래밍 관점에서는

플레이어 오브젝트가 관계를 가지는게 맞지만,

그렇게 되면 충돌감지하는 스레드가

플레이어마다 하나씩 필요해서

메모리 부하가 심해진다.

 

충돌감지 스레드는

혼자 움직이면 되는 왼,오 스레드와 달리

맵까지 신경쓰면서 봐야하는

스레드이기 때문에 부하가 심하다. 

 

그래서 백그라운드에서 for문을 돌며

지속적으로 충돌을 감지하는 컬렉션을 쓰는게 좋다.

 

다만 객체지향 프로그래밍을 지켜

오브젝트에 만드는것도 틀린것은 아니다.

 

컨텍스트 트리(new하는 애들)

context는 프로그램의 최상위에 있다.(main)

 

윈도우는 두 프로세스(프로그램이 메모리에 뜨면)를

관리하기 위해 아이디(프로세스 아이디)를 붙인다.

 

프로세스 아이디로 결국

context에 접근하여 context를 관리하는 것이다.

 

이때 OS는 context만 가지고 관리한다.

 

 

 

package site.metacoding.bubble.ex08;

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

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

/**
 * 
 * @author jaewon 목적 : 버블 발사하기 (충돌감지는 안함!) 수평발사
 *
 */

// main을 가진 클래스는 해당 프로그램의 컨텍스트(문맥)를 다 알고 있다.
// 모든게 main에서 시작(new)하니까 뿌리를 다 알고있기 때문
// 따라서 main을 가진 클래스를 컨텍스트라고 부른다.
public class BubbleFrame extends JFrame/* implements BubbleContext */ {

	// 사용할 때는 BubbleFrame으로 다운캐스팅하여 사용해야함
	// private BubbleContext context = this; // BubbleFrame의 heap 주소
	// player가 new될 때 넘겨주면 됨 그래야 context에 접근해 버블 add 가능
	// 클래스가 context가 알게하기 위해서는 컴포지션 해야함
	// 아니면 그냥 main메서드가 알고있는것일 뿐 !
	private BubbleFrame context = this;

	private JLabel backgroundMap;
	private Player player; // JLabel

	public Player getPlayer() {
		return player;
	}

	public void setPlayer(Player player) {
		this.player = player;
	}

	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(context); // 플레이어 추가
		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 분기 처리는 이벤트 루프에 등록은 되는데 실행이 안되는 것
					}
				} else if (e.getKeyCode() == KeyEvent.VK_SPACE) {
					// 버블을 쏘는 것은 Player의 책임
					// Bubble bubble = new Bubble();
					// add(bubble); 여기다 만들면 안됨!! 플레이어의 책임임 !!
					player.attack();
				}
			}
		});
	}

	public static void main(String[] args) {
		new BubbleFrame();
	}

}
package site.metacoding.bubble.ex08;

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

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

public class Player extends JLabel {

	// 컴포지션
	private BubbleFrame context;

	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 int direction; // 0은 왼쪽, 1은 오른쪽, -1 방향 없음

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

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

	public int getDirection() {
		return direction;
	}

	public void setDirection(int direction) {
		this.direction = direction;
	}

	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(BubbleFrame context) {
		this.context = context;
		initObject();
		initSetting();
		direction = -1;
	}

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

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

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

		leftWallCrash = false;
		rightWallCrash = false;
	}

	public void attack() {
		// 1. 버블 new
		// 의존성 주입
		Bubble bubble = new Bubble(context); // JLabel
		// 2. 버블 화면에 붙여야함 이건 JFrame에서 해야하는데?
		// JFrame과 의존해야함!
		context.add(bubble);
		// 3. 버블은 여러개니까 컬렉션에 담아둬야함(지금안함)
		// 4. 수평 이동(플레이어 방향대로)
	}

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

		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;
		direction = 0;

		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();

	}
}
package site.metacoding.bubble.ex08;

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.ex08;

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

public class Bubble extends JLabel {

	private BubbleFrame context;
	private Player player;

	private int x;
	private int y;

	private static final int BUBBLESPEED = 1;

	private ImageIcon bubble, bomb;

	// bubble은 player의 위치에 의존해야함
	// player에만 의존하면 나중에 적군을 만들때 문제가 발생
	// 따라서 context에 의존!
	public Bubble(BubbleFrame context) {
		this.context = context;
		this.player = context.getPlayer();
		initObject();
		initSetting();

		// 방향 체크
		if (player.getDirection() == 0) {
			left();
		} else if (player.getDirection() == 1) {
			right();
		}
	}

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

	private void initSetting() {
		x = player.getX();
		y = player.getY();
		setIcon(bubble);
		setSize(50, 50);
		setLocation(x, y);
	}

	private void left() {
		new Thread(() -> {
			try {
				for (int i = 0; i < 400; i++) {
					x--; // x = x - 1
					setLocation(x, y);
					Thread.sleep(1);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}).start();
	}

	private void right() {
		new Thread(() -> {
			try {
				for (int i = 0; i < 400; i++) {
					x++; // x = x + 1
					setLocation(x, y);
					Thread.sleep(1);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}).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

 

반응형