최원종의 개발 블로그

Bubble_6(바닥, 층 감지 기능) 본문

Java/SWING

Bubble_6(바닥, 층 감지 기능)

chl6698 2026. 3. 20. 15:02

 

https://youtu.be/3tHGK-ybm1A

bubble/test05/
  ├── Moveable.java                ← bubble-1 과 동일
  ├── PlayerWay.java               ← bubble-5와 동일
  ├── Player.java                  ← playerWay 필드 추가, 람다식으로 Thread 변경
  ├── Bubble.java                  ← bubble-5와 동일
  ├── BackgroundPlayerService.java ← 발판 색상 감지
  └── BubbleFrame.java             ← bubble-4와 동일

배경에 있는 색상을 이용해 벽과 바닥 층 색상을 감지해 플레이어의 위치를 고정시킴.

 


-코드

package test06;


import javax.swing.*;

public class Player extends JLabel implements Moveable {

    // 플레이어의 현재 좌표 상태 값
    private int x;
    private int y;

    // 좌우 방향 이미지(방향키에 따라서 이미지 전환)
    private ImageIcon playerR;
    private ImageIcon playerL;

    // 속도 상수
    private final int SPEED = 4;           // 좌우 이동 속도 (픽셀)
    private final int JUMP_SPEED = 2;      // 점프/낙하 속도
    private final int JUMP_HEIGHT = 130;   // 점프 최대 높이

    // 이동 상태 플래그
    // true = 해당 방향으로 이동 중 (while 루프 조건)
    // false = 멈춤 (while 루프 탈출 -> Thread 종료)
    private boolean left = false;
    private boolean right = false;
    private boolean up = false;
    private boolean down = false;

    // 벽 충돌 상태 플래그
    private boolean leftWallCrash;
    private boolean rightWallCrash;

    /**
     * 플레이어 현재 방향(enum 타입)
     * - left()  -> playerWay = PlayerWay.LEFT 변경
     * - right() -> playerWay = PlayerWay.Right 변경
     */
    private PlayerWay playerWay = PlayerWay.RIGHT; // 게임시작시 오른쪽 바람 봄

    /// getter
    @Override
    public int getX() {
        return x;
    }

    @Override
    public int getY() {
        return y;
    }

    public boolean isLeft() {
        return left;
    }

    public boolean isRight() {
        return right;
    }

    public boolean isUp() {
        return up;
    }

    public boolean isDown() {
        return down;
    }

    public boolean isLeftWallCrash() {
        return leftWallCrash;
    }

    public boolean isRightWallCrash() {
        return rightWallCrash;
    }

    public PlayerWay getPlayerWay() {
        return playerWay;
    }

    /// setter
    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

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

    public void setRight(boolean right) {
        this.right = right;
    }

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

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

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

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

    public void setPlayerWay(PlayerWay playerWay) {
        this.playerWay = playerWay;
    }

    public Player() {
        initData();
        setInitLayout();
    }

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

    private void setInitLayout() {
        // 캐릭터 초기 위치 설정
        x = 55;
        y = 535;
        setSize(50, 50);
        setIcon(playerR); // 초기 방향 설정
        setLocation(x, y);
    }


