본문 바로가기
JAVA

자바 비행기 게임 만들어보기 #2

by binghe819 2020. 5. 1.

저번 포스터에서 비행기를 출력하고 움직이는 것 까지 구현을 하였다.

 

하지만 디버깅을 해보니 스레드로 인해 repaint()가 연속적으로 실행되는 부분이 많았다.

 

그래서 이를 해결하고자 paint()관련 메서드를 정리하고 해당 코드를 조금 수정하였다.

 

그리고 출력할 때 깜빡임을 없애고 부드럽게 하기 위해서 더블버퍼링을 구현하려고 한다.

 


순서

  • paint, update, repaint
  • 더블 버퍼링

 

paint(), update(), repaint()

구글링을 통해 여러 블로그를 보고 그 개념에 따라서 구현을 했는데, 자꾸 생각한대로 결과가 안 나왔다.
그래서, 공식 문서를 확인하고나서 몇몇 블로그들과 내가 구현하고자하는 것이 다른 것을 알 수 있었다.
무작정 다른 블로그를 따라 공부하는 것보다, 공식 문서를 먼저 봐야겠다고 생각이 든 하나의 삽질이였다.

자바 11 API - Component()

공식 문서를 봤는데도, 아직 이해가 안되는 부분은 많지만, 우선 내가 테스트 해본 방법대로 일단 구현해 볼 생각이다. 

(물론 틀린 부분이 있을 것이다. 추후에 관련 개발을 하게 된다면 더 공부할 예정이다.)

 

paint()

  • Paints this component. ( 컴포넌트를 그린다. )
  • This method is called when the contents of the component should be painted; such as when the component is first being shown or is damaged and in need of repair. ( 프레임이 처음 켜졌을때와 데미지를 입었거나, 수리가 필요할때 paint()가 호출된다. )

Component클래스의 paint메서드

프레임이 처음 켜졌을때 paint()가 호출되고, paint()는 사용자가 오버라이딩하여 어떤 것을 그릴지 구현해줘야 한다.

 

update()

  • If this component is not a lightweight component, the AWT calls the update method in response to a call to repaint. You can assume that the background is not cleared. ( 경량 컴포넌트가 아니라면 repaint()의 응답으로 update() 호출한다. )
  • The update method of Component calls this component's paint method to redraw this component. (update()를 호출하면 paint()를 호출합니다. )

Component클래스의 update메서드

위와 같이, 만약 경량 컴포넌트(?)가 아니라면 repaint()를 부르면 update()가 호출된다. 또한 update()는 paint()를 호출한다.

 

즉, repaint() -> update() -> paint() 

 

repaint()

  • Repaints this component. ( 컴포넌트를 다시 그린다. )
  • If this component is a lightweight component, this method causes a call to this component's paint method as soon as possible. Otherwise, this method causes a call to this component's update method as soon as possible. ( 경량 컴포넌트라면 바로 paint()를 호출한다. 아니라면, update()를 호출한다. )
여기서 멘붕이 한번 왔다.
update()에서는 repaint()가 update를 부른다고 했는데, 갑자기 paint()를 부를수도 있다고??....
무슨 소리야... 이러지 마... 했다가 다시 자세히 보니 답을 알 수 있었다.

Swing의 그래픽 Component는 lightweight와 heavyweight에 따라 다르게 동작한다는 것을 알았다.

lightweight vs heavyweight component

The difference between the two is that the Lightweight components are rendered (drawn) using purely Java code, such as drawLine and drawImage, whereas Heavyweight components use the native operating system to render the components.

( 나는 한 화면에서만 비행기를 계속해서 그리는 것이기 때문에 lightweight인 것이다. )

출처 - 위키

 

  • lightweight : repaint() -> paint() , update() -> paint()
  • heavyweight : repaint() - > update() -> paint()

이를 이용해, 스레드를 따로 사용해서 repaint()하지 않고 비행기의 움직임을 구현하였다.

 

결과적으로는 오버라이딩을 통해 paint() -> update() -> repaint() -> paint()의 연속으로 수정하였다.

 

더블 버퍼링

컴퓨터는 그래픽을 표시하기 위해 일정 시간마다 화면을 뿌려준다. 

 

하지만 이게 화면을 지속적으로 갱신하다보면, 깜빡이는 것을 볼 수 있다. 이를 해결한 방법이 더블 버퍼링이다.

 

자세한 내용은 Double Buffering - 더블 버퍼링 기법 에 잘 정리되어 있다.

 

더블 버퍼링을 통해 수정한 그래픽 그리는 부분의 코드는 아래와 같다.

 

더블 버퍼링을 통한 그래픽 그리기

더블 버퍼링을 적용했더니, 깜빡임 없이 매우 부드럽게 비행기가 움직인다. ( 성공! )

 

전체 코드

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Game_View extends JFrame{

    // 이미지 파일 불러오는 툴킷.
    Toolkit imageTool = Toolkit.getDefaultToolkit();
    Image flight = imageTool.getImage("res/img/F4K.png");

    // 이미지 버퍼
    Image buffImg;
    Graphics buffG;

    // 플레이어 비행기의 위치값.
    int xpos = 100;
    int ypos = 100;

    public Game_View(){
        // 프레임의 대한 설정.
        setTitle("JFrame 테스트"); // 프레임 제목 설정.
        setSize(854,480); // 프레임의 크기 설정.
        setResizable(false); // 프레임의 크기 변경 못하게 설정.
        setVisible(true); // 프레임 보이기;
//        setDefaultCloseOperation(DISPOSE_ON_CLOSE); // 프레임의 x버튼 누르면 프레임스레드 종료.
        setDefaultCloseOperation(EXIT_ON_CLOSE); // 프레임의 x버튼 누르면 프로세스 종료.

        // 키 어댑터 ( 키 처리용 )
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch(e.getKeyCode()) {
                    case KeyEvent.VK_UP:
                        ypos-=5;
                        break;
                    case KeyEvent.VK_DOWN:
                        ypos+=5;
                        break;
                    case KeyEvent.VK_LEFT:
                        xpos-=5;
                        break;
                    case KeyEvent.VK_RIGHT:
                        xpos+=5;
                        break;
                }
            }
        });

    }

    @Override
    public void paint(Graphics g) {
        buffImg = createImage(getWidth(),getHeight()); // 버퍼링용 이미지 ( 도화지 )
        buffG = buffImg.getGraphics(); // 버퍼링용 이미지에 그래픽 객체를 얻어야 그릴 수 있다고 한다. ( 붓? )
        update(g);
    }

    @Override
    public void update(Graphics g) {
        buffG.clearRect(0, 0, 854, 480); // 백지화
        buffG.drawImage(flight,xpos,ypos, this); // 유저 비행기 그리기.
        g.drawImage(buffImg,0,0,this); // 화면g애 버퍼(buffG)에 그려진 이미지(buffImg)옮김. ( 도화지에 이미지를 출력 )
        repaint();
    }
}

public class Main {

    public static void main(String[] args){
        Game_View game_view = new Game_View();
    }
}

 

 

다음 포스터에서는 전체적인 구조를 설계할 예정이다.

 

'JAVA' 카테고리의 다른 글

자바 비행기 게임 만들어보기 #4  (0) 2020.05.04
자바 비행기 게임 만들어보기 #3  (0) 2020.05.02
자바 비행기 게임 만들어보기 #1  (0) 2020.04.30

댓글