[JAVA] 자바 2D 게임 만들기 단상 (JFrame drawImage 속도 빠르게)
퓨어 자바로 게임을 짤 때마다 속도가 너무 느려서 좌절하곤 했었다.
원어로 된 관련 서적을 사서 읽어보기도 했지만, 핵심적인 부분을 찾아내지 못했고, 결국 포기했었다.
책에는 분명 퓨어 자바도 빠르게 그림을 찍어낼 수 있다고 쓰여있었는데 말이다.
각설하고, 이제 방법을 알아냈다.
알다시피 2D 게임을 만드는 가장 일반적인 방법은, 루프를 걸어놓고 그림 1장만 계속 찍어내는 것이다.
만약 800 x 600 해상도 게임을 만든다면, 가장 속도가 빠른 방법은 800 x 600 그림 1장을 반복적으로 찍어내는 것이다.
왜? 캐릭터, 타일 등을 곧바로 화면에 찍으면 느리다.
그러므로 800 x 600 짜리 이미지 객체를 만들어놓고, 타일이나 캐릭터 등을 그 이미지 객체에 찍는다.
그리고 완성된 이미지 객체를 화면에 찍는 것이다.
이렇게 해야 화면에 찍는 횟수를 최소화할 수 있다.
코드를 간략히 만들면, 아래와 같은 방식이다.
|
// 게임루프 while (true) { jFrame.getGraphics().drawImage(mainImg, 0, 0, null); } |
여기서 mainImg는 BufferedImage 객체다.
나는 여태까지 BufferedImage 객체이면 다 똑같은 객체인줄 알았다.
그런데 BufferedImage 객체는, 타입값에 따라 내부적으로 다르게 구성되는 특징이 있었고, 이게 키 포인트였다.
여태까지 나는 BufferedImage 의 픽셀을 수정하기 위해서 setRGB 메서드를 썼다.
가로 800, 세로 600 의 이중포문(for)을 돌면서 setRGB 메서드로 이미지 객체를 변경했는데,
이 setRGB 메서드가 엄청나게 느린 것이었다.
setRGB 메서드는 무한루프 안에서 사용하면 안되는 메서드였다.
하지만, 여태까지는 다른 대안을 몰라서 사용할 수 밖에 없었다.
|
for (int x=0; x<800; x++) { for (int y=0; y<600; y++) { // setRGB 메서드는 아주 느리다. 사용금지. mainImg.setRGB(x, y, rgbValue); } } |
그렇다면 다른 방법은 무엇일까?
우선 이미지를 만들 때 아래와 같이 만든다.
|
BufferedImage mainImg = new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB); |
그러면 아래와 같이 int 배열을 뽑아낼 수가 있다.
|
int[] pixels = ((DataBufferInt) mainImg.getRaster().getDataBuffer()).getData(); |
RGB값을 아래와 같이 변경할 수 있다.
단순히 배열의 int 값을 바꾸면 된다.
|
// pixels.length == 480000 이다. 800 x 600 과 같다. int len = pixels.length; for (int i = 0; i < len; i++){ |
이것이 중요한 이유는, setRGB 메서드보다 훨씬 빠른 속도로 RGB 값을 교체할 수 있기 때문이다.
(훨씬 빠르다는 표현이 부족할 정도로, 비교할 수 없을 정도로 큰 속도 차이가 난다. 이 코드 1줄로 인해 자바 2D 게임을 만들 수 있느냐 아니냐가 결정난다.)
마지막으로, int 값을 수정하기 쉽게 하려면, 게임 내에서 사용해야 하는 이미지들을 미리 읽어와야 한다.
게임루프에 들어가기 전 그림을 읽어와야 하는데, 이 때 BufferedImage.TYPE_INT_RGB 로 이미지를 읽어와야 한다.
그림파일을 BufferedImage 객체로 읽어오는 코드는 원래는 다음과 같이 쓴다.
|
public static BufferedImage loadImage(String filePath) throws Exception { BufferedImage image = null; File imgFile = new File(filePath); image = ImageIO.read(imgFile); |
이 코드의 문제는, BufferedImage 객체를 읽어온 후 ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); 코드를 적용하면 오류가 발생한다는 점이다.
클래스 캐스트 익셉션이 발생한다. (java.lang.ClassCastException: java.awt.image.DataBufferByte cannot be cast to java.awt.image.DataBufferInt)
그러므로 미리 그림을 읽어오는 시점에 TYPE_INT_RGB 타입으로 저장하도록 아래처럼 코딩을 하자.
|
public static BufferedImage loadImageToRGB(String filePath) throws Exception { BufferedImage image = null; File imgFile = new File(filePath); image = ImageIO.read(imgFile); |
이렇게 모든 그림파일들을 TYPE_INT_RGB 타입의 BufferedImage 객체로 만들어놓고, 화면에 찍히는 메인 이미지의 int 배열의 값을 교체하는 방식이라면 간단한 2D 게임을 돌리는 데에는 충분한 속도가 나올 것이다.
전체 코드는 다음 포스트를 참고하자.