programming/actionscript2014.06.26 16:10

드디어 본격적인 Multiplay Cloud Service를 사용해 보겠습니다.


YGN(Playerio)이란?

Yahoo Games Network의 약자인데 원래는 Player.io라는 회사의 서비스를 야후가 인수하면서 이름이 바뀌었습니다. 이 때문에 튜토리얼 등 각종 문서의 링크가 꼬이고 검색결과도 꼬이는 상황이 생기고 있지요.. Player.io란 이름이 꽤 직관적인데 왜 YGN이란 의미도 못알아먹는 이름으로 바꾼건지 알수가 없네요. 답답...

어쨌든 이 YGN에는 수 많은 서비스가 제공되고 있는데 여기서는 Multiplayer 부분에만 대략 살펴보도록 하겠습니다.


자세한 설명은 아래 링크를 참고해 주시구요.

참고 : https://gamesnet.yahoo.com/documentation/services/multiplayer/

쉽게 말해서 야후가 알아서 게임서버를 호스팅해 준다~ 고 보시면 됩니다. 

이 서비스의 특징을 보면

  • 클러스터링 할 수 있다. 즉 서버확장이 가능하다.
  • 유용한 룸 기능(채팅방처럼 유저간 그룹을 묶을 수 있죠)
  • 서버코드는 닷넷(C#, VB.NET)으로 개발하면 된다.
  • 클라이언트는 플래시,유니티3D,닷넷,안드로이드,아이폰으로 개발 할 수 있다.
  • 보안 우수하다.
  • 로컬PC에 설치해서 쓰는 개발&디버깅용 서버도 준다.

의 특징을 가지고 있다고 합니다. 


일단 YGN은 크게 Yahoo Channel 서비스그룹과 Backend Services 그룹으로 서비스가 나누어져 있는데요. Yahoo Channel은 일부 유료, Backend Services는 무료로 서비스 되고 있습니다. Multiplayer Service는 Backend Services에 속해 있으므로 무료죠! 하하하


사용법 등등은 문서가 매우 잘 되어있으므로 YGN의 공식 문서를 보시구요. 여기서는 간단한 구현을 통해 얼마나 쉬운지, 얼마나 성능이 쓸만한지 여부만 보도록 하겠습니다.


가입하기

무료가입이니 가입부터 합시다. 필요한 건 이메일 주소 뿐입니다. 가입 후 콘솔 관리창에 들어가면 기본 게임프로젝트에 GameID를 부여 받습니다. 나중에 이 GameID를 가지고 클라이언트에서 접속을 하게 됩니다.



서버 구현하기

일단 Multiplayer를 위해서는 닷넷(C#/VB.NET)으로 서버를 구현해야 합니다. 비주얼스튜디오가 없으신 분은 여기서 다운받으시거나 MonoDevelop을 사용하셔도 됩니다. 

프로젝트는 Class Library(.dll) 프로젝트로 생성합니다. 

그리고 YGN SDK를 다운받아서 Multiplayer 폴더의 Player.IO GameLibrary.dll 파일을 참조합니다.


using PlayerIO.GameLibrary;  

namespace MovingBox  
{  
    [RoomType("MyRoomType")]  
    public class MyGame : Game  
    {  
        public override void GotMessage(BasePlayer player, Message message)  
        {  
            Broadcast(message);  
        }  
    }  
}

위 코드는 클라이언트로부터 메세지를 받으면 그대로 접속중인 모든 클라이언트들에게 뿌려주는 에코서버를 구현한 코드입니다. 엄청 간단하죠?

빌드를 하면 dll이 생성되는데, 이걸 YGN에 업로드 하면 서버 작업은 끝납니다. 


하지만!


서버에 올리기 전에 테스트/디버깅은 필수죠. 그래서 YGN은 Development Server라는 걸 제공해 주고 있습니다.

일단 클라이언트 부터 구현 후에 테스트를 해보독 하죠.


클라이언트 구현하기

여기선 AS3.0으로 구현해 보겠습니다. Playerio에서는 플래시를 위한 swc 라이브러리를 제공합니다. SDK 폴더의 \Flash\NewGame\Flash\PlayerIOClient.swc를 라이브러리로 추가 한 후 아래 코드를 작성합니다. 


package     
{    
    import flash.display.Sprite;  
    import flash.display.StageAlign;  
    import flash.display.StageScaleMode;  
    import flash.events.MouseEvent;  
    import flash.text.TextField;  
    import playerio.Client;  
    import playerio.Connection;  
    import playerio.Message;  
    import playerio.PlayerIO;  
    import playerio.PlayerIOError;  
    
    public class Main extends Sprite     
    {    
        private var txtLog:TextField;    
        private var mcBox:Sprite;    
          
        private var cla:Client;  
        private var conn:Connection;  
            
        public function Main():void     
        {    
            //init display    
            stage.scaleMode = StageScaleMode.NO_SCALE;    
            stage.align = StageAlign.TOP_LEFT;    
                
            //init log    
            txtLog = new TextField;    
            txtLog.selectable = false;    
            txtLog.mouseEnabled = false;    
            txtLog.multiline = true;    
            txtLog.width = stage.stageWidth;    
            txtLog.height = stage.stageHeight;    
            addChild(txtLog);    
                
            //connect   
            var myuserid:String = "GuestUser" + Math.random();  
            PlayerIO.connect(stage, "[발급받은 게임 아이디]", "public", myuserid, "", null, onConnect, onError);  
        }    
          
        private function onConnect(client:Client):void  
        {  
            log("connected to player.io");  
              
            //Set developmentsever  
            //client.multiplayer.developmentServer = "127.0.0.1:8184";  
              
            //join room  
            cla = client;  
            client.multiplayer.createJoinRoom("testRoom",   "MyRoomType", true, {}, {}, onJoin, onError);  
        }  
          
        private function onJoin(connection:Connection):void  
        {  
            log("joined to the room");  
              
            //init  
            conn = connection;  
              
            //draw box  
            drawBox();  
              
            //Add disconnect listener  
            connection.addDisconnectHandler(function ():void  
            {  
                log("Disconnected from server");  
            });  
              
            //Add message listener for users leaving the room  
            connection.addMessageHandler("move", function(m:Message, userid:String, x:Number, y:Number):void  
            {  
                if (cla.connectUserId != userid)  
                {  
                    mcBox.x = x;  
                    mcBox.y = y;  
                }  
            });  
        }  
          
        private function onError(error:PlayerIOError):void  
        {  
            log("got" + error.message);  
        }  
            
        private function onDrag(e:MouseEvent):void     
        {    
            this.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMove);    
        }    
    
        private function onDrop(e:MouseEvent):void     
        {    
            this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMove);    
        }    
            
        protected function onMove(e:MouseEvent):void    
        {    
            mcBox.x = this.mouseX - mcBox.width/2;    
            mcBox.y = this.mouseY - mcBox.height/2;    
            e.updateAfterEvent();    
                
            //broadcast  
            if (conn != null && conn.connected == true)  
            {  
                var msg:Message = conn.createMessage("move", cla.connectUserId, mcBox.x, mcBox.y);  
                conn.sendMessage(msg);  
            }  
        }  
            
        private function drawBox():void    
        {    
            mcBox = new Sprite();    
            mcBox.graphics.lineStyle(3,0x00ff00);    
            mcBox.graphics.beginFill(0x0000FF);    
            mcBox.graphics.drawRect(0,0,100,100);    
            mcBox.graphics.endFill();    
            addChild(mcBox);    
            setChildIndex(mcBox, 0);    
    
            mcBox.addEventListener(MouseEvent.MOUSE_DOWN, onDrag);    
            this.stage.addEventListener(MouseEvent.MOUSE_UP, onDrop);    
            log("drawBox...");    
        }    
            
        private function log(msg:String):void    
        {    
            txtLog.appendText(msg + "\n");    
        }    
    }    
}

