출처는 http://www.zdnet.co.kr/builder/dev/web/0,39031700,39129854,00.htm 입니다. 아래의 연재순서에 있는 링크는 잘못된 링크입니다.
[C#과 플래시로 온라인 게임 만들기] ④ 클라이언트 개발 |
|
연재순서 ① 델리게이트 이해 ② 쓰레드 처리 ③ 게임서버 완성 ④ 클라이언트 개발 (끝) |
요즘 대부분의 웹 사이트에 플래시가 많이 이용되고 있다. 플래시의 특징이라고 한다면 적은 용량으로 역동적인 표현을 할 수 있고, 별도의 다운로드 없이 스트리밍 방식으로 볼 수 있다는 점이다. 이를 게임에도 응용해 보면 별도로 다운받아 설치하거나, CD로 설치하는 작업이 필요없는 온라인 게임을 만들 수 있다. 스트리밍 방식이므로 단지 사이트에 접속하기만 하면 게임이 시작되는 것이다. 이러한 방식은 게임 소프트웨어를 다운받아 설치하는 데 어려움을 느끼는 초보자들에게도 쉽게 접근할 수 있는 방법일 것이다. 이제 이를 이용하여 온라인 게임의 클라이언트 부분을 완성해 보자. 플래시MX의 새로운 기능 지난해 플래시MX가 출시되면서 여러 가지 새로운 기능들이 추가됐다. 먼저 그 기능들에 대해 알아보자. 비디오 지원 플래시MX에서는 동영상 기능이 통합돼 플래시 내에서 동영상 제어가 가능하다. MPEG, DV, MOV, AVI 등의 비디오 포맷을 플래시로 가져오면 자체 포맷인 Sorenson Spark 코덱으로 변환해 플레이된다. 또한 비디오 객체에 대한 수정, 크기 조절, 회전, 가울이기, 마스크 작업을 할 수 있어 다양하게 보여줄 수 있다. 이는 게임 동영상을 보여줄 때 유용하게 쓰일 것이다. 이미지와 사운드의 동적 로딩 플래시MX에서는 외부 JPEG 및 MP3 파일을 런타임에 동적으로 로드할 수 있다. 이번 게임에서도 배경음악이 MP3 파일인데 용량이 1MB가 넘는다. 이를 다 받고 플레이하려면 좀 기다려야 하지만, 여기서는 스트리밍 방식도 지원하므로 다운로드하면서 플레이가 가능하다. 새로운 UI 컴포넌트 UI에 많이 사용되는 콤보박스, 스크롤 바, 체크박스 등을 따로 UI 컴포넌트로 만들어서 더 쉽게 이들을 다룰 수 있도록 했다. 새로운 이벤트 핸들러 메쏘드의 추가 기존 플래시의 액션 스크립트를 이용해 프로그래밍하다 보면, 프로그래밍의 전체 구조를 파악하는 데 시간이 오래 걸렸다. 각각 오브젝트의 이벤트에서 일어나는 일은 그 오브젝트 내의 이벤트 핸들러에서 정의해야 하므로 소스 코드를 한 눈에 다 볼 수 없었기 때문이다. 일일이 각 오브젝트마다 그 안에 뭐라고 코딩을 해놓았는지 확인해 봐야만 전체구조를 파악할 수 있었다. 그래서 코딩 분량이 길어질수록 프로그램 파악하기는 어려워지고 수정하기도 어려웠다. 그러나 이제는 모든 코드를 한 곳에서 다 작성할 수 있다. 그것은 이벤트 핸들러를 메쏘드화해서 메쏘드 형식으로 기술할 수 있기 때문이다. 예를 들면 기존에는 버튼을 클릭할 때 대한 행동을 정의하려면 다음과 같이 직접 그 오브젝트에 가서 정의해야 했다. On(release) { trace(“버튼을 눌렀습니다.”); } 그러나 이제는 만약 그 버튼의 인스턴스 이름이 BUTTON이라면 프레임코드에서 다음과 같이 할 수 있다. BUTTON.onRelease = function() { trace(“버튼을 눌렀습니다.”); } 즉 그 오브젝트에 직접 가지 않아도 프레임에서 이와 같이 정의하면 앞과 똑같이 작동한다. 코드를 한 곳에 모아 두느냐, 아니면 각 오브젝트에 관련된 이벤트를 각 오브젝트 코드 내에 기술하느냐는 각각 장단점이 있다. 기존 방식은 각 오브젝트에 기술했으므로 이번에는 코드를 한 곳에 모두 모아둘 것이다. 과연 어떤 방법이 더 가독성이 좋고 유지 보수하기 좋은지는 독자들의 판단에 맡기겠다. 심플 포트리스를 위한 액션 스크립트 플래시로 프로그래밍을 하려면 액션 스크립트(Action script)에 대해 알아야 한다. 이는 자바 스크립트와 유사한 점이 많은데, 그 이유는 둘다 ECMA-262 표준을 기반으로 하는 스크립트 언어이기 때문이다. 아마 자바 스크립트를 다뤄본 경험자라면 쉽게 액션 스크립트도 사용할 수 있을 것이다. 액션 스크립트에 관한 주요사항 몇 가지를 살펴보겠다. 배열 액션 스크립트에서의 배열은 많은 융통성을 가지고 있다. 배열의 크기는 사용자의 요구에 따라 마음대로 늘리고 줄일 수 있으며, 해시 테이블로도 이용이 가능하다. 또한 스택으로 이용이 가능하며, sort, slice, join과 같은 기능도 있다. 다음은 배열의 선언 방법이다. [1] var a = new Array(“apple”, “banana”, “orange”); [2} var a = [“apple”, “banana”, “orange”]; [3] var a = new Array(3); a[0] = “apple”; a[1] = “banana”; a[2] = “orange”; [4] var a = new Array(); a[“one”] = “apple”; a[“two”] = “banana”; a[“three”] = “orange”; [5] var a = new Array(); a.one = “apple”; a.two = “banana”; a.three = “orange”; 이 5가지 방법은 모두 동일하게 과일을 저장하는 배열을 생성하고 있다. 특히 [4]와 [5]를 보면 배열 첨자로 숫자뿐만 아니라, 문자를 쓸 수 있다는 것을 볼 수 있을 것이다. 액션 스크립트에서 처음에 배열 크기를 [3]과 같이 3으로 정해도 나중에 얼마든지 늘일 수 있다. 즉 a[10] = “melon”;과 같은 식을 써도 아무 이상이 없다. 이때 인터프리터는 a[3]∼ a[9]까지는 undefined로 채워준다. 변수 영역 액션 스크립트 프로그래밍을 하다보면 가장 헷갈리는 것이 변수의 scope이다. 이 변수가 어디까지 영향력을 행사하는지 알고 있어도 코딩하다 보면 자주 실수를 하게 된다. 그러므로 이 부분을 확실히 알아둬야 에러없는 프로그램을 만들 수 있다. 다음과 같은 예제를 보자. 1번 타임 라인에 다음과 같이 변수 a에 값을 넣어 보자. var a = 10; 이번에는 2번 타임라인에 ‘Inser Keyframe’ 명령어로 프레임을 삽입하고 다음과 같이 a값을 확인해 보자. trace ( a ); a값이 제대로 나온다. 그러므로 프레임 번호가 다르더라도 같은 타임라인 안에서 정의된 변수는 모두 사용이 가능한 것이다. 그러나 한 가지 주의할 것은 액션 스크립트가 인터프리터 언어이므로 순차적으로 해석을 한다는 것이다. 그러므로 2번 프레임에 var b=20;이라고 선언하고 1번 프레임에서 trace(b);라고 하면 undefined라는 메시지가 나온다. 즉 1번 프레임을 해석하는 순간에는 2번 프레임에 무슨 변수가 있는지 모르므로 undefined가 출력되는 것이다. 여기서 또 한 가지 생각할 점이 있는데 다음과 같은 코드를 1번 프레임에 넣었다고 하자. var a = 10; function display() { trace(b); } 변수 a는 정의하고, 변수 b는 아직 정의하지 않았다. 하지만 display()라는 함수는 b를 출력하는 기능을 한다. 이제 2 프레임에 다음과 같은 코드를 추가해 보자. var b=20; display(); stop(); b값을 정의하고, 1번 프레임에 있는 display()를 호출한다. 이 때에는 b값이 정해져 있으므로, 20이라는 값을 출력한다. 이와 같이 액션 스크립트는 한번 정의된 변수는 같은 타임라인 안에서는 기억하고 있으므로 계속 사용이 가능하다. 한편, 변수는 같은 타임라인이기만 하면 되므로, 레이어에도 상관이 없다. 즉 레이어 2에 var c=30;이라고 정의하고 레이어 3에서 trace(c);를 해도 제대로 된 결과 값이 나온다. 무비클립 변수 영역 플래시는 기본적으로 처음에 하나의 타임라인을 가지고 있지만, 무비클립 오브젝트에 따라 각자의 타임라인을 가지고 있다. 플래시를 처음 시작했을 때 나타나는 타임라인도, 하나의 메인 무비클립 오브젝트의 타임라인이라고 봐도 무방하다. 무비클립 오브젝트들은 각자의 타임라인을 가지고 있으므로 이들끼리는 서로 다른 변수 영역을 갖는다. 그러므로 서로 경로를 확실히 명시해 주어서 사용해야만 한다. 다음과 같은 예제를 보자. 메인 타임라인의 1번 프레임에 사각형 하나를 그리고, 이를 MoveClip로 변환한다. 그러면 이 사각형 MovieClip은 자신만의 타임라인을 가지게 된다. 이곳에 변수 a를 다음과 같이 정의하자. var a=20; 그 다음, 메인 타임라인으로 돌아와서 2번 프레임에 다음과 같은 코드를 기술하자. trace ( a ); a값이 제대로 나올까? 정답은 undefined가 나오게 된다. 메인 타임라인에서는 a가 정의되어 있지 않기 때문에 새로운 변수로 생각하고 undefined를 출력하는 것이다. 이를 제대로 출력하려면 다음과 같이 하면 된다. 먼저 사각형 무비클립에 ‘BOX’라는 인스턴스 이름을 주고 메인 타임라인의 2번 프레임을 다음과 같이 고친다. trace ( BOX.a ); 이렇게 하면 20이라는 제대로 된 값을 출력할 수 있다. 액션 스크립트에는 경로를 지정하기 위해 절대 경로를 위한 _root와 상대 경로를 위한 _parent라는 속성을 지원한다. _root라는 것은 메인 타임라인을 지칭하는 것으로 앞의 표현을 _root를 써서 표현하면 _root.BOX.a가 된다. _parent는 부모를 의미하는 것이다. 만약 메인 타임라인에 var c라는 변수가 있고, BOX에서 이를 참조하려면, _root.c라고 해도 되고 _parent.c라고 해도 된다. 이를 계층구조로 나타내면 <그림 1>과 같다.
지역 변수의 범위 액션 스크립트에는 진정한 전역변수가 없다. 메인 타임라인 안에 선언한 변수도 결국은 메인 타임라인 안에서만 효력을 발휘하기 때문이다. 메인 타임라인 밖에서 이를 참조하려면 앞에서처럼 경로를 명시해줘야만 한다. 액션 스크립트에서는 변수를 쓰는 데 있어 주의할 점이 있는데 그것은 변수를 선언없이 사용할 수 있다는 것이다. 그렇기 때문에 뜻하지 않는 오류가 발생하기도 한다. 그러므로 이들의 관계를 잘 파악해둬야만 한다. <리스트 1>의 예제를 보자. <리스트 1>을 실행하면 어떤 결과가 나올까? 실행 결과는 다음과 같다. ff를 호출하기 전의 변수 값 x : 10 y : 20 z : w : ff 함수 내에서의 변수 값 x : 100 y : 200 z : 300 w : 400 ff를 호출하고 난 후의 변수 값 x : 10 y : 200 z : 300 w : 먼저 ff를 호출하기 전에는 w와 z값을 정의하지 않았기 때문에 아무런 값이 출력되지 않는다. 그 다음에 ff에 들어와서는, 먼저 x를 새로 선언(var)했다. 이는 밖에 있는 x와는 별도로 함수 ff 내에서 쓰기 위한 전용 변수를 선언한 것이다. 따라서 밖의 x와는 전혀 다른 변수이다. 그리고 y라는 변수를 사용했는데, 이것은 외부에 선언된 y변수와 동일한 변수이다. 그리고 z라는 변수를 쓸 때 선언없이 그냥 사용하였다. 그러면 이는 지역변수로 취급이 되지 않고 외부에서 선언한 것과 동일한 효력을 발휘한다. 액션 스크립트가 변수 선언없이 사용할 수 있기 때문에 가능한 일이다. 그러므로 z는 이제부터는 외부에서도 사용할 수 있는 변수가 되었다. w는 var이라는 키워드를 사용하여 지역변수로 선언하였기 때문에 ff내에서만 사용이 가능하다. 이제 ff를 벗어나서 각 변수를 다시 출력해 보면 변한 값들을 알 수 있을 것이다. 이처럼 액션 스크립트는 변수의 선언없이 아무 곳에서든 마음대로 선언하고 쓸 수 있다. 단 변수에 오타가 나더라도 이를 새로운 변수로 인식하기 때문에 코딩시 주의를 해야 한다. 액션 스크립트에서 소켓 통신 기존 액션 스크립트에서 서버와 통신을 하려면, loadVariable() 함수를 이용해 통신을 했다. 하지만 이 방식은 데이터 전송이 끝나면 채널도 끊어버리기 때문에, 지속적인 통신에는 어려움이 있다. 또한 항상 플래시에서 서버로 요청을 하기 때문에 서버 쪽에서 플래시 쪽으로 요청을 할 수가 없었다. 그러나 플래시 5에 와서 XML을 지원하면서부터 XML 데이터를 지속적으로 통신하기 위한 소켓인 XMLSocket이라는 클래스를 지원하기 시작했다. 게임에서 서로의 통신을 위해 XML 데이터를 써야 한다면 데이터를 일일이 파싱하는 데에 따른 오버헤드를 걱정해야만 한다. 게임에서는 속도가 중요하기 때문이다. 그래서 이번 게임에서는 XML Socket을 이용하기는 하되, 데이터는 XML을 이용하지 않는다. 단지, 콤마로 구분되는 스트링 데이터를 이용하여 통신을 할 것이다. 단 이때 주의해야 할 것은 플래시에서는 제로바이트로 구분되는 데이터를 줘야만 XMLSocket이 데이터를 받을 수 있다는 점이다.
즉 문자열의 끝을 나타내는 null 문자(‘\0’)가 데이터 끝에 꼭 있어야만 된다. 이것을 빠뜨리면 플래시는 XML 데이터로 인식하지 못해 데이터를 받지 않는다. 또한 서버 쪽에서 데이터를 보낼 때 UTF8 형식으로 데이터를 인코딩해서 보내야 제대로 된 내용을 받아 볼 수 있다. 그럼 이들의 간단한 예제를 살펴보자. <리스트 2>는 지난 호에 소개한 간단한 서버와 비슷한 예제이다. 지난 호 다른 점은 데이터 전송시 제로바이트를 추가했다는 것과 인코딩을 UTF8 형식으로 했다는 것뿐이다. 이 게임에서는 패킷을 주고받을 때 콤마로 구분된 문자열을 주고 받는데, 처음에는 메시지 종류, 그리고 그 다음에는 메시지 내용을 담고 있다. 이 내용은 지난 달에 이미 소개했기 때문에 그 부분에 대해서는 지난 호를 참고하기 바란다. 그러면 플래시에서 어떻게 통신을 하는지 클라이언트 예제를 보자.
<리스트 3>을 보면 플래시의 XMLSocket을 이용해 소켓 통신을 하고 있다. 메시지를 받는 부분인 handleIncoming이라는 함수를 보면 split이라는 함수를 이용하여 콤마 단위로 토큰을 나누고, 토큰별 의미가 무엇인지 표시하고 있다. 다음은 실행 결과이다. 연결 초기화 성공 연결 성공 첫번째 토큰 : 메세지 종류 두번째 토큰 : 내용 심플 포트리스의 몇가지 알고리즘 이제 이 연재의 마지막 부분이다. 소켓 통신까지 되었으니 게임을 완성하는 일만 남았다. 전체 소스는 ‘이달의 디스켓’으로 제공하니 참고 바란다. 대신 포트리스 게임을 구현하는 데 있어 몇가지 알고리즘을 소개하고 마치겠다. 탱크로 구불구불한 지형 따라가기 게임을 해보면 탱크가 구불구불한 구름 위를 따라서 자연스럽게 움직이는 것을 볼 수 있을 것이다. 이는 플래시의 액션 스크립트 중 무비 클립에 있는 hitTest(x, y, shapeFlag)라는 함수를 이용해 구현했다. 플래시에서의 충돌체크는 전통적인 방법인 사각형 충돌체크 외에도 앞의 메쏘드를 이용하면 한 점에 대한 충돌체크를 할 수 있다. 이를 이용해 탱크의 바닥 좌표와 구름과의 충돌 여부를 탐지해 이동위치를 정하는 것이다. 먼저 <그림 2>를 보자.
<그림 2>를 보면 탱크의 이동에 있어서 두 가지 경우가 있다. 하나는 갈 곳의 지면이 현재보다 아래에 있어 탱크의 y좌표를 증가시키는 경우와 다른 하나는 갈 곳의 지면이 현재보다 위에 있어 탱크의 y좌표를 줄이는 경우가 있을 것이다. 첫 번째 경우는 탱크와 구름의 충돌체크를 하여 충돌을 안 하면 탱크는 구름과 충돌할 때까지 y좌표를 늘려 나간다. 두 번째 경우는 탱크와 구름의 충돌체크를 하여 충돌을 하면 탱크와 구름이 충돌 안할 때까지 y좌표를 줄여 나간다. 이런 식으로 하면 탱크가 구름 위를 자연스럽게 움직이는 것이다. <리스트 4>는 이를 구현한 소스의 일부이다.
포탄의 움직임 포탄은 포물선으로 운동하기 때문에, 우리가 고등학교 때 배운 포물선 운동의 공식을 대입하면 쉽게 포물선 운동을 시킬 수 있다. 먼저 우리가 고등학교 때 배운 포물선 운동의 공식은 다음과 같다. ![]() 여기에서 g는 중력가속도이다. 그런데 이 공식을 쓰면, 한 가지 문제점이 나온다. tan가 90도에서는 무한대의 값을 가지기 때문에, 대포를 수직 방향으로 쏘았을 경우, 이상한 값이 나와버린다. 그래서 x, y를 서로 따로 풀어서 구하는 공식을 사용했다. 다음은 그 공식이다. ![]() x와 y를 분리하여 tan를 제거했다. 따라서 수직으로 대포를 쏘아도 제대로 된 값을 나타내 준다. 그런데 여기서 한 가지 주의할 것은 중력 가속도이다. 현실 세계에서 중력 가속도는 9.8m/s이다. 즉 지구가 밑으로 당기는 힘이 초당 9.8m나 된다는 것이다. 그런데 우리 게임은 픽셀 단위로 나타내는 세계이므로 이를 적절하게 바꿔줄 필요가 있다. 그냥 9.8이라고 쓰면 한 타임당 9.8픽셀씩 당기는 힘이 되므로, 너무 빨라서 미처 올라가지도 못하고 떨어져버린다. 그러므로 이 수치는 낮게 주어야 하는데, 실험 결과 0.04pixel/time 정도가 적당했다. <리스트 5>는 이를 구현한 소스의 일부다.
이제 플래시로 게임을 만들자 지금까지 4회에 걸쳐 C#과 플래시를 이용해 심플 포트리스라는 온라인 게임을 만들어 보았다. 이 온라인 게임을 만드는 데 있어 이 두 플랫폼을 선택하게 된 데에는 크게 두 이유가 있다. 먼저 C#(닷넷)을 이용하게 된 이유는 대용량 서버를 구축하기 위해 Winsock2의 IOCP 기능을 이용해야 하는데, 이를 닷넷 환경에서 구축하면 보다 손쉽게 구현할 수 있기 때문이다. 닷넷에서는 기본적으로 비동기 소켓 통신을 하게 되면, 내부적으로 IOCP 기능을 이용해 보다 쉽게 코딩할 수 있다. 두 번째로 플래시를 선택한 이유는 클라이언트 쪽에서 별도의 프로그램을 다운로드할 필요없이 언제 어디서나 즉석에서 온라인 게임을 할 수 있다는 점 때문에 택하게 되었다. XMLSocket을 지원하면서 소켓 통신이 가능해졌기 때문에 이 소켓을 이용해 서버와 지속적인 연결을 할 수 있는 온라인 게임을 만들 수 있는 것이다. 이번 필자의 강의를 꾸준히 따라온 독자라면 이를 응용해 장기, 바둑, 테트리스, 포트리스, 오목과 같은 간단한 게임을 별도의 다운로드가 필요없는 플래시 게임으로 만들 수 있을 것이다. @ ▶ 전체 소스 다운로드 |
'C#' 카테고리의 다른 글
사용자 컨트롤에서 비동기처리 중에 이벤트 넘기기 (0) | 2007.10.15 |
---|---|
사용자 컨트롤에 이쁜 아이콘 추가하기 (0) | 2007.10.15 |
[C#과 플래시로 온라인 게임 만들기] ③ 게임서버 완성 (0) | 2007.08.24 |
[C#과 플래시로 온라인 게임 만들기] ② 쓰레드 처리 (0) | 2007.08.24 |
[C#과 플래시로 온라인 게임 만들기] ① 델리게이트 이해 (0) | 2007.08.24 |