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

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

안녕하세요? 친소맨입니다. 이번 강좌는 소스가 길어서 내용은 별루 안되는 데, 길이만 길군요.. 앞으로도 계속 이런 사태가 벌어질것 같아서 걱정입니 다. 대신 주석을 너무하다 싶을정도로 붙여놓은 소스이므로 참고 하시면 많 은 도움이 될듯 합니다. * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. 3.1.3 마음대로 움직이는 탱크를 만들어 봅시다. 지금까지 배운 것을 모두 총괄 복습하는 의미에서 다소 덩치가 있는 프로 그램을 만들어 보도록 하겠습니다. 제목에서 알 수 있듯이 우리가 임의로 조정가능한 탱크 애플릿을 만들어 볼 것입니다. 우선 간단하게 계획부터 세 우기로 합시다. ● 탱크 애플릿의 목적 "화면상에 탱크 이미지를 출력하고, 우리가 누르는 방향키에 따라 상/하/ 좌/우 네방향으로 이동 하도록한다. 각각의 방향에 따라 서로 다른 이미 지가출력되도록 한다." ● 계략적인 프로그램의 흐름 ?start method ① 애플릿에서 사용할 이미지 데이터를 서버로부터 전송해 온다. ② 전송해 온 이미지를 CropImageFilter를 이용해 4방향 각각의 이미지 로 분리한다. ③ 실제 작업을 할 메모리 버퍼(더블 버퍼링용)를 생성한다. ④ 애플릿의 메인 쓰레드를 생성하고 활성화 시킨다. ⑤ 각종 변수들을 초기화 한다. (탱크의 초기 위치, 탱크의 방향 등) ?run method ⑥ 화면을 갱신하고 일정기간 쓰레드를 쉬게 한다. ⑦ 키보드가 PUSH 됐다면, 해당 키 값에 따라 탱크를 이동시킨다. ⑧ ⑥번으로 이동한다. ?paint method 탱크 이미지가 로드 완료 되었나? 예) 탱크의 방향에 맞춰 이미지를 탱크의 현재 위치에 출력한다. 아니오) "Loading Image..."를 출력해 이미지 로드중임을 표시한다. ?keyDown method PUSH 된 키 값이 상/하/좌/우 중 한 값이면 해당 키보드 플랙을 true로 셋팅한다. 탱크의 현재 방향을 설정한다. ?keyUp method POP된 키보드 값이 상/하/좌/우 중 한 값이면 해당 키보드 플랙을 fal- se로 셋팅한다. 탱크 애플릿의 수행 절차입니다. keyDown메소드와 keyUp메소드, paint 메 소드는 OS로 부터 통보 되는 이벤트에 의해 불려지는 핸들러 이므로 우리가 따로 불러주지 않더라도 자동으로 불려지게 됩니다. 탱크의 이동처리를 ke- yDown메소드에서 직접 처리하지 않는 이유는 keyDown메소드로는 연속적으로 눌려져 있는 키 값을 처리하지 못하기 때문입니다. (KeyDown 이벤트는 키가 눌려지는 시점에서 단 1회 발생합니다) 이 절차에 따라 만들어진 소스가 아 래 [소스2]입니다. ! 주의 : 아래의소스중 라인번호가 붙어 있지 않은 라인은 원래는 위의줄 과 한 라인이나, 편집을 위해 나눠 놓은 것입니다. 001: import java.awt.*; 002: import java.applet.*; 003: import java.awt.image.*; 004: import java.lang.Runnable; 005: 006: public class Tank extends Applet implements Runnable 007: { 008: // 애플릿의 주 쓰레드 009: private Thread trdMain; 010: 011: private MediaTracker tracker; 012: //서버로 부터 받을 이미지 013: private Image imgTankAll; 014: // 각 방향의 이미지 015: private Image imgUp; 016: private Image imgLeft; 017: private Image imgRight; 018: private Image imgDown; 019: 020: // 탱크 이미지를 그릴 메모리 버퍼 021: private Image imgOffScr; 022: private Graphics grpOffScr; 023: 024: // 탱크의 방향과 위치 025: private int tankDir; 026: private int tankXp, tankYp; 027: 028: // 탱크의 방향 설정및 체크에 사용할 상수 029: private final int UP = 1; 030: private final int LEFT = 2; 031: private final int RIGHT = 3; 032: private final int DOWN = 4; 033: 034: // UP/DOWN/LEFT/RIGHT 키의 상태를 저장할 Flag 035: private boolean Key_UP = false; 036: private boolean Key_DOWN = false; 037: private boolean Key_LEFT = false; 038: private boolean Key_RIGHT = false; 039: 040: public void start() 041: { 042: ImageProducer producer; 043: ImageFilter filter; 044: 045: // 4방향의 탱크 이미지를 서버로 부터 가져온다 046: tracker = new MediaTracker(this); 047: imgTankAll = getImage(getCodeBase(), "Tank.GIF"); 048: 049: tracker.addImage(imgTankAll, 0); 050: 051: producer = imgTankAll.getSource(); 052: 053: // 서버로부터가져온 이미지에서 4방향이미지를 잘라낸다 054: filter = new CropImageFilter(1, 1, 136, 136); 055: imgUp = createImage(new FilteredImageSource(producer, filter)); 056: tracker.addImage(imgUp, 1); 057: 058: filter = new CropImageFilter(138, 1, 136, 136); 059: imgRight = createImage(new FilteredImageSource( producer, filter)); 060: tracker.addImage(imgRight, 1); 061: 062: filter = new CropImageFilter(275, 1, 136, 136); 063: imgDown = createImage(new FilteredImageSource( producer, filter)); 064: tracker.addImage(imgDown, 1); 065: 066: filter = new CropImageFilter(412, 1, 136, 136); 067: imgLeft = createImage(new FilteredImageSource( producer, filter)); 068: tracker.addImage(imgLeft, 1); 069: 070: // 애플릿의 크기에 맞춰 더블버퍼링을 할 버퍼를 만든다 071: imgOffScr = createImage(size().width, size().height); 072: grpOffScr = imgOffScr.getGraphics(); 073: 074: // 탱크의 초기 방향을 위로 정하고, 탱크를 화면 중앙에 위치시키기 075: // 위해 좌표를 설정한다. (탱크 이미지의 가로, 세로 크기가 136 Pixel임) 076: tankDir = UP; 077: tankXp = (size().width - 136) / 2; 078: tankYp = (size().height - 136) / 2; 079: 080: // 메인 쓰레드를 생성하고 활성화 시킨다 081: if (trdMain == null) { 082: trdMain = new Thread(this); 083: trdMain.start(); 084: } 085: 086: // 애플릿에게 키보드 이벤트가 발생하도록 OS에게 Focus를 요청한다 087: requestFocus(); 088: } 089: 090: public void run() 091: { 092: // 그림이 완전히 로드될때 가지 기다린다 093: try { 094: tracker.waitForAll(); 095: } catch (InterruptedException ie) {} 096: 097: // 주 루프 098: while (true) { 099: repaint(); // 화면을 갱신한다 100: try { 101: Thread.sleep(50); // 0.05초간 쉰다 102: } catch (InterruptedException ie) {} 103: 104: // 방향에 따라 탱크를 이동시킨다 105: if (Key_UP) 106: tankYp -= 2; 107: if (Key_DOWN) 108: tankYp += 2; 109: if (Key_LEFT) 110: tankXp -= 2; 111: if (Key_RIGHT) 112: tankXp += 2; 113: } 114: } 115: 116: public void stop() 117: { 118: // 쓰레드를 종료한다 119: if (trdMain != null) { 120: trdMain.stop(); 121: trdMain = null; 122: } 123: } 124: 125: public void update(Graphics g) 126: { 127: // update의 고약한 버릇을 없애기 위해 덮어썼음 128: paint(g); 129: } 130: 131: public void paint(Graphics g) 132: { 133: // 이미지가 완전리 로드 되었는지 체크 134: if ((tracker.statusAll(true) & MediaTracker.COMPLETE) != 0) { 135: // 이전 위치의 탱크를 지우기 위해 메모리 버퍼를 배경색으로 136: // 깨끗히 지운다. 137: grpOffScr.setColor(getBackground()); 138: grpOffScr.fillRect(0, 0, size().width, size().height); 139: // 탱크의 현재 방향에 따라 탱크의 이미지를 메모리 버퍼에 그린다 140: switch (tankDir) { 141: case UP: 142: grpOffScr.drawImage( imgUp, tankXp, tankYp, this); 143: break; 144: case RIGHT: 145: grpOffScr.drawImage( imgRight, tankXp, tankYp, this); 146: break; 147: case DOWN: 148: grpOffScr.drawImage( imgDown, tankXp, tankYp, this); 149: break; 150: case LEFT: 151: grpOffScr.drawImage( imgLeft, tankXp, tankYp, this); 152: break; 153: } 154: // 메모리 버퍼의 내용을 화면으로 옮긴다 155: g.drawImage(imgOffScr, 0, 0, this); 156: } else 157: // 이미지가 로드 중이면 메시지를 출력한다 158: g.drawString("Loading Image....", 20, 20); 159: } 160: 161: public boolean keyDown(Event evt, int key) 162: { 163: // PUSH된 키값에 따라 해당키의 플랙을 true로 설정하고 164: // 탱크의 방향을 바꾼다 165: switch (key) { 166: case 1004: 167: Key_UP = true; 168: tankDir = UP; 169: break; 170: case 1005: 171: Key_DOWN = true; 172: tankDir = DOWN; 173: break; 174: case 1006: 175: Key_LEFT = true; 176: tankDir = LEFT; 177: break; 178: case 1007: 179: Key_RIGHT = true; 180: tankDir = RIGHT; 181: break; 182: } 183: return true; // 처리 완료! 184: } 185: 186: public boolean keyUp(Event evt, int key) 187: { 188: // POP된 키의 플랙을 false 로 설정한다 189: switch (key) { 190: case 1004: 191: Key_UP = false; 192: break; 193: case 1005: 194: Key_DOWN = false; 195: break; 196: case 1006: 197: Key_LEFT = false; 198: break; 199: case 1007: 200: Key_RIGHT = false; 201: break; 202: } 203: return true; 204: } 205: } [소스2] Tank.java 소스가 다소 길지만 지금까지 해온 내용들을 이해하고 오셨다면 보시는데 큰 무리는 없었을 것입니다. Tank애플릿을 실행해 보면 화면 중앙에 탱크가 나올 것입니다. 상/하/좌/우키를 누르면 탱크는 방향을 바꾸고 앞으로 전진 을 할 것입니다. ☞ 새로 등장한 것들 ◇ final [소스2]의 29-32라인에서 사용된 final 키워드는 상수를 정의 하거나 더 이상 서브클래스에서 바꿀수 없는 메소드를 만들고 싶을때 사용합니 다. final 키워드가 붙은 데이터멤버는 우리가 임의로 바꿀 수 없고 참 조만 가능합니다. 만약 우리가 데이터 멤버의 값을 바꾸려 한다면 Java 컴파일러는 컴파일을 거부할 것입니다. ◇ Component.size() 이 메소드는 해당 Component 객체의 크기를 Dimension 객체형태로 되 돌립니다. Dimension 클래스는 width 와 height 데이터 멤버를 가지며, 각각 너비와 높이에 대한 데이터를 가지고 있습니다. [소스2]에서는 애 플릿이 홈페이지 상에서 얼마 만큼의 크기를 가지는지를 알아내기 위해 사용하고 있습니다. ◇ Component.requestFocus() requestFocus 메소드는 OS에게 Focus를 요청합니다. Focus를 가진 윈 도우만이 키보드 입력을 받을 수 있으므로 Tank애플릿이 키보드 입력을 받을 수 있게 하기위해 사용되었습니다. ◇ Component.keyDown(Event evt, int key) Component.keyUp(Event evt, int key) 키보드가 PUSH 되거나 POP 될 때 OS에 의해 불려지는 핸들러 입니다. 첫번째 인수로 발생된 이벤트에 대한 정보가 넘어오고, 두번째 인수로 는 PUSH/POP된 키의 코드가 전달 됩니다. 이 두메소드는 boolean 값을 리턴하는데 true 는 모든 처리가 끝났음을 알리는 것이고, false 는 두 메소드가 포함된 Component 의 부모 Component 에게 이벤트를 넘기라는 의미 입니다. 보통 true를 사용합니다. 3.2 애플릿에서 마우스는 어떻게 처리합니까? 입력장치 제어 방법 두번째로 마우스에 대해서 알아보겠습니다. 마우스는 키보드에 비해서 훨씬 다양한 이벤트를 발생시킵니다. 마우스의 버튼이 PU- SH/POP될 때, 마우스 커서가 애플릿 영역을 돌아다닐 때, 마우스 버튼을 누 른채로 애플릿 영역안을 돌아다닐 때, 마우스 커서가 애플릿 영역을 빠져나 가고 들어올 때 각각 OS에 의해 이벤트가 발생됩니다. 이 이벤트들에 대한 핸들러는 Component에 다음과 같이 정의 되어 있습니다. public boolean mouseEnter(Event evt, int x, int y) : 마우스 커서가 애플릿안으로 들어옴 public boolean mouseExit(Event evt, int x, int y) : 마우스 커서가 애플릿 밖으로 나감 public boolean mouseMove(Event evt, int x, int y) : 마우스 커서가 애플릿 내를 돌아다님 public boolean mouseDown(Event evt, int x, int y) : 마우스 버튼이 눌려짐 public boolean mouseDrag(Event evt, int x, int y) : 버튼을 누른채로 마우스를 움직임 public boolean mouseUp(Event evt, int x, int y) : 마우스 버튼을 놓음 [표2] 마우스 이벤트 처리 핸들러 마우스 이벤트 핸들러들은 공통적인 인수를 가지고 있는데, 두 번째와 세 번째 인수인 (x, y)는 해당 이벤트가 발생한 시점에서 마우스커서의 애플릿 내의 위치를 뜻합니다. 연습삼아 간단한 프로그램을 만들어 보도록 하겠습 니다. 화면내에 푸른색 상자가 하나 디스플레이 되고, 윈도우즈에서 아이콘 을 옮기듯이 마우스로 옮길 수 있도록 해보죠. 이것을 구현하는 방법은 상자의 내부에서 마우스의 버튼이 눌려지면 상자 는 이동 가능한 상태로 변하고, 마우스 드래깅(버튼을 누르고 커서를 옮김) 이 일어나면 상자의 위치를 변화시키면 될것입니다. 현재 상자의 상태가 이 동가능한 상태가 아닐 경우 마우스 드래깅에는 별 반응을 보이지 않아야 합 니다. 소스를 보시기 바랍니다. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class MouseTest extends Applet 05: { 06: private Rectangle box; // 화면에 나타날 상자 07: private Color boxColor; // 상자의 색깔 08: private boolean boxFlag; // 상자의 상태 플랙 09: 10: public void start() 11: { 12: int x, y, w, h; 13: 14: // 애플릿의 크기에 따라 상자의 크기를 조절 (애플릿 크기의 1/10으로) 15: w = size().width / 10; 16: h = size().height / 10; 17: x = (size().width - w) / 2; 18: y = (size().height- h) / 2; 19: 20: // 상자를 만든다 21: box = new Rectangle(x, y, w, h); 22: 23: // 상자의 초기 색깔과 상태를 설정 24: boxColor = Color.blue; 25: boxFlag = false; 26: } 27: 28: public void update(Graphics g) 29: { 30: // 나쁜버릇 제거.. 31: paint(g); 32: } 33: 34: public void paint(Graphics g) 35: { 36: // 화면을 바탕색으로 지우고 37: g.setColor(getBackground()); 38: g.fillRect(0, 0, size().width, size().height); 39: // 상자를 그린다 40: g.setColor(boxColor); 41: g.fillRect(box.x, box.y, box.width, box.height); 42: } 43: 44: public boolean mouseDown(Event e, int x, int y) 45: { 46: // 상자위에서 버튼이 PUSH됐으면 47: if (box.inside(x, y)) { 48: // 상자의 색깔을 바꾸고 눌려짐 상태를 true로 한다 49: boxFlag = true; 50: boxColor = Color.red; 51: repaint(); 52: } 53: return true; 54: } 55: 56: public boolean mouseUp(Event e, int x, int y) 57: { 58: // 버튼이 POP 됐으면 상자 상태를 false로 하고 색깔을 바꾼다 59: boxFlag = false; 60: boxColor = Color.blue; 61: repaint(); 62: return true; 63: } 64: 65: public boolean mouseDrag(Event e, int x, inty) 66: { 67: // 상자의 눌려짐 상태가 true일 경우 드래그가 발생하면 68: if (boxFlag) { 69: // 상자를 이동시킨다 70: box.x = x - box.width / 2; 71: box.y = y - box.height / 2; 72: repaint(); 73: } 74: return true; 75: } 76: } [소스3] MouseTest.java 앞의 설명대로 상자는 현재의 상태를 boxFlag에 기록하고 있습니다. 사용 자에 의해 버튼이 눌려져 44번 라인의 mouseDown 메소드가 호출되면 버튼이 눌려진 위치가 상자의 영역에 포함되는지 검사합니다. 만약 포함이 되어 있 다면 상자의 상태를 이동가능한 상태로 바꾸고, 상자의 색상을 붉은 색으로 바꿔준 후 화면을 갱신합니다. 눌려진 버튼이 놓여지면 56번 라인의 mouse- Up 메소드가 호출되고, 상자의 상태를 이동 불가능 상태로 바꾸고, 색깔을 푸른색으로 되돌리고 화면을 갱신합니다. 드래깅을 했을 경우 65번 라인의 mouseDrag 메소드가 호출되고 현재 상자 의 상태가 이동 가능한 상태이면 상자의 위치를 이동시키고, 이동 불가능한 상태라면 그냥 무시합니다. ☞ 새로 등장한 것들 ◇ Rectangle - Rectangle(int x, int y, int w, int h) 이 클래스는 사각형을 정의하고 사각형에 대한 각종 연산을 제공합니 다. 생성시에 사각형의 좌측 상단좌표 (x, y)와 사각형의 가로 세로 길 이 (w, h)를 넘겨줘야 합니다. Rectangle클래스의 데이터 멤버는 x, y, width, height 등이 있습니다. ◇ Rectangle.inside(int x, int y) 주어진 좌표 (x, y)가 사각형내에 포함되는지 여부를 체크합니다. 사 각형 내에 포함되면 true값을 리턴하고, 포함되지 않으면 false값을 리 턴합니다. 이상으로 화면처리에 관한 것은 모두 끝이 났습니다. 이정도로 게임에 사 용되는 모든 그래픽 기법을 배운 것은 아니지만, 앞으로 공부해 나갈것들의 기본이 되는 내용들이므로 무척 중요한 내용들 입니다. 뒷장을 공부하면서 이해가 잘 안되는 부분이나 막히는 부분이 있을 때는 다시 차분히 이 절을 읽어 보시면 많은 도움이 될 것입니다.

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

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

 

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

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

안녕하세요? 강좌가 좀 늦었습니다. 주말에는 개인적인 일로 무척 바빴습니 다. 그래서 강좌가 늦어진거죠. 이번 강좌는 스프라이트와 키보드 처리에 관련된 내용입니다. 키보드나 마우스 처리는 JDK1.0.x대와 1.1.x대 에서 차 이가 나는 부분이므로, 테스트는 해보지 않았으나, 문제가 발생할 소지가 다분히 있습니다. 될 수 있으면 자료실에 있는 JDK1.0.2 를 사용 하시는 것 이 좋을 것입니다. 만약에 원인모를 이유로 애플릿이 정상 작동 안된다면, 십중 팔구 JDK1.0.x와 JDK1.1.x의 이벤트 처리방식의 차이 때문일 것입니다 * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. 2.3 스프라이트는 어떻게 처리하나요? 게임에서 스프라이트의 처리는 거의 필수적입니다. (스프라이트라는 것은 배경을 파손하지 않고 이동하는 이미지를 뜻합니다. 게임기나 8Bit MSX등에 서는 스프라이트를 처리하기 위해 VDP 라고 하는 칩들이 있고, 이들이 독립 적으로 스프라이트들을 관리하며 배경 손상없이 이동을 가능하게 해줍니다. 하지만 PC나 SUN Sparc 등의 컴퓨터에서는 VDP가 따로 존재하지 않고, 대부 분의 VGA 나 그래픽 카드들이 스프라이트를 독립적으로 관리해 주지 않으므 로, 일일이 프로그램으로 배경복구를 해줘야 합니다.) Java 는 스프라이트 관련 메소드를 지원하지 않습니다. 그런데 어떻게 스프라이트를 지원하느냐 구요? 그것은 앞서 배운 DoubleBuffering기법을 이용하면 간단히 해결 됩니 다. 배경을 복구하는 작업과 스프라이트를 다른 곳으로 이동시키는 작업을 우리의 눈에 보이지 않는 메모리 버퍼내에서 하게 되므로 게임을 즐기는 플 레이어 입장에서는 하드웨어 스프라이트와 별반 차이를 느끼지 못하게 됩니 다. ● 투명색의 구현 우선 지난 강좌의 [소스4] CutBitmap.java 를 실행하여 보십시오. 잘려진 빨강, 녹색, 파랑 3개의 공이 따로따로 아래에 그려져 있을 것입니다. 그런 데 원래그림의 흰색 배경도 함께 짤려져 공이 아니라 마치 스케치북에 그린 그림조각 같아 보일 것입니다. 실제 게임에서도 캐릭터가 사각형의 배경에 갖혀서 돌아 다니는 것을 여러분은 원하지 않을 것입니다. 그런데 CropIma- geFilter에 의해 짤려지는 그림은 어짜피 사각형이므로 동그랗게 공을 짤라 낼 수는 없습니다. 셀 애니메이션(Cell Animation)에서는 그림을 투명한 셀 로판지위에 그리고, 그것을 배경위에 놓아서 애니메이션을 구현합니다. Ja- va에서도 마찬가지로, 흰색 바탕이 투명한 색이라면 사각형으로 잘려지더라 도 큰 문제는 없을 듯 합니다. (실제 그림은 사각형이지만, 투명한 영역은 뒷부분의 배경이 보이게 되므로, 그림이 폐곡선 형태인것 처럼 보이게 될껍 니다.) Java는 GIF89a형식을 지원합니다. GIF89a는 발전된 GIF로써 투명색이라는 것을 지원하고 있습니다. Java프로그램에서는 투명색을 위해 아무것도 해줄 것이 없습니다. 단순히 이미지 파일내의 바탕색을 투명색으로 지정해 주는 것만으로 투명색 구현을 할 수 있는 것입니다. 앞 절에서 만들었던 CutImage애플릿에 사용되는 이미지는 "Ball.GIF"입니 다. 이 이미지를 GIF89a형식으로 바꾸고, 투명색을 지정해 보겠습니다. ◇ 투명색을 지원하는 유틸리티들 GIF89a형식에서 투명색을 지원하는 유틸리티는 여러가지가 있습니다. 이들 중에서 우리가 사용할 것은 "GIFTRANS.EXE"라고 하는 프로그램 입 니다. 사용방법을 배워 봅시다. (이것 외에도 LViewPro, GifConstruct- on Kit, PhotoShop등 많은 유틸리티가 투명GIF를 지원합니다.) ① 투명색으로 선정할 색깔의 번호를 알아낸다 먼저 "GIFTRANS.EXE" 를 이용해 투명색으로 만들 색깔의 번호를 알아 내야 합니다. "Ball.GIF"에서 투명색은 완전한 흰색으로 (RGB농도가 각 각 255, 255, 255)입니다. "GIFTRANS.EXE"에서 색상리스트를 보는 옵션 은 '-l'입니다. <실행의 예> C:\JavaGame>GIFTRANS.EXE -l BALL.GIF Global Color Table: Color 0: Red 255, Green 255, Blue 255, #ffffff Color 1: Red 199, Green 43, Blue 43, #c72b2b Color 2: Red 199, Green 43, Blue 87, #c72b57 Color 3: Red 199, Green 43, Blue 131, #c72b83 Color 4: Red 199, Green 43, Blue 175, #c72baf Color 5: Red 179, Green 43, Blue 199, #b32bc7 . . . Color 250: Red 170, Green 0, Blue 170, #aa00aa Color 251: Red 170, Green 0, Blue 0, #aa0000 Color 252: Red 0, Green 170, Blue 170, #00aaaa Color 253: Red 0, Green 170, Blue 0, #00aa00 Color 254: Red 0, Green 0, Blue 170, #0000aa Color 255: Red 0, Green 0, Blue 0, #000000 실행 결과로 색번호와 RGB농도가 출력됩니다. 그런데 너무 빨리 지나 가서 제대로 알아 볼 수가 없을 것입니다. "GIFTRANS.EXE" 는 Color T- able List를 표준에러 스트림으로 출력합니다. 이것을 파일로 저장하는 방법은 "-e 출력파일명"옵션을 주면 됩니다. 위의 컬러 테이블에서 0번 색깔의 RGB 농도가 255, 255, 255로 "Ball.GIF"의 배경색임을 알 수 있 습니다. ② 투명색 처리를 한다 이제 투명색으로 만들 색번호를 알아냈으니 투명색 처리를 하도록 하 겠습니다. "GIFTRANS.EXE"는 원본 이미지를 GIF89a로 바꾼 내용을 표준 출력 스트림으로 출력합니다. (MS-DOS에서 표준 출력은 화면이므로, 화 면에 변환된 내용들이 주욱 나올것입니다.) "-o 출력파일명" 옵션으로 표준 출력을 파일로 보낼 수 있습니다. <실행의 예> C:\JavaGame>GIFTRANS.EXE -t 0 -o Ball2.GIF Ball.GIF -t 0 : 0번색을 투명색으로 지정한다 -o Ball2.GIF : 표준출력(GIF89a로 변환된 내용)을 Ball2.GIF로 출력 Ball.GIF : 원본 이미지의 파일명 변환이 끝났습니다. 디렉토리의 내용을 살펴보면 "Ball2.GIF"가 생성 되어 있을것입니다. 이 이미지의 이름을 "Ball.GIF" 로 바꾸고 바로 앞 절에서 만든 [소스4] CutBitmap.java 를 실행해 보십시오. 그림의 바깥 쪽 흰색 테두리가 없어진 것을 확인하실수 있을 것입니다. 시간이 된다 면 <BODY> Tag에 배경그림을 다른것으로 줘서 실제 뒷 배경을 파손하지 않는지 테스트 해보셔도 좋을것입니다. 3. 그림을 마음대로 움직여 볼까요? 이번 절에서는 지금까지 배워온 기법들에 덧붙여 화면상의 그림을 마음대 로 움직일 수 있는 기법을 배우게될 것입니다. Java에서 사용가능한 입력장 치는 키보드, 마우스 두가지 입니다. 불행이도 게임패드, 죠이스틱은 아직 지원하지를 않습니다. 3.1 애플릿에서 키보드는 어떻게 처리합니까? GUI 환경의 운영체제에서 사용자가 할 수 있는 일은 무척 많습니다. 윈도 우상에 존재하는 각종 버튼을 누를수도 있을 것이고, 키보드를 직접 누를수 도 있을 것이고, 아예 윈도우를 닫아버릴수도 있을 것입니다. 이런 사용자 의 행동을 응용프로그램이 직접 체크를 한다면 응용프로그램을 만드는 일이 무척 고역일 것입니다. 어떤 사람이 어떤 행동을 취할지 일일히 경우의 수 를 따져 프로그램하기란 여간 힘든일이 아닐테니까요. MS-Windows를 비롯한 대부분의 GUI 운영체제들은 사용자들의 행동을 감시 하고, 친절하게 이들의 행동을 응용 프로그램에게 통보해 줍니다. 덕분에 응용프로그램들은 일일이 사용자들의 행동을 체크할 필요가 없습니다. 이때 운영체제로 부터 응용프로그램으로 통보되는 내용을 메시지(Message) 또는 이벤트(event)라고 부릅니다. 응용 프로그램에서는 각각의 이벤트에 대한 핸들러(Handler : 해당 이벤트를 처리하기 위한 메소드)를 만들어 줌으로써 사용자의 요구를 수용할 수 있게 되는 것입니다. 예를 들어 사용자가 윈도 우를 Minimize 시킨후 다시 이를 원래 크기로 환원시켰다면, OS 는 응용 프 로그램에게 '윈도우의 내용을 다시 그리시오' 라고 'repaint' 이벤트를 발 생시키고 통보를 할 것입니다. 응용 프로그램은 repaint이벤트에 대한 핸들 러로 paint메소드를 가지고 있으므로 paint 메소드를 실행해 윈도우의 내용 을 다시 그리게 되고 우리는 정상적인 화면을 볼 수 있게 되는 것입니다. 'repaint'이벤트와 마찬가지로 사용자가 키보드를 눌렀을때에도 이벤트가 발생되고, 해당 응용 프로그램으로 OS가 통보합니다. 3.1.1 키보드에서 발생할 수 있는 이벤트의 종류는? 키보드에서 발생할 수 있는 이벤트는 두 가지 입니다. 우리가 키를 누를 때 KeyDown 이벤트가 발생되고 눌렀던 키를 놓을 때 KeyUp 이벤트가 발생되 어 현재 포커스(focus)를 가지고 있는 Component(거의 모든 AWT요소들의 최 상위 클래스입니다)에게 통보됩니다. 각각의 이벤트에 대해서 Component 는 해당 이벤트를 처리하기 위한 메소 드를 제공 하는데, KeyDown 이벤트에는 keyDown 메소드, KeyUp 이벤트에는 keyUp메소드가 불려지게 됩니다. 이 두 개의 메소드는 Component 클래스에 다음과 같이 정의되어 있습니다. public boolean keyDown(Event evt, int key); public boolean keyUp(Event evt, int key); [표1] 키보드 이벤트 처리 핸들러 기본적으로 위의 두 메소드는 별다른 일을 하지 않도록 정의 되어 있으므 로, 우리는 paint 메소드들 다시 정의한 것처럼 이 메소드들을 다시 정의함 으로써 원하는 일을 할 수 있을 것입니다. 3.1.2 키코드를 출력해주는 프로그램을 만들어 봅시다. 키보드 처리를 하는 방법을 연습하기 위해 간단히 키를 누르면 해당 키의 코드 값을 출력해주는 애플릿을 만들어 보도록 합시다. 게임에서 사용되는 각종 키들의 코드 값을 알아내는데 사용할 것이므로 꼭 필요한 유틸리티 입 니다. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class ReadKeyCode extends Applet 05: { 06: private String strMsg; // 출력할 메시지를 담을 변수 07: 08: public void start() 09: { 10: // 처음에는 아무것도 안눌려졌음 11: strMsg = "No key pressed"; 12: } 13: 14: public void paint(Graphics g) 15: { 16: // 화면에 메시지 출력 17: g.drawString(strMsg, 10, 45); 18: } 19: 20: public boolean keyDown(Event e, int key) 21: { 22: // 눌려진 키값을 출력하기 위해 문자열을 만들고 23: strMsg = "Key code is (" + key + ")"; 24: // 화면 갱신 25: repaint(); 26: return true; // 처리 완료! 27: } 28: 29: public boolean keyUp(Event e, int key) 30: { 31: // 키보드에서 손을 뗏으므로 아무것도 안눌려졌음 32: strMsg = "No key pressed"; 33: // 화면 갱신 34: repaint(); 35: return true; // 처리 완료! 36: } 37: } [소스1] ReadKeyCode.java ReadKeyCode 애플릿을 실행해 봅시다. 화면 중앙에 "No key pressed"라는 메시지가 나와 있을 것입니다. 그런데 키보드를 누르면 키 값이 화면에 "K- ey code is (xxx)" 식으로 출력 되어야 될것인데 이상하게도 아무런 반응이 없습니다. 이유는 Web Browser나 AppletViewer가 Focus를 가지고 있기 때문 입니다. 다른 이벤트와는 달리 키보드 이벤트는 Focus를 가지고 있는 윈도 우에게만 전달 됩니다. 때문에 우리가 누른 키보드에 대한 이벤트는 ReadK- eyCode 애플릿이 가져간 것이 아니라 Web Browser나 AppletViewer가 가져간 것이므로, 애플릿은 아무 반응이 없는 것입니다. 마우스커서를 애플릿 영역 으로 옮긴후 한 번 클릭을 해줘서 애플릿이 Focus를 가지게 한 후 키보드를 눌러봅시다. 제대로 하셨다면 우리가 누른 키보드에 대한 코드가 디스플레 이 될 것입니다. 그렇다면 키보드 입력을 받는 모든 애플릿에서 매번 마우 스로 한 번 클릭을 한 후 사용해야 할까요? 그렇지는 않습니다. Component 객체의 메소드중 requestFocus() 라고 하는 메소드가 있는데, 이 메소드를 start()메소드 등에서 한 번 실행해 주면 현재 Focus가 Web Browser나 App- letViewer에서 애플릿으로 넘어가게 됩니다.

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

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

 

자바 게임 프로그래밍 강좌 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

 

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

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

안녕하세요? 두 개 연속 편집 (2 Hit Combo?)의 과업을 달성한 친소맨 입니 다. 꼴은 모니터를 장장 두시간이나 쳐다보고 있었더니, 눈이 아른 아른 거 리는 군요.. @_@ 이번 강좌로 자바 게임 프로그래밍의 1장이 끝났습니다. 이번 강좌까지 만 들어 내게될 각종 소스파일들을 자료실에 업로드 해놓겠습니다. 잘라서 편 집하기 싫으신 분들은 자료실에서 다운로드 받아서 바로 실행해 보세요. * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. ● 완성된 PingPong2 이제 더블버퍼링을 구현하는 법도 배웠고, Multi-Thread를 구현하는 법도 배웠으니, 실전에 써먹어 보도록 하겠습니다. 아래에 쓰레드와 더블버퍼링 을 이용한 PingPong2 애플릿의 소스가 있습니다. 지금까지 배운 것을 적용 하며 읽다보면 반이상은 이해가 되실테니 천천히 읽어 보고, 다음으로 넘어 가시길 바랍니다. 모르는 부분이 많더라도 낙담하실 필요는 없습니다. 앞으 로 수십번은 더봐야 할 내용이니까요. 01: import java.awt.*; 02: import java.applet.*; 03: import java.lang.Runnable; 04: 05: public class PingPong2 extends Applet implements Runnable 06: { 07: Thread trdMyThread;// 애플릿의 주 쓰레드 08: 09: Image imgOffScr; // 메모리 버퍼로 쓸 Image 10: Graphics grpOffScr; // 메모리 버퍼의 Graphics 핸들 11: 12: int ballXp, ballYp, ballXs, ballYs; 13: 14: public void init() 15: { 16: resize(400, 400); 17: } 18: 19: public void start() 20: { 21: // 아직 쓰레드가 만들어져 있지 않으면 새로 만드시오 22: if (trdMyThread == null) 23: trdMyThread = new Thread(this); 24: 25: // 메모리 버퍼가 만들어져 있지 않다면 400x400의 26: // 크기로 만드시오(애플릿의 영역이 400x400이므로) 27: if (imgOffScr == null) 28: imgOffScr = createImage(400, 400); 29: 30: // 메모리 버퍼에 대한 Graphics 핸들을 얻어 온다 31: grpOffScr = imgOffScr.getGraphics(); 32: 33: ballXp = 100; 34: ballYp = 200; 35: ballXs = 4; 36: ballYs = 4; 37: 38: // 만들어진 쓰레드를 활성화 시킨다 39: trdMyThread.start(); 40: } 41: 42: // 쓰레드의 주 실행 루틴 43: public void run() 44: { 45: // 무한 루프가 쓰레드 안으로 옮겨졌다! 46: while (true) { 47: // paint를 한번 불러준다 48: repaint(); 49: // 0.1초간 쉬고 50: try { 51: Thread.sleep(100); 52: } catch (InterruptedException ie) {} 53: 54: // 공을 옮긴다 55: ballXp += ballXs; 56: ballYp += ballYs; 57: 58: // 벽에 부딪혔으면 방향을 반전 59: if (ballXp > 400 || ballXp < 0) 60: ballXs = -ballXs; 61: if (ballYp > 400 || ballYp < 0) 62: ballYs = -ballYs; 63: } 64: } 65: 66: public void paint(Graphics g) 67: { 68: // 메모리 버퍼를 까맣게 지우고 69: grpOffScr.setColor(Color.black); 70: grpOffScr.fillRect(0, 0, 400, 400); 71: 72: // 메모리 버퍼에 공을 그린다 73: grpOffScr.setColor(Color.white); 74: grpOffScr.fillArc(ballXp, ballYp, 25, 25, 0, 360); 75: 76: // 완성된 그림을 화면에 뿌린다 77: g.drawImage(imgOffScr, 0, 0, this); 78: } 79: 80: public void update(Graphics g) 81: { 82: paint(g); 83: } 84: 85: public void stop() 86: { 87: // 쓰레드가 살아 있다면 활동을 중지시키고, 없애버린다 88: if (trdMyThread != null) { 89: trdMyThread.stop(); 90: trdMyThread = null; 91: } 92: } 93: } [소스5] PingPong2.java 소스의 구조가 2 회 강좌에서 봤던 [표3]의 Runnable Interface를 포함한 Applet의 기본구조와 거의 같아 졌습니다. 내용을 살펴보도록 하겠습니다. ◇ Thread 의 생성 [소스5]에서 쓰레드를 생성하는 곳은 23번 라인입니다. 23번 라인에 서 new 연산자를 이용해 Thread 객체를 생성 합니다. 이때 this라고 불 려지는 특수한 포인터를 넘겨주게 되는데, 여기에서 this 라는 것은 애 플릿 자체를 뜻합니다. Thread의 생성자(Constructor)중 Runnable 인터 페이스를 인수(Parameter)로 받아 들이는 것이 있습니다. 이 생성자는 Runnable 인터페이스의 run메소드를 쓰레드의 주 루틴으로 연결해 주는 역할을 합니다.(Thread의 주 루틴도 run 으로 Runnable 인터페이스에서 의 run과 메소드 명이 같습니다) PingPong2 애플릿 역시 Runnable 인터 페이스를 상속받은 서브 클래스이므로, Runnable 인터페이스를 인수로 하는 생성자에게 넘겨질 수 있고, 그 덕택에 우리의 PingPong2 애플릿 이 쓰레드화(?) 되는 것입니다. 39번 라인에서 trdMyThread라는 쓰레드 를 활성화 시키는데, 이 쓰레드의 주 루틴은 PingPong2 애플릿의 run메 소드이므로, PingPong2애플릿의 run 메소드 부분이 쓰레드가 되어 실행 이 되는 것입니다. (소스의 어느 곳을 봐도 run메소드를 직접 불러주는 곳은 없습니다. 그 이유는 Thread의 start 메소드가 쓰레드를 생성시키 고, 바로 run메소드를 불러주기 때문입니다) ◇ 메모리 버퍼의 생성 이 내용은 앞에서 배운 `Double Buffering의 구현'에서 이미 다룬 내 용이지만, 확인도 할겸해서 한번 더 설명하도록 하겠습니다. [소스5]의 28번 라인에서 더블버퍼링을 위해 메모리버퍼를 만드는 작업을 하고 있 습니다. createImage메소드로 애플릿이 화면상에 차지하고 있는 영역과 크기가 같은 400x400 짜리 Image를 생성 합니다. 다음으로 31번 라인에 서 메모리내에 할당된 Image에 대한 Graphics핸들을 getGraphics메소드 를 이용해 얻어내서 이것을 grpOffScr에 저장합니다. 이후 모든 작업은 grpOffScr을 통해 이루어질 것입니다. ◇ run 메소드 (쓰레드의 주 루틴) paint메소드에서 무한 루프가 없어지는 대신 새로 생긴 run 메소드에 무한 루프가 생겼습니다. 한 두 차례 실행하고 말것이라면 궂이 쓰레드 로 만들 필요가 없기 때문에 쓰레드의 주 루틴은 무한루프인 경우가 많 습니다. run메소드는 우선 repaint메소드를 호출해서 paint메소드를 간 접적으로 불러 줍니다. 바로 paint 메소드를 부르지 않고 repaint 메소 드를 불러주는 이유는 run 메소드 내에서는 paint메소드에 넘겨질 애플 릿에 대한 Graphics 핸들을 얻을 수 없기 때문입니다. 다음으로 Threa- d.sleep(100) 이라는 문장을 수행하게 되는데, Thread 클래스의 메소드 인 sleep는 밀리초 (1/1000초) 단위로 쓰레드의 활동을 중단 시키는 역 할을 합니다. PingPong2 애플릿 에서는 이 수치가 100 이므로 0.1초 정 도의 시간동안 공이 멈추게 됩니다. 이 수치를 작게하면 작게할수록 공 이 빨리 움직일 것입니다. 만약에 이 문장을 빼 버린다면 공은 애플릿 영역속을 미친 듯이 튀어 다닐 것입니다. (너무 빨라서 공이 순간이동 을 하는 것 처럼 보일지도 모릅니다) sleep는 단순히 공의 이동 속도를 지연 시키는것 이외에도 CPU를 다른 프로그램들에게 양보하는 의미에서 주로 사용되어 집니다. ◇ update 메소드 못보던 녀석이 나타났습니다. update메소드를 살펴보면 별로 하는 일 이 없어 보입니다. 단순히 paint 메소드를 불러주는군요. 왜 이렇게 했 을까요? 이유를 알고 싶다면 다음 글을 읽기 전에 update메소드 부분을 주석처리하고 컴파일하여 애플릿을 다시 Web Browser로 불러 보시기 바 랍니다. 아마 엄청나게 껌벅이는 화면을 보실수 있을 것입니다. 우리가 repaint메소드를 불러주면 바로 paint메소드가 불려 지는 것이 아니라, update메소드가 먼져 불려지고, 다음으로 paint메소드가 update 메소드 에 의해 불려지게 됩니다. 그런데 update 메소드는 아주 고약한 버릇을 가지고 있습니다. 그 버릇이 무엇이냐면, 화면을 흰색으로 깨끗이 지우 고 paint메소드를 부르는 것입니다. 이 때문에 화면이 심하게 껌벅이게 되는 것이죠. 그래서 그 못된 버릇을 고쳐주기 위해 부모의 update메소 드를 재정의하여 나쁜 버릇을 없앤 것 입니다. ☞ 새로 등장한 것들 ◇ Component.createImage(int w, int h) 거의 모든 AWT 요소들의 부모인 Component의 메소드로서 지정된 크기 (w x h)의 Image를 만들고 이것을 되돌립니다. ◇ Image.getGraphics() createImage 메소드에 의해서 만들어진 Image 에 한해서 실행 가능한 메소드 입니다. 이 메소드는 메모리에 만들어진 Image에 대한 Graphics 핸들을 되돌립니다. ◇ Graphics.drawImage(Image img, int x, int y, ImageObserver obsrvr) Graphics 핸들로 지정되는 사각영역의 (x, y)좌표를 좌측상단으로 하 는 Image 를 그립니다. 이때 Image 가 완전하지 못할 경우 obsrvr 에게 통보합니다. 보통 obsrvr는 this(애플릿 자체)를 넣어 주면 됩니다. 1.3.4 좀더 범용적인 PingPong을 만들어 봅시다. 이번 단원에서는 APPLET Tag 중 PARAM Tag 를 어떻게 사용하는 지를 배울 수 있을 것입니다. 앞 단원에서 완성시킨 PingPong 애플릿은 범용성이 떨어 집니다. 우리는 공의 크기를 바꾸거나 공의 속도를 바꾸기 위해 매번 컴파 일을 다시 하고 싶지는 않습니다. 또 공의 색깔을 바꾸고 싶어도 다시 컴파 일을 해야 합니다. 무척 불편하군요. 이제 이런 불펀함을 고쳐보도록 합시 다. ● 어떻게 HTML 로 부터 데이터를 넘겨 받을 수 있습니까? 우리는 첫번째 강좌의 1.2 절에서 APPLET Tag 의 사용법에 대해서 배웠습 니다. (기억이 잘 안나시는 분은 다시 첫번째 강좌를 펴서 275 번째 줄부터 읽어 보십시오) 여기에서 <PARAM...> 이라고 적혀있던 Tag가 기억날 것입니 다. 바로 이 Tag 를 이용하여 HTML 이 Java 애플릿에게 데이터를 넘겨줄 수 있습니다. 1.2 절에서 만들어 보았던 HelloWorld 애플릿을 개선하여 우리가 원하는 문장을 출력하는 애플릿으로 만들어 봅시다. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class ShowMessage extends Applet 05: { 06: private String strMsg; 07: 08: public void init() 09: { 10: resize(100, 100); 11: } 12: 13: public void start() 14: { 15: strMsg = getParameter("MESSAGE"); 16: } 17: 18: public void paint(Graphics g) 19: { 20: g.drawString(strMsg, 20, 45); 21: } 22: } [소스6] ShowMessage.java [소스6] 은 HelloWorld 애플릿을 개조하여 만든 ShowMessage 라는 애플릿 입니다. 이 애플릿은 HTML파일에서 넘어온 메시지를 화면에 출력해 줄 것입 니다. 이 소스에서 주의해서 살펴볼 곳은 15 번 라인 입니다. getParameter 라는 메소드는 인수로 넘어간 문자열과 일치 하는 변수명을 가진 PARAM Tag 로 부터 VALUE 뒤에 명시된 값을 String 형태로 받아 오는 역할을 합니다. 만약 우리가 'I am a boy!' 라는 문장을 출력하고 싶다면 다음과 같이 HTML 파일을 만들어 주면 될 것입니다. 01: <HTML> 02: <BODY> 03: <CENTER> 04: ShowMessage Applet 05: <HR> 06: <APPLET 07: CODE=ShowMessage.class 08: WIDTH=100 09: HEIGHT=100> 10: <PARAM NAME="MESSAGE" VALUE="I am a boy!"> 11: </APPLET> 12: <HR> 13: </BODY> 14: </HTML> [소스7] ShowMessage.html ● PingPong 애플릿을 어떻게 개조할까요? HTML로 부터 데이터를 넘겨 받는 방법을 배웠으니, 이제 PingPong 애플릿 을 좀더 실용적으로 바꿔 봅시다. 다음과 같은 기능을 추가시킬 것입니다. ① RADIUS 변수를 통해 공의 반지름을 넘겨줄수 있도록 한다. ② XSPEED, YSPEED 변수를 통해 한 번에 공이 이동하는 거리를 넘겨 주도 록 한다. ③ DELAY변수를 통해 PingPong애플릿이 쉬는 시간을 조정할 수 있도록 한 다. 우선 알아야할 내용을 정리한후, 소스를 보도록 하겠습니다. ◇ String 값을 int 값으로 바꾸는 방법 getParameter 메소드를 통해 HTML로 부터 받아오는 값은 String 형태 의 값들 입니다. 반지름이나 속도, 지연시간등은 String 값이 아닌 int 값이어야 하므로, 이 값들을 PingPong 애플릿에서 사용하려면 int 형으 로 바꿔야 합니다. <Tip!> (Integer.valueOf(getParameter("변수명"))).intValue(); String을 int로 바꾸기 위해서는 위와 같은 방법을 사용하면 됩니다. 다소 복잡해 보이지만 풀어서 보면 크게 복잡하지는 않습니다. getPar- ameter 메소드로 받아온 변수값을 String형에서 Integer 클래스의 val- ueOf메소드를 이용해 Integer 형으로 변환합니다. 다음으로 Integer 클 래스의 메소드인 intValue메소드로 다시 int 값으로 변환 하면 됩니다. ● 완벽한 PingPong 애플릿 지금까지 배운 지식을 총동원 하여 만들어낸 완벽한 PingPong애플릿의 소 스가 아래에 있습니다. [소스5] 의 PingPong2.java와 거의 대부분의 코드가 동일 하고, HTML 로부터 몇몇파라메터들을 읽어와서 적용하는 부분만 추가 되었습니다. [소스9]는 완성된 PingPong 애플릿을 테스트 하기위한 HTML 파 일 소스입니다. 001: import java.awt.*; 002: import java.applet.*; 003: import java.lang.Runnable; 004: 005: public class PingPong extends Applet implements Runnable 006: { 007: Thread trdMyThread;// 애플릿의 주 쓰레드 008: 009: Image imgOffScr; // 메모리 버퍼로 쓸 Image 010: Graphics grpOffScr; // 메모리 버퍼의 Graphics 핸들 011: 012: int ballXp, ballYp, ballXs, ballYs; 013: int ballRad, delay; 014: 015: public void init() 016: { 017: resize(400, 400); 018: } 019: 020: public void start() 021: { 022: // 아직 쓰레드가 만들어져 있지 않으면 새로 만드시오 023: if (trdMyThread == null) 024: trdMyThread = new Thread(this); 025: 026: // 메모리버퍼가 만들어져 있지 않다면 400x400의 크기로 027: // 만드시오 028: if (imgOffScr == null) 029: imgOffScr = createImage(400, 400); 030: 031: // 메모리 버퍼에 대한 Graphics 핸들을 얻어 온다 032: grpOffScr = imgOffScr.getGraphics(); 033: 034: ballXp = 100; 035: ballYp = 200; 036: ballXs = 037: (Integer.valueOf(getParameter("XSPEED"))).intValue() 038: ballYs = 039: (Integer.valueOf(getParameter("YSPEED"))).intValue() 040: ballRad = 041: (Integer.valueOf(getParameter("RADIUS"))).intValue() 042: delay = 043: (Integer.valueOf(getParameter("DELAY"))).intValue(); 044: 045: // 만들어진 쓰레드를 활성화 시킨다 046: trdMyThread.start(); 047: } 048: 049: // 쓰레드의 주 실행 루틴 050: public void run() 051: { 052: // 무한 루프가 쓰레드 안으로 옮겨졌다! 053: while (true) { 054: // paint를 한번 불러준다 055: repaint(); 056: // 0.1초간 쉬고 057: try { 058: Thread.sleep(delay); 059: } catch (InterruptedException ie) {} 060: 061: // 공을 옮긴다 062: ballXp += ballXs; 063: ballYp += ballYs; 064: 065: // 벽에 부딪혔으면 방향을 반전 066: if (ballXp > 400 || ballXp < 0) 067: ballXs = -ballXs; 068: if (ballYp > 400 || ballYp < 0) 069: ballYs = -ballYs; 070: } 071: } 072: 073: public void paint(Graphics g) 074: { 075: // 메모리 버퍼를 까맣게 지우고 076: grpOffScr.setColor(Color.black); 077: grpOffScr.fillRect(0, 0, 400, 400); 078: 079: // 메모리 버퍼에 공을 그린다 080: grpOffScr.setColor(Color.white); 081: grpOffScr.fillArc( 082: ballXp, ballYp, ballRad, ballRad, 0, 360); 083: 084: // 완성된 그림을 화면에 뿌린다 085: g.drawImage(imgOffScr, 0, 0, this); 086: } 087: 088: public void update(Graphics g) 089: { 090: paint(g); 091: } 092: 093: public void stop() 094: { 095: // 쓰레드가 살아 있다면 활동을 중지시키고 096: // 없애버린다 097: if (trdMyThread != null) { 098: trdMyThread.stop(); 099: trdMyThread = null; 100: } 101: } 102: } [소스8] PingPong.java 01: <HTML> 02: <BODY> 03: <CENTER> 04: PingPong Applet 05: <HR> 06: <APPLET 07: CODE=PingPong.class 08: WIDTH=400 09: HEIGHT=400> 10: <PARAM NAME="RADIUS" VALUE="30"> ← 공의 반지름을 30으로 11: <PARAM NAME="XSPEED" VALUE="2"> ← 가로축 속도를 2 12: <PARAM NAME="YSPEED" VALUE="3"> ← 세로축 속도를 3 13: <PARAM NAME="DELAY" VALUE="10"> ← 지연시간을 10밀리초로 14: </APPLET> 15: <HR> 16: </BODY> 17: </HTML> [소스9] PingPong.html

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

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

 

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

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

1.3 절을 다 올릴려구 했는데, 너무 길어져서 도저히 안되겠군요. 근데, 혹 시 GIF로 그려놓은 설명용 그림을 쉽게 TEXT로 바꿀 수 있는 방법이 없을까 요? 설명용 그림을 대부분 생략하고 넘어가고, 글의 내용을 바꿔 버리기는 하고 있는데, 꼭 필요한 그림들도 있어서 뺄 수 도 없고... 일일히 그리자 니 너무 노가다틱 하군요.. 저에게 이런 고통을 안겨준 애아범 호진 대삽님이 미울 따름이에요 ... T_T * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. 1.3 PingPong 애플릿을 만들어 봅시다. 이번에 우리가 만들게 될 PingPong애플릿은 화면의 애플릿 영역내에서 공 하나가 좌충우돌 튀어 다니는 것입니다. 이번 절을 통해 우리는 Java에서 어떻게 애니메이션(Animation)을 구현하는 지를 배울 수 있을 것입니다. 1.3.1 PingPong 애플릿의 설계 일단 화면상에 공이 튀어 다니는 것을 목표로 하고, 프로그램의 구조같은 것은 추후에 신경을 쓰도록 하겠습니다. PingPong 애플릿이 해야할 일을 대 충 정해보면 아래와 같습니다. ① 화면을 깨끗하게 지운다. ② 이전에 그려졌던 공을 지운다. ③ 화면에 공을 그린다. ④ 공을 임의의 방향으로 움직인다. ⑤ 공이 벽에 충돌했는가? 예) ⑥번으로 이동 아니오) ⑦번으로 이동 ⑥ 공의 충돌 방향에 따라 X, Y 방향의 속도를 조절한다. ⑦ ②번으로 이동한다. 1.3.2 첫 번째 PingPong 애플릿 첫 번째 PingPong 애플릿은 프로그램의 구조가 상당히 좋지 않습니다. 어 떤점이 좋지 못한지는 소스를 보면서 설명하도록 하고, 우리가 해야할 일은 이 좋지못한 PingPong 애플릿을 하나 하나 고쳐나가면서 Java AWT 프로그래 밍에 능숙해지고, Java 애니메이션에 대한 기법들을 배워 나가는 것입니다. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class PingPong1 extends Applet 05: { 06: int ballXp, ballYp, ballXs, ballYs; 07: int oldBallXp, oldBallYp; 08: 09: public void init() 10: { 11: resize(400, 400); 12: } 13: 14: public void start() 15: { 16: ballXp = 100; 17: ballYp = 200; 18: ballXs = 1; 19: ballYs = 1; 20: 21: repaint(); 22: } 23: 24: public void paint(Graphics g) 25: { 26: g.setColor(Color.black); 27: g.fillRect(0, 0, 400, 400); 28: 29: while (true) 30: { 31: g.setColor(Color.black); // 이전의 공을 지운다 32: g.fillArc(oldBallXp, oldBallYp, 25, 25, 0, 360); 33: g.setColor(Color.white); // 새로운 공을 그린다 34: g.fillArc(ballXp, ballYp, 25, 25, 0, 360); 35: oldBallXp = ballXp; // 이전 공의 좌표를 기억한다 36: oldBallYp = ballYp; 37: ballXp += ballXs; // 공을 이동시킨다 38: ballYp += ballYs; 39: // 벽에 공이 부딪혔으면 속도를 반전시킨다 40: if (ballXp > 400 || ballXp < 0) 41: ballXs = -ballXs; 42: if (ballYp > 400 || ballYp < 0) 43: ballYs = -ballYs; 44: } 45: } 46: } [소스3] PingPong1.java ● PingPong1 애플릿에 대한 설명 PingPong1 애플릿의 구조는 HelloWorld 애플릿과 거의 같습니다. 다만 몇 몇 데이터들이 추가 되고, 각각의 메소드들이 좀 더 많은 일들을 하는것에 불과 합니다. 우선 추가된데이터 멤버(Data Member)들에 대해서 설명 하겠 습니다. ◇ ballXp, ballYp : 현재 공의 위치를 나타냅니다. ◇ ballXs, ballYs : 공의 X, Y 축으로의 속도를 나타냅니다. ◇ oldBallXp, oldBallYp : 바로 이전에 공이 있던 위치를 나타냅니다. 다음으로 각각의 메소드들에 대해서 살펴 봅시다. 우선 init() 메소드는 애플릿의 크기를 400x400으로 조절하고, start() 메소드는 데이터 멤버들을 초기한후 repaint() 메소드들 호출하여, paint() 메소드를 부르게 됩니다. paint() 메소드는 우선 26번 라인에서 브러시(Brush) 를 검은색으로 정하 고, 27번 라인에서 애플릿 영역 전체를 덮는 속이 꽉찬 사각형을 그려서 애 플릿 영역을 지우게 됩니다. 29번 라인을 보면 무조건 참인 while() 루프를 볼 수 있는데 이것은 무한 루프입니다. while() 루프는 조건식이 거짓일 때 루프를 탈출하게 되므로, 여기에서는 하늘에서 벼락이 쳐서 컴퓨터를 맞춰 메모리 내부의 'true' 값이 'false'로 바뀌지 않는한, 'true'가 'false'로 바뀔리 없으므로 무한루프가 되는 것입니다. (혹시 이런 경우가 생겨 무한 루프에서 탈출하는 일을 겪은 분이 계시면 저에게 연락 바랍니다) 루프의 내용을 살펴보면 앞절에서 제시한 순서에 따라 진행되고 있을 것입니다. ☞ 새로 등장한 것들 이번 단원 부터는 AWT에 대한 새로운 개념들이 하나 둘씩 등장하므로, 이 들에 대한 설명을 각 단원의 "☞ 새로 등장한 것들"이라는 제목의 소단원에 서 설명하겠습니다. 단, AWT와 관련이 없는 Java 의 일반 문법에 해당하는 내용들은 이 강좌의 내용과 맞지 않으므로, 여기에서 다루지 않겠습니다. ◇ Graphics 객체 paint() 메소드의 인자로 넘겨지는Graphics 객체에 대해서 알아보겠 습니다. Graphics 객체는 AWT 요소(Component)가 화면상 또는 메모리속 에서 차지하고 있는 사각형의 영역을 뜻합니다. 지금까지 배워온 paint (Graphics g)에서의 'g' 는 현재 paint() 메소드가 속해 있는 애플릿이 화면상에서 차지하고 있는 영역을 뜻하는 것입니다. AWT가 화면상에 무 엇인가를 출력하기 위해서 행하는 모든 행동은 이 Graphics객체를 통해 서만 이루어 지며, Graphics 객체는 AWT가 필요로 하는 거의 모든 메소 드들을 가지고 있습니다. 우리가 게임에서 주로 사용할 여러 가지 메소 드들은 결국 Graphics 클래스 내부에 존재하는 메소드들인 것입니다. (다른 곳에서는 Graphics 객체를 Device Context라고 부르기도 합니다. 왜냐하면 Graphics 객체가 일종의 장치핸들이기 때문입니다.) ◇ Graphics.setColor(Color c) [소스3]의 26번 라인에서 볼 수 있는 setColor(Color c) 메소드는 G- raphics 객체의 브러시 색상을 정하게 됩니다. 여기에서 브러시(Brush) 란 Graphics 객체가 자신의 영역에 그림을 그릴 때 사용하는 붓을 뜻합 니다. 우리가 도화지에 붉은색 줄을 긋고 싶으면 붉은 색연필이나, 크 레용을 들 듯이 Graphics 객체도 그림을 그리기 이전에 어떤 붓으로 그 림을 그릴지, 어떤 색상으로 그림을 그릴지등을 설정해 주는 것입니다. ◇ Graphics.fillRect(int x, int y, int w, int h) fillRect() 메소드는 속이꽉찬 사각형을 그립니다. 이 메소드는 주로 화면을 지우거나 이전의 그림을 지우는 등의 작업을 할 때 사용하게 될 것입니다. 사각형의 색상은 setColor() 메소드에서 설정된 색상을 사용 합니다. ◇ Graphics.fillArc(int x, int y, int xr, int yr, int sAg, int aAg) 이 메소드는 속이꽉찬 부채꼴을 그리는데 사용됩니다. (x, y)를 중심 으로 가로반지름 xr, 세로반지름 yr의 부채꼴을 시작각(sAg) 부터 지정 된각도(aAg)만큼의 부채꼴을 그립니다. 만약 화면에 원을 그리고 싶다 면 [소스3] 에서 처럼 시작각을 0 으로 하고, 부채꼴의 각도를 360으로 정해주면 됩니다. ● 무엇이 문제인가요? 첫 번째 PingPong 애플릿을 만들고 실행해 보았습니다. 상당히 실망이 큰 분들도 계실 것입니다. 결과는 번쩍번쩍 이는 공과 번쩍번쩍 이는 마우스커 서, 그리고 애플릿 영역을 다른 윈도우로 덮은후 다시 나오게 한 다음의 황 당함 등등, 아직 개선해야할 여러 문제들이 남아 있습니다. 어째서 이러한 문제점이 생기게 되었을까요? 하나 하나 짚어 보도록 합시다. ◇ 번쩍번쩍이는 공 이 문제점의 원인은 공을 그리고, 지우는 작업이 우리가 보는 화면내 에서 일어 난다는데 있습니다. [소스3]의 31-34라인에서 이루어지는 작 업을 우리가 볼 수 있는 화면내에서 작업함으로서('g' 객체가 화면상에 서 애플릿이 차지하고 있는 공간에 대한 핸들이므로) 공을 지우고, 다 시 그리는 작업이 눈에 보이게 되고, 이로 인해 심한 깜박임이 발생하 는 것입니다. 이것을 해결하기 위해서는 여러 가지 방법이 있으나, 가 장 좋은 방법은 안보이는데서 작업을 한 후 완성된 이미지만 보여 주는 것이겠습니다. ◇ 번쩍번쩍이는 마우스 커서 애플릿 때문에 마우스 커서가 상당히 고생을 하는 것 같습니다. 이같 은 문제점이 발생하는 이유는 애플릿이 CPU를 너무 많이 점유하기 때문 에 발생하게 됩니다. paint 메소드는 원칙적으로 자신의 영역을 갱신하 고 제어권을 넘겨 줘야만 합니다. 하지만, 우리의 PingPong1의 paint메 소드는 무한루프를 내부에 가지고 있고, Web Browser를 끝내거나, 다른 홈페이지로 이동하지 않는한 paint 메소드 속에서 무한이 공을 새로 그 리고, 지우고 하는 작업을 반복할 것입니다. 이 때문에 마우스 커서가 심하게 번쩍이게 되는 것입니다. 이를 해결하기 위해서는 Thread를 이 용한 프로그래밍을 해야만 합니다. ◇ 다른 윈도우로 덮여져 있던 애플릿 영역이 복구가 안되는군요! 예, 그렇습니다. 덮여져 있던 영역은 우리의 PingPong1 애플릿에서는 도저히 복구될 수 없습니다. 애플릿의 영역을 덮고 있던 다른 윈도우가 사라짐과 동시에 PingPong1 애플릿에게는 repaint메시지가 발생하게 됩 니다. repaint 메시지는 paint메소드를 호출하게 되겠죠. 하지만, Pin- gPong1 의 paint메소드는 영원히 빠져나올수 없는 무한루프속에서 열심 히 공을 튕기고 있으므로, 운영체제에서 내리는 명령 조차 무시 하면서 공튕기기에만 열중할 것입니다. 이것의 해결 방법 역시 Thread를 사용 해 paint메소드 내부의 무한루프를 없애는 것입니다. 1.3.3 개선된 PingPong 애플릿 앞 단원에서 살펴본 바와 같이 PingPong1 애플릿에는 몇가지 문제점이 발 견되었습니다. 이제 이것들을 수정해 보도록 하겠습니다. ● Multi-Thread 의 구현 Java는 java.lang.Thread라는 패키지에 쓰레드 클래스가 정의되어 있습니 다. 이것을 상속받으면 멀티 쓰레드 버전의 PingPong 을 만들 수 있겠군요! 라고 생각할 수 있으나, Java는 애석하게도 다중상속이 허용되지 않습니다. PingPong은 이미 Applet으로 부터 파생된 클래스이므로 Thread를 같이 상속 받을수는 없는 것입니다. 그렇다면 방법이 없는 것일까요? 그렇지는 않습니 다. Java를 만드신 창조주님들께서 클래스로 부터의 다중상속은 허용하지 않았지만, implements라는 문장을 통해 다수의 인터페이스 (Interface : 클 래스의 축소판이라고 보시면 됩니다, 자세한 내용 설명은 이 강좌의 설명범 위를 벗어나므로, Java 입문서를 참고하시기 바랍니다)로 부터의 다중 상속 은 허용해 주셨습니다. 이제 그 사용법을 알아보도록 합시다. ◇ Runnable Interface Runnable Interface는 java.lang.Thread를 인터페이스화 시켜놓은 것 입니다. 우리는 Runnable 인터페이스를 PingPong 애플릿의 조상(완전한 조상이라고 보기엔 좀 그렇습니다만...)으로 둠으로써 Multi-Thread 기 반의 PingPong 애플릿을 만들 수 가 있을 것입니다. ◇ Runnable Interface를 포함한 애플릿의 기본골격 import java.awt.*; import java.Applet.*; import java.lang.Runnable; public class 애플릿이름 extends Applet implements Runnable { Thread myThread; // 애플릿 쓰레드 public void init() { // 애플릿이 최초 로드되어서 해야할 일들 } public void start() { // 애플릿의 실행이 시작될 때 해야할 일들 // 아직 쓰레드가 생성되어 있지 않다면 새로운 // 쓰레드를 생성하고 쓰레드를 활성화 시킨다 if (myThread == null) { myThread = new Thread(this); myThread.start(); } } public void run() { // 쓰레드의 메인 루틴 } public void paint(Graphics g) { // 애플릿이 갱신되거나 새로 그려져야할 때 해야할 일들 } public void stop() { // 애플릿의 실행을 중지할 때 해야할 일들 // 쓰레드가 실행중이면 이를 중지시키고, // 메모리에서 제거시킨다 if (myThread != null) { myThread.stop(); myThread = null; } } public void destroy() { // 애플릿을 메모리에서제거할 때 해야할 일들 } } [표3] Runnable 인터페이스를 포함한 애플릿의 기본구조 [표3]에서 볼 수 있듯, Runnable 인터페이스를 포함한 애플릿의 구조 는 저번 강좌에서 봤던 [표1]의 애플릿의 구조와 거의 같습니다. 몇가 지 추가된 사항으로 Thread형 데이터 멤버 한 개와, start, stop메소드 내부에 if 문, 그리고 run 이라는 새로운 메소드가 추가된 것 정도입니 다. 이들에 대한 자세한 설명은 PingPong2 애플릿의 설명에서 함께 하 겠습니다. ● Double Buffering 의 구현 공이 번쩍이는 것을 해결하기 위해서는, 보이지 않는 곳에서 작업을 하는 것이 제일이라고 했습니다. 보이지 않는 곳에서 작업을 하는 알고리즘을 흔 히 게임프로그래밍에서 "더블버퍼링"이라고 부릅니다. 이제 "더블버퍼링"이 라는 것을 배워 보도록 하겠습니다. ◇ 더블버퍼링이란 무엇인가요? 더블버퍼링이라는 것은 게임이나, 컴퓨터 애니메이션등에서 화면에서 바로 갱신을 할 경우, 화면상에 일어나는 깜박임을 제거하기 위하여 메 모리 속의 임시 영역에서 작업을 하여, 완성된 이미지를 화면에 출력하 는 방식을 말합니다. (이때 고속으로 화면상에 전송되므로 사용자가 깜 빡임을 거의 느낄 수 없습니다) ◇ 좋군요, 그런데 어떻게 더블버퍼링이라는 것을 구현할 수 있습니까? Java 는 Image 클래스를 제공합니다. Image 클래스는 게임의 그래픽 데이터(캐릭터나 배경등)를 저장하기 위해 많이 사용되는데, 이외에도 더블버퍼링을 위한 버퍼역할을 할 수 도 있습니다. 더블 버퍼링의 알고 리즘은 아래의 그림과 같습니다. 그림 보기 ◇ 못알아 듣겠습니다. 실제로 소스를 보여주십시오. 백문이불여일견! 소스를 보도록 하십시오. 01: Image imgOffScr; 02: Graphics grpOffScr; 03: 04: public void start() 05: { 06: imgOffScr = createImage(100, 100); 07: grpOffScr = imgOffScr.getGraphics(); 08: } 09: 10: public void paint(Graphics g) 11: { 12: grpOffScr.drawString("안녕하세요?", 10, 10); 13: g.drawImage(imgOffScr, 0, 0, this); 14: } [소스4] 더블버퍼링의 구현 더블 버퍼링을 이용하여 화면에 "안녕하세요?"라는 문자열을 찍는 애 플릿의 일부분 입니다. 소스의 내용을 살펴보면 start메소드에서 임시 버퍼를 만들고 있습니다. 6라인의 createImage메소드는 주어진 크기(가 로, 세로)의 이미지를 메모리속에 생성시키는 역할을 합니다. 7라인의 getGraphics메소드는 Image객체의 멤버로서, Image자신에 대한 핸들을 Graphics 객체형태로 되돌려 줍니다. 이 Graphics 객체를 이용하여 메 모리버퍼에서 그림을 그리는 작업을 하게 되는 것입니다. 12번 라인을 보면 7번라인에서 얻은 Graphics 객체를 이용해 메모리 버퍼에 "안녕하 세요?"라는 문자열을 (10, 10)의 좌표에 출력하고 있습니다. 다음으로 13번 라인에서 메모리 버퍼를 실제 화면으로 옮기는 작업을 하게 되고, 결국 우리는 완성된 이미지를 볼 수 있게 되는 것입니다.

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

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

 

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

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

안녕하십니까? 친소맨 김명현입니다. 이미 만들어 놓은 문서를 새로 편집하
는것도 장난이 아니군요.. <장장 한시간이나 칸맞추고, 줄그리고, 말바꾸고
등등 난리를 쳤어요> 1 회 분량으로 꽤 많은 내용이 될지 모르겠는데, 다음
부터는 이거보다 더 많아질것 같아서 걱정이네요 :) 잘쓴 문서는 아니지만,
초보자나, 자바 게임프로그래밍에 관심이 있으신 분들에게 도움이 되었으면
좋겠군요...

  * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다.
  * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이
    되지 않을 수 있습니다.


1. 기본적인 AWT(Abstract Windows Tookit) 사용법을 배워봅시다.

1.1 AWT란 무엇인가요?
  AWT는 Java에서 지원되는 기본적인 GUI 컴포넌트들의 집합입니다. Java는
여러 가지 다른 기종들 사이에서 실행될 수 있도록 고안되어졌으므로, 어느
한가지 운영체제에 맞는 GUI라이브러리를 가지고 있을 수 는 없습니다. AWT
는 OS 차원의 GUI 컴포넌트들을 추상화 시키고, 이들에 대해 동일한 인터페
이스를 통하여 접근 통제할 수 있도록 만들어져 있습니다.   때문에 각각의
운영체제마다 가지고 있는 독특한 GUI 요소들을 사용할수 는 없게 되었지만
GUI 운영체제들이 가지고 있는 일반적 요소들을 거의 대부분 포함하고 있으
므로, Java 프로그래머들이 기종을 가리지 않고 실행될 수 있는 GUI 프로그
램을 만들수 있게 합니다.

  우리가 앞으로 공부하게 될 Game Programming 도 그래픽 환경에서 수행되
는 것이 대부분이므로, 필연적으로 AWT를 사용하게 될 것입니다. Java 에서
그래픽 프로그래밍은 AWT 를 빼놓고는 이야기 할 수 없는 것이기 때문이죠.
대부분의 Java프로그래머들이 AWT 의 사용법에 대해서 잘 알고 있을 것이라
생각은 되지만, 몇가지 AWT에 대한 예제들을 통해 자신의 실력을 확인해 보
고 넘어가는 것도 좋을 것입니다.

1.2 AWT용 "Hello, world!"를 만들어 봅시다.
  C언어 시절부터 전통적으로 최초의 프로그램은 "Hello, world!"라는 문장
을 화면에 출력하는 예제였습니다. 우리도 이 아름다운(?) 전통을 이어나가
기 위해 최초로 만들게 되는 프로그램으로 "Hello, world!"를  화면 중앙에
출력하는 것으로 하게 될 것입니다.

● Applet기반의 Hello, world!

  독립적인 프레임(Frame) 윈도우를 가지는  AWT 프로그램은 나중에 기회가
닿으면 배우기로 하고, 우선 이해하기 쉽고 프로그램이 간단한 Applet 기반
의 "Hello, world!"를 만들어 보도록 하겠습니다. 소스를 먼저 보고 설명을
하도록 하겠습니다.

01: import java.awt.*;                                            
02: import java.applet.*;                                         
03:                                                               
04: public class HelloWorld extends Applet                        
05: {                                                             
06:     public void init()                                        
07:     {                                                         
08:         resize(100, 100);                                     
09:  }                                                         
10:                                                               
11:     public void start()                                       
12:     {                                                         
13:         repaint();                                            
14:     }                                                         
15:                                                               
16:     public void paint(Graphics g)                             
17:     {                                                         
18:         g.drawString("Hello, world!", 20, 45);                
19:     }                                                         
20: }                                                             
                      [소스1] HelloWorld.java

  너무나 간단한 소스라서  설명할 것도 없을 것 같습니다만,  처음 보시는
분들을 위해 간단히 설명을 하도록 하겠습니다. 여러분들이 Java의 기초 문
법은 알고 있다는 가정하에 설명을 해나갈 것이므로  중간에 모르는 부분이
나오면 과감하게 입문서 등을 참고하시기 바랍니다.

● Applet의 기본적인 구조

  애플릿(Applet)은 Web Browser에 의해 인터프리팅되는 형태의 프로그램을
말합니다. 즉, 단독 실행은 불가능 하고, HTML 내에 삽입되어져 홈페이지와
함께 보여지는 형태로 실행됩니다.  애플릿은 다음과 같은 기본적인 구조를
가지고 있습니다.

  import java.awt.*;                                              
  import java.Applet.*;                                           
                                                                  
  public class 애플릿이름 extends Applet                          
  {                                                               
      public void init()                      
      {                                                           
          // 애플릿이 최초 로드되어서 해야할 일들                 
      }                                                           
                                                                  
      public void start()                                         
      {                                                           
          // 애플릿의 실행이 시작될 때 해야할 일들                
      }                                                           
                                                                  
      public void paint(Graphics g)                               
      {                                                           
          // 애플릿이 갱신되거나 새로 그려져야할 때 해야할 일들   
      }                                                           
                                    
      public void stop()                                          
      {                                                           
          // 애플릿의 실행을 중지할 때 해야할 일들                
      }                                                           
                                                                  
      public void destroy()                                       
      {                                           
          // 애플릿을 메모리에서 제거할 때 해야할 일들            
      }                                                           
  }                                                               
                                                                  
                      [표1] 애플릿의 기본구조

  애플릿은 AWT기반의 프로그램입니다.  Web Browser가 GUI기반의 프로그램
임을 감안할 때 당연한 것이라 할 수 있겠습니다. 그러므로 애플릿은 AWT패
키지를 import해야 합니다.(import는 C언어에서 include와 비슷한 개념입니
다) 또, 우리의 애플릿은 기본적인 애플릿 뼈대에서 파생되는 서브클래스로
구현이 되므로,  기본적인 애플릿의 내용을 담고 있는  java.applet.Applet
패키지도 import 해야 할 것입니다. (제일 위에 두줄은 무조건 적어줘야 한
다고 생각하시는게 편할껍니다)
  다음으로 위의 [표1]에서 볼 수 있듯 애플릿은 기본적으로 5개의 내부 메
소드를 가지고 있습니다. 이것 외에도 여러 가지의 메소드를 가지고 있습니
다만, 그런것들은 차차 배우기로 하고 이 절에서 필요한 것들만 알아보도록
하겠습니다.

  ◇ public void init() 메소드
      이 메소드는 Web Browser나 AppletViewer에 의해  애플릿이 시스템으
    로 로드된후 최초로 불려지게 됩니다.  init()메소드는 최초로 start()
    메소드가 실행되기전에 반드시 실행됩니다.

  ◇ public void start() 메소드
      이 메소드는 Web Browser나 AppletViewer에 의해  애플릿이 실행되는
    때에 불려지게 됩니다. 위에서 말했듯이 init()메소드가 실행된후 불려
    지게 되고, 이미 방문했던 홈페이지를 다시 방문할 때 init() 메소드가
    아닌 start() 메소드 부터 불려지게 됩니다. (여기에서 이미  방문했던
    홈페이지라는 것은 아주 매우 오래전에 방문하여 브라우져 캐시에 겨우
    남아 있을 법한 홈페이지를 말하는 것이 아니라, 몇번 이전 페이지버튼
    을 눌러서 갈 수 있는 정도의 페이지를 말합니다. 다시 말하자면, 애플
    릿 인스턴스가 메모리에서 완전히 제거되기 이전의 상태인  홈페이지를
    말합니다.)

  ◇ public void stop() 메소드
      이 메소드는 Web Browser나 AppletViewer에 의해 애플릿의 실행이 중
    단되는 때에 불려지게 됩니다. 보통 현재 애플릿이 포함되어 있는 홈페
    이지에서 다른 홈페이지로 이동할 때 불려지며, Web Browser나 Applet-
    Viewer를 종료할때에도 destroy() 메소드에 앞서 이 메소드가 불려지게
    됩니다.

  ◇ public void destroy() 메소드
      이 메소드는 Web Browser나 AppletViewer가 현재 실행중인  애플릿의
    수행을 완전히 종료시키고, 애플릿을 위해 할당한 자원(Resource)을 반
    환시키는 등의 작업을 하게 됩니다. 물론 이 메소드도 자신이 수행되기
    이전에 stop()메소드를 먼저 수행하게 됩니다.

  ◇ public void paint(Graphics g) 메소드
      이 메소드는 원래 애플릿의 메소드가 아니라,  애플릿의 까마득한 조
    상인 Component라는 클래스의 메소드입니다. 애플릿의 족보를 살펴보면
    Component 클래스로부터 파생되어 Container 를 거치고, Panel 을 이어
    받아 Applet까지 오게 되는데,  이런 연유로 인해 paint 메소드가 애플
    릿에 포함되게 된 것입니다.  이 메소드가 하는 일은 이름 그대로 현재
    자신의 영역을 새로히 그리는 일을 하게 됩니다. 예를들어 애플릿의 영
    역위에 MS-Windows의 계산기가 나타났다가 없어졌다면 애플릿의 상당부
    분이 계산기에 의해 지워졌을 것입니다. 이럴 때 Web Browser나 Apple-
    tViewer는 paint()메소드를 호출하여 애플릿 영역을 새로그리도록 합니
    다.

  이상으로 애플릿의 기본적인 구조에 대해 알아 보았습니다. 이제 다시 본
론으로 돌아가서 [소스1]에 대한 분석을 해보도록 하겠습니다.  (너무 오래
전 일이라 소스가 기억나지 않으시는 분들은 다시 앞으로 돌아가 천천히 읽
어보시기 바랍니다. 좀전에 알아본 애플릿의 기본구조 덕택에 좀더 많은 부
분을 이해하실 수 있을지도 모릅니다)

  우선 1-2번 라인은 AWT 패키지와, Applet 패키지를 import 시키는 것입니
다. 다음으로 4번 라인을 살펴보면 HelloWorld라는 이름의 클래스를 Applet
으로 부터 파생시키는 것을 알 수 있을 것입니다.  이제 HelloWorld는 애플
릿의 일가족이 되었습니다.

  6-9번 라인까지는 init() 메소드에 대해서 정의되어 있습니다.  여기에서
하는 일은 애플릿의 사이즈를 100x100으로 조정하는 일입니다. resize() 메
소드 역시 애플릿의 여러 가지 메소드 중 하나이며,  애플릿 영역의 크기를
조절하는 역할을 합니다.

  11-14번 라인은 start()메소드에 대해서 정의되어 있는데, 별로 하는일은
없어 보입니다. 단순히 repaint()라는 메소드를 한 번 불러주는군요. repa-
int() 라는 메소드는 paint() 메소드와 마찬가지로 애플릿의 메소드가 아닌
애플릿의 윗대 조상의 메소드 입니다. repaint()가 하는 일은 단순히 pain-
t()메소드를 불러주는 일입니다.  궂이 우리가 repaint()메소드를 불러주지
않더라도 Web Browser나 AppletViewer에서 자동으로 paint( )메소드를 불러
줄 것입니다만, 이 기회를 빌어 repaint()메소드를 설명하기 위해서 한줄을
넣어 보았습니다.

  16-19번 라인까지는 paint()메소드에 대해서 정의되어 있습니다.  내용을
살펴보면

    g.drawString("Hello, world!", 20, 45);

  위와 같습니다. g 라는 객체의 drawString() 메소드를 호출 하는 것인데,
g는 paint(Graphics g)라는 문장을 볼때 Graphics형태의 객체임을 알 수 있
습니다. Graphics객체는 나중에 공부하도록 하고, 위의 문장을 계속 분석해
보면 (20, 45)영역을 좌측 상단으로 Hello, world! 라는 문장을 출력하시오
라고 되어 있으므로 Java는 우리가 시키는 데로 (20, 45)영역에 Hello, wo-
rld! 라고 예쁘게 출력해 줄 것입니다.

  그런데 stop() 이나 destroy() 메소드는 [소스1]에서 보이지를 않습니다.
잘 보셨습니다. [소스1]에서는 별도의 메모리 할당이나  쓰레드를 생성시키
지 않았으므로,  애플릿을 종료할 때 특별하게 해주어야 할 일이 없습니다.
(단순히 화면에 글자 몇 개 출력하는 것으로역할을 다하는 것이죠) 그래서
stop()이나 destroy()메소드는 생략한 것입니다. 사실 init()나 start() 메
소드도 생략가능합니다.  물론 생략했을 때 Web Browser나 AppletViewer 는
HelloWorld의 부모인 Applet의 start(), init(), stop(), destroy() 메소드
들을 호출 할것입니다.

  분석도 끝냈으니 실행을 해봐야겠죠?. 위의 [소스1]을 'HelloWorld.java'
라는 이름으로 저장했다면, 컴파일을 해봅시다.

● 컴파일 방법
  ◇ JDK1.x 에서의 컴파일
     javac 프로그램파일명.java
  ◇ Symantec Cafe 또는 VisualCafe
     javac 프로그램파일명.java
     또는 통합환경에서 소스코드를 불러와서 Compile메뉴를 선택해도 됩니
     다.
  ◇ Microsoft Visual J++
     jvc 프로그램파일명.java
     또는 통합환경에서 소스코드를 불러와서 Compile메뉴를 선택해도 됩니
     다.

  컴파일을 해보면  다른 종류의 언어 컴파일러에 비해 결과가 무척이나 무
뚝뚝 합니다. 에러가 있다는 메시지가 보이지 않으면 실행을 해보도록 하겠
습니다.  (만약 에러가 난다면, 위의 오자가 난것이 없는지 잘 체크 하십시
오.) 아차! 한가지 빼먹은게 있군요.  우리가 만든 프로그램은 애플릿 형태
의 프로그램이 었습니다. 즉, Web Browser나 AppletViewer에 의해 실행가능
한 프로그램이란 이야기이죠. 그러므로 실행에 앞서 Web Browser가 읽을 수
있도록 HTML파일을 우선 만들어 줘야 합니다. 우리가 만들어 줘야할 HTML파
일의 내용은 [소스2]와 같습니다.

01:<HTML>                                                        
02: <BODY>                  
03: <CENTER>                                                      
04: HelloWorld Applet                                             
05: <HR>                                                          
06: 
07:<APPLET  CODE=HelloWorld.class                                     
08:     WIDTH=100                                                 
09:     HEIGHT=100>                         
10: </APPLET>                                                     
11: <HR>                                                          
12: </BODY>                                                       
13: </HTML>  
                                                     
11: <HR>                                                          
12: </BODY>                                                       
13: </HTML>                                                       
    
                   [소스2] HelloWorld.html

  HTML Tag를 자세히 설명하는 것은 이 책의 내용을 벗어나는것이므로, 우
리가 알아야 할 APPLET Tag에 대해서만 설명하도록 하겠습니다. 다른 Tag는
HTML문법에 관련된 책자들을 참고하시기 바랍니다.

  <APPLET                                                         
      CODE={애플릿의 URL}                                         
      CODEBASE={URL}                                              
      WIDTH={애플릿의 가로사이즈}                                 
      HEIGHT={애플릿의 세로사이즈}                                
      ALIGN={left, right, center}>                                
  <PARAM NAME={변수명1} VALUE={변수값1}>                          
  <PARAM NAME={변수명2} VALUE={변수값2}>                          
  <PARAM ...>                                                     
  <BLOCKQUOTE>                                                    
  {애플릿이 로드되는 동안 출력될 임시 HTML Tag}   
  </BLOCKQUOTE>                                                   
  </APPLET>                                                       


                   [표2] APPLET Tag의 기본 형식

 ◇ CODE={애플릿의 URL}
      애플릿이 위치하고 있는곳을 URL(Uniform Resource Location) 형태로
    기술해 줍니다.  애플릿이 꼭 HTML 파일이 있는 곳에 위치할 필요는 없
    으므로, 해당 애플릿이 있는 URL을 기술하도록 되어 있습니다. [소스2]
    에서는 단순히 애플릿의 파일명만 적혀 있는데,  이것은 애플릿이 있는
    위치와 HTML 파일이 있는 위치가 같기 때문에 생략할수 있는 것입니다.

  ◇ CODEBASE={URL}
      애플릿이 요구하는 각종 이미지나 사운드 파일들을 어디로 부터 읽어
    올지를 URL 형태로 기술하여 줍니다. CODEBASE를 생략하면 애플릿이 로
    드된 URL을 기본 CODEBASE로 인식합니다.

  ◇ WIDTH={애플릿의 가로사이즈}
      애플릿의 가로크기를 10진수 형태로 기술하여 줍니다.

  ◇ HEIGHT={애플릿의 세로사이즈}
      애플릿의 세로크기를 10진수 형태로 기술하여 줍니다.

  ◇ ALIGN={left, right, center}
      홈페이지 상에서 애플릿의 정렬위치를 설정하여 줍니다.

  ◇ PARAM NAME={변수명} VALUE={변수값}
      홈페이지에서 애플릿으로 어떠한 인수를 넘겨줄 필요가 있을 때 사용
    하게 됩니다. PARAM Tag 를 통하여 넘긴 변수명과 변수값을 애플릿에서
    읽어, 그 값을 이용하여 애플릿의 실행 결과등을 바꿀 수 있게 합니다.

  ◇ BLOCKQUOTE
      애플릿이 로드되는 동안 홈페이지상의  애플릿이 나타날 영역에 대신
    하여 출력될 HTML Tag를 기술해 주면 됩니다. 애플릿의 사이즈가 큰 경
    우 로드에 상당한 시간이 걸리므로 "잠시만 기다리세요"와 같은 문장을
    넣어 주면 기다리는 사람도 좀 덜 지루할 것입니다. (덩치가 있는 애플
    릿의 경우 시스템이 다운된 것 같은 기분이 들정도로 시간이 오래 걸리
    기도 한답니다)

  이제 'HelloWorld.java'를 컴파일 하여 'HelloWorld.class'파일도 생성되
었고, 'HelloWorld.html'파일도 만들었으니 Web Browser를 실행하여 결과를
살펴 보십시오. 실행 결과는 애플릿의 중앙영역에 'Hello, world!' 를 출력
하는 것입니다.

───────────────────────────────────
 DO NOT DISTRIBUTE!                            (C)opyright 1997 MadOx


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

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

+ Recent posts