위 코드는 이전 포스팅과 마찬가지로 박스 옮기기를 구현한 코드입니다.


테스트 및 디버깅하기

이제 서버와 클라이언트 구현이 모두 끝났지만 프로그래머라면 꼭 거쳐야할 과정이 바로 테스트와 디버깅이겠죠~! YGN에서는 이를 위한 독립서버를 제공하고 있습니다. 

아까 다운받은 SDK에 Multiplayer 폴더를 보면 Player.IO Development Server.exe가 있습니다. 이걸 실행 하면 아래와 같은 창이 뜨는데, 중간에 [MovingBox.dll] 부분을 클릭하면 dll파일을 선택하는 창이 뜹니다. 

바로 여기서 아까 서버 빌드한 dll파일을 선택하면 되는거죠. 


서버 코드를 디버깅을 하려면 원하는 코드에 Break Point를 찍고 Debug Mode로 빌드 한후 dll 파일을 Development Server에서 불러옵니다. 그리고 Visual Studio의 DEBUG > Attach to Proccess 메뉴를 선택해서 Player.IO Development Server.exe 프로세스를 Attach 하면 Break Point가 걸립니다.



그리고 클라이언트 코드에서는 onConnect 함수에서 주석처리된 라인을 해제하면 됩니다. 


private function onConnect(client:Client):void  
{  
    log("connected to player.io");  
      
    //Set developmentsever  
    client.multiplayer.developmentServer = "127.0.0.1:8184";  
      
    //join room  
    cla = client;  
    client.multiplayer.createJoinRoom("testRoom",   "MyRoomType", true, {}, {}, onJoin, onError);  
}

