최원종의 개발 블로그

Bubble_5(물방울 동작 처리) 본문

Java/SWING

Bubble_5(물방울 동작 처리)

chl6698 2026. 3. 19. 17:33

파일 구성

bubble/test05/
  ├── Moveable.java                ← bubble-1 과 동일
  ├── PlayerWay.java               ← 신규 (enum - 방향 상태)
  ├── Player.java                  ← playerWay 필드 추가, 람다식으로 Thread 변경
  ├── Bubble.java                  ← Moveable 구현, 이동 로직 추가
  ├── BackgroundPlayerService.java ← bubble-3 과 동일
  └── BubbleFrame.java             ← bubble-4 와 동일

 

 

https://youtu.be/FFShOznHalc

package test05;

/**
 * [enum] 플레이어의 방향 상태
 *
 * enum 을 사용하는 이유:
 * - boolean 두 개(isLeft, isRight)로 방향을 관리하면
 * 둘 다 true 가 되는 잘못된 상태가 생길 수 있음
 * enum은 정해진 값 중 하나만 가질 수 있어서 안전함
 *
 * 사용방법:
 * playerWay = PlayerWay.LEFT //값 설정
 * if(playerWay == PlayerWay.LEFT) {} // 값 비교
 *
 * 왜 사용할까?
 * 나의 프로젝트나 논리 안에서 데이터의 범위를 지정하고 싶을 때 사용한다
 */
public enum PlayerWay {
    LEFT,RIGHT

}

 

-인터페이스

package test05;

public interface Moveable {
    void left();
    void right();
    void up();
    default void down() {
    }
    // Adapter 클래스가 너무 많이 생겨서 default 문법을
    // 인터페이스에서 사용할 수 있도록 만들어 줬다.
    // 즉 default 키워드를 사용하면 인터페이스 안에서 일반 메서드를 선언할 수 있다.
}

 

-Player

package test05;


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


    }

    @Override
    public void down() {
        down = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < JUMP_HEIGHT / JUMP_SPEED; i++) {
                    y = y + JUMP_SPEED;
                    setLocation(x, y);
                    try {
                        Thread.sleep(3); // 5ms 간격 (낙하 보다 느리게 설정 낙하 3ms )
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                down = false;
            }
        }).start();

    }
}

 

-Bubble

package test05;

import javax.swing.*;

public class Bubble extends JLabel implements Moveable {

    private int x;
    private int y;

    private ImageIcon bubbleIcon;
    private Player player;

    //버블 이동 상태 플래그
    private static final int HORIZONTAL_DISTANCE = 400; // 수평 이동 거리
    private static final int BUBBLE_SPEED_MS = 1;       // 이동 간격 (ms)
    private static final int SCREEN_TOP = 0;            // 화면 상단 경계

    private boolean leftMoving = false;
    private boolean rightMoving = false;
    private boolean upMoving = false;

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

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

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

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

    // DI
    public Bubble(Player player) {
        this.player = player;
        initData();
        setInitLayout();
        bubbleStartThread(); // 생성과 동시에 플레이어 방향 판단해서 바로 이동 시작
    }

    // 물방울 이동 Thread 시작
    public void bubbleStartThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if(player.getPlayerWay() == PlayerWay.LEFT) {
                    left(); // 왼쪽으로 400px 이동 --> 완료 후 --> up() 호출
                } else {
                    right(); // 오른쪽으로 400px 이동 --> 완료 후 --> up() 호출
                }
            }
        }).start();
    }

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

    private void setInitLayout() {
        x = player.getX();
        y = player.getY();
        setIcon(bubbleIcon);
        setSize(50, 50);
        setLocation(x, y);
        setVisible(true);
    }


    @Override
    public void left() {
        leftMoving = true;
        for (int i = 0; i < HORIZONTAL_DISTANCE; i++) {
            x--;
            setLocation(x, y);
            try {
                Thread.sleep(BUBBLE_SPEED_MS);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        leftMoving = false;
        up(); // 수평이동 완료 후 -> 상승 시작
    }

    @Override
    public void right() {
        rightMoving = true;
        for (int i = 0; i < HORIZONTAL_DISTANCE; i++) {
            x++;
            setLocation(x, y);
            try {
                Thread.sleep(BUBBLE_SPEED_MS);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        rightMoving = false;
        up(); // 수평 이동 완료 -> 상승 시작
    }

    @Override
    public void up() {
        upMoving = true;
        while (y > SCREEN_TOP) {
            y--;
            setLocation(x, y);
            try {
                Thread.sleep(BUBBLE_SPEED_MS);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        upMoving = false;
    }

    @Override
    public void down() {

    }
}

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

Bubble_6(바닥, 층 감지 기능)  (0) 2026.03.20
Bubble_4(물방울 생성)  (0) 2026.03.19
Bubble_3(왼쪽 오른쪽 벽 감지)  (0) 2026.03.19
Bubble_2(이동, 점프)  (0) 2026.03.19
Bubble_1(기본 화면완성)  (0) 2026.03.18