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

+ Recent posts