이제 실행해보면 클라이언트는 GameID를 확인하는 최초접속만 YGN서버에 접속하고 Room 접속 이후부터는 Development Server(기본 포트 8184)에 대신 접속하게 됩니다.


서비스하기

별 문제 없이 돌아간다면 이제 YGN에 올려봅시다. YGN 관리창에 접속해서 Mutiplayer > Game Code > Update Game Code 메뉴를 차례로 들어가서 서버 dll 파일을 업로드 합니다. 특별히 에러로그를 많이 수집해야하는 경우가 아니라면 웬만하면 Release Mode로 빌드한 후 올리시기 바랍니다. 성능차이가 많이 납니다.



클라이언트는 아까 테스트용으로 추가한 client.multiplayer.developmentServer 셋팅 부분을 제거 한 후 컴파일하면 모든 작업이 끝이 납니다. 클라이언트도 Release로 컴파일해서 배포해야 한다는 거 잊지 마시구요.


구현결과

developmentServer에서 할때는 꽤 좋은 성능을 보였는데, YGN으로 올리니 역시 약간 통신속도가 느려지는군요. 뭐 하지만 이정도면 나쁘지 않다고 봅니다.. 샘플이 극단적인 박스 이동이라서 조금 끊기긴 하지만 이동관련은 보간으로 튜닝하면 되니까요. 관련한 설명은 Building Flash Multiplayer Games의 Synchronization이나 Interpolation, Latency 부분을 읽어보시길 바랍니다.


Client A

Client B


결론

다양한 서비스, 비교적 잘 된 문서화, 쉬운 구현 방법, 깔끔한 콘솔창 등 전체적으로 매우 우수한 서비스로 보입니다. 특히 이 모든게 무료!!! 라는건 엄청난 매력이죠. 문제는 Yahoo가 이 서비스를 얼마나 유지할 것이냐가 가장 큰 불안요소.... 뭐 하지만 요즘 같이 생명주기가 짧은 모바일 게임시장에선 큰 불안요소가 아닐 것 같기도 하네요. 

어쨌든 인디개발자나 1인 프로그래머만 보유한 중소기업에겐 꽤 매력적인 서비스 인 것은 확실합니다. 

관심 있으신 분은 한 번 검토해 보시길...

Posted by 귀뫄뉘

댓글을 달아 주세요

  1. 좋은 자료 감사합니다.
    덕분에 좋은 거 하나 얻어가네요. 시도해보겠습니다 ^^
    +) 블로그에 링크 형식으로 퍼가겠습니다. :)

    2015.08.11 23:35 신고 [ ADDR : EDIT/ DEL : REPLY ]