    @Override
    public void left() {
        if (left) {
            return;  // 이미 이동 중이면 중복 Thread 생성 방지
        }
        playerWay = PlayerWay.LEFT;
        left = true;
        setIcon(playerL);

        new Thread(new Runnable() {
            @Override
            public void run() {
                // left 가 true 인 동안 계속 이동
                // keyReleased 에서 setLeft(false) 가 호출이 되면 while 탈출
                while (left) {
                    x = x - SPEED;
                    setLocation(x, y);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();
    }

    @Override
    public void right() {
        if (right) {
            return;
        }
        playerWay = PlayerWay.RIGHT;
        right = true;
        setIcon(playerR);

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (right) {
                    x = x + SPEED;
                    setLocation(x, y);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();
    }

    @Override
    public void up() {
        // 점프 기능을 어떻게 구현할까?
        if(up) {
            return;
        }
        up = true;

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 130 / 2 --> 65 반복 65 픽셀 업
                for (int i = 0; i < JUMP_HEIGHT / JUMP_SPEED; i++) {
                    y = y - JUMP_SPEED;
                    setLocation(x, y);
                    try {
                        Thread.sleep(5); // 5ms 간격 (낙하 보다 느리게 설정 낙하 3ms )
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                up = false; // 최고점 도달 하고 -> 점프 상태 해제
                down(); // 자동 낙하 시작
            }
        }).start();


    }

    /**
     *  낙하 변경(중력)
     *  for -> while(down) 으로 변경
     *  while(true) -->  (down)
     *    BackgroundPlayerService 가 바닥 감시
     *    setDown(false) 호출 -->  down 상태 값을 false 변경 한다면 while(down) 이 종료 -> 낙하 종료
     */
    @Override
    public void down() {
        down = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (down) {
                    y += JUMP_SPEED;
                    setLocation(x, y);
                    try {
                        Thread.sleep(3);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();

    }
}

 

 

-BackgroundPlayerService

package test06;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * [클래스 역할] - 플레이어 충돌 감지 서비스(백그라운드에서 계속 돌아감) - 메인 쓰레드는 너무 바쁨
 */
public class BackgroundPlayerService implements Runnable {

    // ImageIcon / Image
    // 화면에 그려서 보여주기 위한 이미지
    // --> 픽셀 데이터에 직접 접근이 불가능 함
    // BufferedImage
    // --> 메모리에 픽셀 배열로 저장된 이미지
    // --> getRGB(x, y)로 특정 좌표의 색상값을 직접 읽을 수 있음
    private BufferedImage image;
    private Player player;

    // 의존성 주입 DI(Dependency Injection)
    // player 를 생성자를 통해서 외부에서 주입 받음
    // 즉, 이 서비스가 직접 플레이어를 생성하지 않고 외부에서 주입 받아 사용 됨.
    public BackgroundPlayerService(Player player) {
        this.player = player;
        try {
            image = ImageIO.read(new File("img/backgroundMapService.png"));
        } catch (IOException e) {
            throw new RuntimeException(" 충돌 감지 이미지를 찾을 수 없습니다 " + e);
        }
    }


    @Override
    public void run() {

        // 게임 끝날 때 까지 계속 실행 되어야 함.
        while (true) {

            Color leftColor = new Color(image.getRGB(player.getX(), player.getY() + 25));
            Color rightColor = new Color(image.getRGB(player.getX() + 60, player.getY() + 25));

            // 바닥/층 감지 좌표 (플레이어 발 아래 두점)
            int bottomLeft = image.getRGB(player.getX() + 20, player.getY() + 55);
            int bottomRight = image.getRGB(player.getX() + 50, player.getY() + 55);
            System.out.println("bottomLeft : " + bottomLeft);
            System.out.println("bottomRight : " + bottomRight);

            if (bottomLeft + bottomRight == -2) {
                // 계속 낙하 시킬 예정
                // 발 아래가 허공 -> 아직 점프/낙하 중이 아닐 때만 낙하 시작해야 함
                // player.isUp() == false -> 점프 중이 아님
                // player.isDown() == false -> 낙하 중이 아님
                if (player.isUp() == false && player.isDown() == false) {
                    player.down();
                }
            } else {
                // 발 아래가 바닥/ 층 -> 낙하 즉시 중단
                player.setDown(false); // while(down) --> false -> while 종료 , Thread 종료
            }

            //  정수 값 - 1 면 흰색
            //  정수 값 -65536 면 빨간색

            // 왼쪽 벽 충돌 감지 판단
            if (isRed(leftColor)) {
                // 충돌 상태 변수 ON
                player.setLeftWallCrash(true);
                player.setLeft(false); // while(false) 종료 --> 이동 멈춤 (Thread 종료)
            } else {
                // 벽에서 벗어나면 즉시 해제 --> 다시 이동 가능하게 설정
                player.setLeftWallCrash(false);
            }

            // 오른쪽 벽 충돌 감지 판단
            if (isRed(rightColor)) {
                // 충돌 상태 변수 ON
                player.setRightWallCrash(true); // 충돌 상태 ON
                player.setRight(false);         // while(right) 종료 --> 이동 멈춤 Thread 종료
            } else {
                player.setRightWallCrash(false); // 벽에서 벗어나면 즉시 해제
            }

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    // 255, 0 , 0 -> 빨간색
    private boolean isRed(Color color) {
        return color.getRed() == 255
                && color.getGreen() == 0
                && color.getBlue() == 0;
    }

}

 

 

'Java > SWING' 카테고리의 다른 글

Bubble_5(물방울 동작 처리)  (0) 2026.03.19
Bubble_4(물방울 생성)  (0) 2026.03.19
Bubble_3(왼쪽 오른쪽 벽 감지)  (0) 2026.03.19
Bubble_2(이동, 점프)  (0) 2026.03.19
Bubble_1(기본 화면완성)  (0) 2026.03.18