자바 게임 프로그래밍 강좌 4

하이텔 자바 동호회 김명현 (MadOx@hitel.net)님의 글입니다.
이글은 김명현님의 동의없이 함부로 배포하실 수 없습니다.

안녕하세요? 친소맨입니다. 아 벌써 새벽 3시군요.. 괜히 이상한 사이트에 접속했다가, 컴퓨터가 다운되는 바람에 두번씩이나 편집을 하느라, 이 시간 까지 잠도 못자고.. 흑흑.. 이번에는 비트맵 그래픽을 처리하는 방법을 배우게 됩니다. 게임에서 비트 맵 그래픽은 거의 필수라고 할 수 있죠. 이번 절을 보고 나면, 최소한 애플 릿에 그림한장 정도는 띠우실 수 있을 껍니다. :) * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. 2. 일단 그림을 띠우고 봅시다. 2.1 화면에 비트맵(Bitmap) 그래픽을 출력해 봅시다. 2.1.1 비트맵(Bitmap) 그래픽이란 무엇입니까? 비트맵 그래픽은 우리가 일상적으로 흔히 접할 수 있는 그림 파일들을 말 합니다. AUTOCAD나 3D Studio등으로 만들어진 도면들은 Vector 방식으로 저 장되어 있습니다. 3차원 공간 또는 2차원 공간속에서 정점(vertex)를 정의 하고, 각 정점으로 부터의 선분(segment)을 정의하고, 또 면(face)들을 정 의하고... 이런식으로 구성, 저장되는 그림은 비트맵이라 부르지 않습니다. 비트맵 그래픽이란 화소 단위로 구성된 그림을 뜻하며, 화소는 화면상에 나타날 수 있는 최소한의 단위인 점(Pixel)이라고 부릅니다. 흔히 접할 수 있는 비트맵 그래픽 파일은 MS-Window95의 바탕화면에 쓰이는 BMP파일이나, 인터넷 상에서 홈페이지를 꾸미기 위해 사용되는 JPEG, GIF 등이 있습니다. 각각 독특한 알고리즘으로 데이터를 압축하여 저장하지만, 화면에 보여질때 는 화면이 나타낼 수 있는 최소단위인 점단위로 표현이 됩니다. 2.1.2 우리는 어떤 비트맵 파일을 사용합니까? 아마 인터넷에서 가장 많이 사용되는 그래픽 파일 형식은 GIF형식일 것입 니다. MS-Windows에서는 BMP를 많이 사용하고, X-Windows에서는 XBM을 주로 사용합니다. 하지만 GIF는 어떤 한 기종에 국한되지 않고, 여러 기종에 걸 쳐 골고루 사용되며, Java 가 지원가능한 형태중 투명색을 유일하게 지원하 는 형식이기도 합니다. 투명색은 게임에서 상당히 중요하므로 특별히 큰 배 경을 제외하고는 GIF89a 형식을 사용할 것이며, GIF 의 용량이 너무클 경우 에 한하여 압축률이 좋은 JPEG를 사용할 것입니다. 2.1.3 그림을 화면에 출력해 봅시다. 비트맵 그래픽파일을 읽어 애플릿 영역에 나타내어 보도록 합시다. 예쁘 게 웃고 있는 노란 스마일 그림 어떻습니까? 좋으시다구요? 네, 그럼 화면 에 노란 스마일을 출력해 보도록 노력해 보죠. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class ShowBitmap extends Applet 05: { 06: private Image imgSmile; // 스마일그림을 담을 Image데이터 07: 08: public void init() 09: { 10: resize(200, 200); // 애플릿의 크기를 200x200으로 11: } 12: 13: public void start() 14: { 15: // 스마일 그림을 애플릿으로 가져온다 16: imgSmile = getImage(getCodeBase(), "Smile.GIF"); 17: } 18: 19: public void paint(Graphics g) 20: { 21: // 화면에 스마일을 그린다 22: g.drawImage(imgSmile, 0, 0, this); 23: } 24: } [소스1] ShowBitmap.java 위의 소스는 스마일 비트맵을 읽어 애플릿 영역에 뿌려주는 일을 합니다. 실행 결과는 웹브라우저 상의 애플릿 영역에 징그럽게 웃고 있는 노란 스마 일이 보일것입니다. (자료실에서 소스를 다운받아 실행해 보십시오) 스마일을 출력하기 위해 ShowBitmap 애플릿이 어떠한 행동을 했는지 따라 가 보면, init메소드에서 애플릿의 크기를 200x200으로 조절합니다. 다음으 로 start메소드에서 16번 라인의 getImage메소드에 의해 스마일의 이미지인 "Smile.GIF"를 데이터 멤버 imgSmile 에 저장시킵니다. 마지막으로 화면에 스마일 이미지를 출력해 줍니다. ☞ 새로 등장한 것들 ◇ Applet.getImage(URL url, String name) getImage 메소드는 Applet 클래스의 멤버 함수입니다. 지정된 url로 부터 name의 이름을 가진 이미지를 가져올 것입니다. 이미지가 실제 로 드되는 시점은 getImage메소드가 실행된 시점이 아니라 그 이미지를 최 초로 사용하는 시점입니다. [소스1]에서 본다면 최초로 paint 메소드가 호출되어서, 22번 라인의 drawImage메소드가 실행되면서 로드되는 것입 니다. ◇ Applet.getCodeBase() 이 메소드는 현재 실행중인 애플릿을 로드한 시스템의 URL 을 되돌립 니다. [소스1]을 살펴 보면 이미지가 위치하고 있는 URL을 명시하기 위 해 애플릿의 URL을 읽어올 목적으로 사용되었음을 알 수 있습니다. 이미지를 서버로부터 가져오고, 이것을 화면에 출력하는 것을 알아보았습 니다. 생각보다 무척 간단했을 것입니다. 그런데 아직 우리가 해결해야 할 문제점이 남아있습니다. 이 문제점을 다음단원에서 알아보도록 하겠습니다. 2.2 효과적인 그림 로딩법을 배워봅시다. 2.2.1 비트맵의 실제 로드 타이밍에 의한 문제점 앞 단원에서 getImage 메소드를 설명할 때 잠시 언급한 바 있었던 문제점 입니다. getImage메소드는 실행되는 즉시 비트맵 이미지를 서버로에서 사용 자 시스템으로 전송하지 않는다고 했습니다. 이 때문에 [소스1] 의 22번 라 인에서 실제로 그 이미지를 사용할 때 로드가 되는데, 만약 넷트웍 정체로 인해 그림이 반 정도 전송되고 멈춰있는 상태라고 가정해 봅시다. 이미 pa- int메소드가 호출되어 화면을 갱신하기 위해 drawImage 메소드가 실행이 되 었고, drawImage메소드는 아직 반밖에 전송되지 않은 스마일을 화면에 출력 할 것입니다. 우리는 반쪽짜리 스마일을 보게 되는 것이죠. 이러한 문제는 로드해야할 이미지의 숫자가 많을 수록 더욱더 심각해 집니다. Java는 쓰레 드 기반의 언어이므로 이미지를 서버로부터 가져오는 역할을 프로그램의 주 흐름이 맡지 않고, 각각 따로 쓰레드를 생성시켜 처리합니다. 덕분에 이미 지가 로드되는 동안 다른 일을 할 수 있고, 여러개의 이미지를 한꺼번에 서 버로 부터 전송해 올 수 있지만 네트워크의 속도가 우리가 원하는 만큼 빠 르지않기 때문에 미완성된 이미지들이 화면상을 활개치며 돌아다니게 될 것 입니다. (서서히 이미지가 로드되어 가면서 완성되는 과정을 눈으로 확인할 수 있겠죠) 실제 게임에서는 많은 수의 이미지들이 사용됩니다. 비트맵의 로드 타이 밍문제로 인해 반쪽짜리 플레이어가 반쪽짜리 총알에 맞아 반쪽짜리 화염을 내뿜으며 화면상에서 사라지는 것을 플레이어들은 별로 원하지 않을 것입니 다. 2.2.2 HTTP 컨넥션에 의한 문제점 Java는 서버에서 이미지를 가져오기 위해 HTTP 컨넥션을 이용합니다. HT- TP 프로토콜은 데이터를 가져오기 위해 매번 'Connect - Transfer - Close' 3단계 과정을 거치며, 이 때문에 전송효율이 좋지를 않습니다. 더욱이 많은 수의 이미지를 사용하는 JavaGame 애플릿의 경우 그 정도가 더욱더 심해 수 시로 'Connect - Transfer - Close' 를 하게되고, 게임에서 사용되는 작은 그래픽 조각 하나하나를 모두 별개의 이미지 파일로 저장했을 경우, 그림이 전송되어 오는 시간보다 'Connect/Close'에 더 많은 시간을 허비하게 될 수 도 있습니다. 2.2.3 MediaTracker 사용하기 2.2.1절에서 제시되었던 문제점은 Java의 MediaTracker 라는 도구를 이용 하여 해결 할 수 있습니다. MediaTracker는 일종의 유틸리티 클래스로서, 각종 매체의 현 상태를 추적할 수 있습니다. 여기에서 매체란 Image 데이터 나 AudioClip(음향) 데이터 등을 말합니다. 현재는 AudioClip은 추적할 수 없고, 이미지 데이터에 대한 추적만 가능합니다. ● MediaTracker의 사용예제 2.1.3절에서 다루었던 ShowBitmap 애플릿을 MediaTracker를 사용한 버전 으로 수정한 것이 [소스2]입니다. 먼저 소스를 살펴보십시오. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class ShowBitmap2 extends Applet 05: { 06: private MediaTracker tracker; 07: private Image imgSmile; 08: private boolean isLoaded; 09: 10: public void init() 11: { 12: resize(200, 200); 13: } 14: 15: public void start() 16: { 17: // MediaTracker 객체를 생성한다 18: tracker = new MediaTracker(this); 19: // 스마일 그림을 애플릿으로 가져온다 20: imgSmile = getImage(getCodeBase(), "Smile.GIF"); 21: // 스마일 그림을 MediaTracker에 등록한다 22: tracker.addImage(imgSmile, 0); 23: // 이미지의 로드완료 여부를 나타내는 flag 24: isLoaded = false; 25: 26: // 이미지가 로드될때까지 기다린다 27: try { 28: tracker.waitForID(0); 29: } catch (InterruptedException ie) {} 30: isLoaded = true; 31: } 32: 33: public void paint(Graphics g) 34: { 35: // 이미지 로드에 실패했다면 화면을 빨간색으로 36: if ((tracker.statusAll(true) & 37: MediaTracker.ERRORED) != 0) { 38: g.setColor(Color.red); 39: g.fillRect(0, 0, size().width, size().height); 40: return; 41: } 42: if (isLoaded) 43: // 이미지가 로드 됐다면 화면에 스마일을 그린다 44: g.drawImage(imgSmile, 0, 0, this); 45: else 46: // 로드중이면 메시지 출력 47: g.drawString("Loading Image....", 0, 0); 48: } 49: } [소스2] ShowBitmap2.java ◇ MediaTracker 객체 생성 [소스2]의 18번 라인에서 MediaTracker객체를 생성하고 있습니다. M- ediaTracker 객체의 생성자는 이미지가 그려질 Component객체를 요구하 는데, 현재 실행중인 애플릿의 클라이언트 영역에 그리는 경우가 대부 분이므로, 'this'를 넣어 주면됩니다. ◇ 미디어 등록 생성된 MediaTracker 객체에 추적을 원하는 미디어를 등록해 줘야 합 니다. [소스2]의 22번 라인에서 미디어를 등록하고 있는데, 이때 각각 의 미디어마다 고유의 아이디 번호를 부여하고, 이 아이디 번호를 통해 관리를 합니다. 한 개의 아이디로 여러개의 미디어를 한꺼번에 관리할 수 있으므로 같은 그룹의 이미지들은 동일한 아이디를 부여해 함께 관 리할 수 있습니다. ◇ 등록된 미디어 감시 이번에는 MediaTracker객체에 등록된 미디어를 감시하는 방법입니다. MediaTracker는 이와 관련된 여러 가지 메소드를 제공하는데 앞의 소스 에서 사용된것만 설명하도록 하겠습니다. ① 이미지 로드가 완료될때까지 대기하기 이미지로드가 완료될때까지 프로그램의 수행을 중지시킬 수 있는 방법입니다. MediaTracker의 waitForID 메소드를 이용하여 해당 아 이디의 이미지가 로드완료 될때까지 프로그램을 멈추게 할 수 있습 니다. [소스2]의 27-29번 라인에서 직접 사용되고 있으니 참고하십 시오. ② 이미지의 상태 감시하기 [소스2]의 36번 라인에서 사용된 statusAll 메소드는 현재 Medi- aTracker에 등록된 모든 미디어들에 대한 상태를 알려줍니다. 만약 이미지를 로드하는 중에 에러가 발생했다면 statusAll메소드는 ER- RORED 비트플랙을 1 로 설정하게 되고, 우리는 MediaTracker의 ER- RORED 상수를 논리 AND 연산을 취하여 에러 여부를 검사할 수 있습 니다. ☞ 새로 등장한 것들 ◇ MediaTracker MediaTracker 클래스는 각종 미디어(Image, AudioClip 등)의 상태를 추적, 감시할 수 있도록 하는 기능을 가진 유틸리티 클래스입니다. Me- diaTracker 객체를 생성할 때 추적 대상이 될 미디어가 출력될 Compon- ent객체를 인수로 넘겨줘야 하며, 일반적으로 애플릿 자신을 나타내는 'this' 포인터를 넘겨주게 됩니다. ◇ MediaTracker.addImage(Image img, int id) MediaTracker 객체에 추적을 위한 이미지를 등록하는 메소드 입니다. 각각의 이미지마다 고유의 숫자 ID를 부여할 수 있고, 함께 로드되어야 할 이미지들일 경우 같은 ID를 부여해 한꺼번에 관리할 수도 있습니다. ◇ MediaTracker.waitForID(int id) 주어진 ID의 미디어가 로드될때까지 기다리도록 하는 메소드 입니다. 또 해당 아이디가 부여된 미디어중 로드가 되지않은 것들이 있다면, 이 들을 로드하도록 할 것입니다. waitForID 메소드는 미디어의 로드가 끝 나지 않고 중단된 경우 InterruptedException을 발생시키므로 try - c- atch 문을 이용해 예외를 잡아줘야 합니다. ◇ MediaTracker.waitForAll() 아직 등장하지 않은 메소드이지만 한꺼번에 설명 하도록 하겠습니다. waitForID메소드는 ID별로 관리를 하지만 waitForAll 메소드는 현재 M- ediaTracker 에 등록된 모든 미디어들에 대해 로드가 완료 될때까지 대 기 하게 됩니다. 그외의 사항은 waitForID메소드와 같습니다. ◇ MediaTracker.statusAll(boolean load) 이 메소드는 MediaTracker에 등록된 모든 미디어들에 대한 상태를 되 돌립니다. statusAll 메소드에 의해 넘어오는 미디어들의 상태는 4가지 이며, 그 값들은 아래와 같습니다. MediaTracker.LOADING : 미디어가 로드되는 중이다 MediaTracker.ABORTED : 미디어의 로드를 취소했다 MediaTracker.ERRORED : 미디어를 로드하는 도중 오류가 발생했다 MediaTracker.COMPLETE: 미디어 로드를 완료했다 [표1] MediaTracker가 제공하는 미디어의 상태 값 각각의 상태를 체크하는 방법은 statusAll메소드가 되돌린값과 [표1] 의 값을 논리 AND 한 값을 이용하면 됩니다. [소스2]의 36번 라인에서 미디어 로드에 오류가 발생한것인지를 체크하기 위해 MediaTracker.ER- RORED값과 AND 한 후 값을 비교하고 있음을 알 수 있습니다. statusAll 메소드의 인수로 넘어가는 값은 미디어가 로드되지 않은 경우, 그 값이 true면 미디어를 로드시키고, false면 보류합니다. 일반적으로 true 값 을 사용합니다. ◇ MediaTracker.statusID(int id, boolean load) statusAll 메소드와 하는 일은 거의 같습니다. 다만 MediaTracker에 등록된 미디어 전체의 상태를 되돌리는 것이 아니라, 주어진 ID를 가지 는 미디어들에 대한 상태만을 되돌립니다. 그외의 사항은 statusAll과 동일합니다. 2.2.4 큰 그림 조각내기 2.2.2 절에서 알아 본 HTTP 의 전송방식 때문에 많은수의 작은 이미지를 가져오는것 보다 큰 사이즈의 그림 하나를 가져오는 것이 더 효율 적이라는 것을 알 수 있었습니다. 게임에서는 많은 수의 이미지들이 사용 됩니다. 이 것들을 따로따로 별개의 이미지 파일로 관리하는 것도 쉬운일이 아니고, 서 버로 부터 로드시 소요되는 시간도 무척 길어질 것이므로, 이것을 해결하기 위해 다수의 이미지를 하나의 그림파일에 저장 하고 그 파일을 서버로 부터 가져온후 그림을 조각내어 사용하는 법을 알아보겠습니다. ● ImageFilter Java는 큰 그림을 작은 몇 개의 조각으로 나눌 수 있도록 ImageFilter 라 는 클래스를 제공하고 있습니다. ImageFilter 는 원본 이미지를 좀더 작은 사각형형태로 잘라내기 위한 CropImageFilter와 원본 이미지의 색상에 변화 를 주기위해 주로 사용되는 RGBImageFilter 이렇게 두 가지가 있습니다. 이 절에서는 앞으로 사용될 CropImageFilter에 대해서만 설명 하도록 하겠습니 다. (RGBImageFilter를 이용할 수 있는곳을 하나 예로 들자면, 스트리트 파 이터 등의 게임에 등장하는 캐릭터의 옷색깔을 바꿀 때 사용하면 될 것입니 다) CropImageFilter는 createImage메소드가 기존의 이미지를 Filter처리하여 새로운 이미지를 만들어 낼 때 사용합니다. CropImageFilter는 말그대로 원 본 이미지를 자르기 위한 하나의 필터일 뿐이고, 실제 컷팅 작업을 하는 것 은 FilteredImageSource라는 클래스가 담당합니다. FilteredImageSource 는 createImage메소드가 인수로 요구하는 ImageProduce(인터페이스 입니다) 타 입의 서브클래스 이므로, FilteredImageSource 객체를 바로 createImage 메 소드의 인수로 넘겨줄 수 있습니다. 실제 소스를 보도록 합시다. 01: ImageFilter filter; 02: 03: orgImage = getImage(getCodeBase(), "TEST.GIF"); 04: filter = new CropImageFilter(10, 10, 50, 50); 05: smallImage = createImage(new FilteredImageSource( 06: orgImage.getSource(), filter)); [소스3] ImageFilter사용의 예 위 소스중 4번라인에서 CropImageFilter를 생성합니다. 원본 그림인 org- Image의 좌표(10, 10)을 좌측 상단으로 하는 50x50 크기의 이미지를 얻어오 기 위한 필터입니다. 다음으로 5번 라인에서 실질적으로 원본 이미지에 Fi- lter 처리를 하여 새로운 이미지를 만들어냅니다. 지금까지 알아본 이미지 절단법을 이용하여 만든 애플릿의 소스가 아래에 있습니다. 빨강, 녹색, 파랑 세개의 공이 그려져있는 이미지를가져와서 각 각의 공을 따로 분리해서 출력하는 예제입니다. 앞에서 거의다 설명한 내용 이므로 소스의 이해에는 문제가 없을 것입니다. 01: import java.awt.*; 02: import java.applet.*; 03: import java.awt.image.*; // Image관계 클래스들이 정의 04: 05: public class CutBitmap extends Applet 06: { 07: private MediaTracker tracker; 08: 09: // 원본 이미지(빨강/녹색/파랑 3개의 공) 10: private Image imgBallAll; 11: 12: // 원본 이미지에서 오려낸 각각의 공 13: private Image imgRedBall; 14: private Image imgGreenBall; 15: private Image imgBlueBall; 16: 17: public void init() 18: { // 하는일이 없다 19: } 20: 21: public void start() 22: { 23: ImageProducer producer; 24: ImageFilter filter; 25: 26: tracker = new MediaTracker(this); 27: // 원본 이미지를 서버로부터 가져온다 28: imgBallAll = getImage(getCodeBase(), "Ball.GIF"); 29: 30: tracker.addImage(imgBallAll, 0); 31: 32: // 원본 이미지로부터 ImageProducer를 추출 33: producer = imgBallAll.getSource(); 34: 35: // 빨간공을 위한 필터를 만든다 36: filter = new CropImageFilter(0, 0, 63, 63); 37: // 그림을 잘라 낸다 38: imgRedBall = createImage( 39: new FilteredImageSource(producer, filter)); 40: tracker.addImage(imgRedBall, 1); 41: 42: // 녹색공을 위한 필터를 만든다 43: filter = new CropImageFilter(63, 0, 63, 63); 44: // 그림을 잘라 낸다 45: imgGreenBall = createImage( 46: new FilteredImageSource(producer, filter)); 47: tracker.addImage(imgGreenBall, 1); 48: 49: // 파란공을 위한 필터를 만든다 50: filter = new CropImageFilter(126, 0, 63, 63); 51: // 그림을 잘라 낸다 52: imgBlueBall = createImage( 53: new FilteredImageSource(producer, filter)); 54: tracker.addImage(imgBlueBall, 1); 55: 56: try { 57: tracker.waitForAll(); 58: } catch (InterruptedException ie) {} 59: } 60: 61: public void paint(Graphics g) 62: { 63: // 모든 이미지가 로드됐으면, 화면에 이미지를 그린다 64: if ((tracker.statusAll(true) 65: & MediaTracker.COMPLETE) != 0) { 66: g.drawImage(imgBallAll, 0, 0, this); 67: g.drawImage(imgRedBall, 0, 100, this); 68: g.drawImage(imgGreenBall, 100, 100, this); 69: g.drawImage(imgBlueBall, 200, 100, this); 70: } else 71: g.drawString("Loading Image....", 20, 20); 72: } 73: } [소스4] CutBitmap.java ☞ 새로 등장한 것들 ◇ ImageFilter ImageFilter 는 Image 데이터를 가공하기 위해 제공되는 클래스 입니 다. ImageFilter는 이미지 데이터 가공을 위한 각종 메소드들을 가지고 있고, FilteredImageSource 클래스에 의해 새로운 이미지를 만들어 낼 때 사용하게 됩니다. ◇ CropImageFilter(int x, int y, int w, int h) CropImageFilter 클래스는 원본 이미지를 작은 조각으로 잘라내는 수 단을 제공하며, ImageFilter 의 서브 클래스 입니다. (x, y)를 좌측 상 단으로 하고, w x h 만큼의 크기로 이미지를 절단합니다. ◇ ImageProducer ImageProducer는 이미지를 재 구성 하기 위해 필요한 클래스 입니다. 33번 라인에서 원본 이미지로 부터 ImageProducer를 추출해내고 있습니 다. ◇ Image.getSource() 해당 이미지로 부터 ImageProducer 인터페이스를 되돌립니다. ◇ FilteredImageSource(ImageProducer orgImg, ImageFilter filter) 이 클래스는 주어진 orgImg(원래 이미지)에 filter 를 적용하여 새로 운 ImageProducer를 되돌려 줍니다. createImage(ImageProducer produ- cer)메소드의 인수로 사용되어 필터 처리가 된 새로운 이미지를 만들어 내게 됩니다. ◇ Component.createImage(ImageProducer producer) 3회 강좌 1.3.3절에서 본바 있는 createImage(int w, int h)메소드와 함께 Component클래스에 정의되어 있습니다. 이 메소드는 주어진 Imag- eProducer로 부터 새로운 이미지를 만들어 냅니다. [소스4]의 38 번 라 인에서 실제로 사용되고 있습니다.

'자바 > 자바게임' 카테고리의 다른 글

자바프로그래밍#6  (0) 2010.10.28
자바게임프로그래밍#5  (0) 2010.10.28
자바게임프로그래밍#3  (0) 2010.10.28
자바게임프로그래밍#2  (1) 2010.10.28
자바게임프로그래밍#1  (1) 2010.10.28

+ Recent posts