본문 바로가기

프로그래밍/Unity3D

유니티 네트워크 프로그래밍 -Unity5 Network




1. 개요




http://www.jenkinssoftware.com/




유니티 네트워크에 앞서 먼저 소개할 네트워크엔진 Raknet이다.


2005년도에 이 라이브러리를 이용하여 P2P방식의 네트워크 게임을 만든 경험이 있다.


당시에도 홀펀칭, 릴레이서버 기능등을 지원했었고 웬만한 게임에는 적용할만하다고


생각했었다.


그 이후 무료버전, 상용버전으로 나누어 운영을 하다가 한동안 관심이 없었는데


요즘 한참 핫이슈인 오큘러스VR 이 인수하여 오픈소스화 했다는


소식을 들었다.



Oculus Acquires & Open Sources RakNet

July 7, 2014

Oculus is pleased to announce that they’ve acquired RakNet. Oculus has open-sourced it starting today under a modified BSD license (the same license Facebook uses for its open source projects) from the Oculus GitHub repo: https://github.com/OculusVR/RakNet.


이 엔진은 게임전문 엔진으로 Only UDP기반이며 C++로 짜여져 있다.


처음에 이 엔진을 가지고 유니티에서 사용을 해볼까 고민을 하다가


다음과 같은 소식을 들었다.


Unity uses RakNet version 3.731, which should also be network compatible with 3.732. In order to use RakNet with Unity, you do not have to download RakNet or sign a license agreement, as Unity ships RakNet integrated into the binary code already.


유니티에 내장된 네트워크 기능이 Raknet 이었어??


얼마전까지만 하더라도 대부분 유니티4를 사용하는 사람이 많았을텐데


국내에 소개된 유니티 네트워크에 대한 설명이 별로 없는것 같아


정리를 해보았다.





유니티4에서도 기본적인 네트워크 기능은 제공하고 있었는데,


유니티5로 넘어오면서 네트워크 관련 함수들을 좀 더 Wrapping하면서 


고레벨 단계로 올려버렸고 (확인해본 결과 이 과정에서 API가 많이 수정되었다)


몇몇 인터페이스는 아예 에디터에 통합을 함으로써 더 쉽게 만들수가 있게 되었다.



이렇게 하이레벨로 제작된 API 스크립트를 Unity에서는 HLAPI 라고 한다.


Raknet에 기반한 라이브러리라서 UDP 기반의 프로토콜을 사용하고 있고


peer들을 매칭시켜주는 Matchmaking, Relay Server등을 지원하고 있다.






2. 유니티 네트워크 용어



HOST


로컬 클라이언트와 서버를 동시에 가동하고 있는 오브젝트


보통 PC1대로 네트워크 테스트를 할때 많이 사용한다.



Remote Client


순수하게(?) 클라이언트 기능만 사용하고 있는 오브젝트



Server


서버기능만 사용하고 있는 오브젝트




실행하고 있는 오브젝트가 로컬 플레이어인지 체크하려면 


isLocalPlayer의 변수를 확인하면 된다.





스폰(Spawn)


유니티에서 네트워크 오브젝트의 탄생(?)을 스폰이라고 부르는데 이는 FPS와 같은


게임에서도 사용되는 용어이다.


플레이어가 최초 나타나는 좌표 또는 죽었을때 다시 살아나는 장소로 사용되곤 했다.


중요한점은 이 스폰의 역할은 오직 서버에서만 가능하다는 점이다.


서버에서 오브젝트를 생성하고 적(Enemy), 일반물체와 같은 NonPlayer 객체를


관리한다.


게임오브젝트에 NetworkTransform Component가 있다면


오브젝트의 움직임이 자동 동기화된다.





NetworkManager


말그대로 전체 네트웍기능을 담당하는 컨트롤타워라고 생각하면 된다.


이미 지정이 되어 있으므로 컴포넌트 추가에 타이핑만 하면 


자동검색이 되어 부착이 쉽다.



Scene이 넘어가도 유지를 하려면, Dont Destroy On Load 를 체크해주고


네트워크 기능이 비활성화된 Scene 


예를 들면 메뉴화면과 같은 Scene을 Offline Scene에 등록해 준다.


반면 네트워크 기능이 활성화된 Scene


예를 들면 게임플레이과 같은 Scene을 Online Scene에 등록해 준다.


네트워크가 활성화 된 상태에서 서버 또는 호스트 연결이 끊어지면


자동으로 Offline Scene으로 전환이 된다.


만약에 하나의 Scene으로 제어를 할려고 한다면 굳이 하지 않아도 된다.




다음은 Spawn Info 부분인데 중요한 부분이다.


자신이 컨트롤할 플레이어 Prefab 을 화면과 같이 Player Prefab에 끌어다 등록해 준다.

Auto Create Player 체크박스를 켜주면 시작과 동시에 플레이어가 스폰이 된다.


플레이어 스폰을 보류하려면 이 체크박스를 꺼주면 된다.




또한 스폰방법도 지정할 수 있는데, 


Player Spawn Method를 Random으로 해주면 


NetworkStartPos로 설정된 지점중에 랜덤으로 스폰이 된다.


RoundRobin으로 설정할 경우 주의점이 있는데,


만약 StaratPos가 3개인데, 4번째 클라이언트가 입장을 하면 접속이 거부된다.




NetworkStartPos Component도 이미 지정이되어 있기 때문에 타이핑해서 추가해주면 된다.


그리고 네트워크 동기화를 해야할 모든 오브젝트(prefab)를


Registered Spawnable Prefabs 에 등록해 준다.


예를 들어 적(Enemy), 동기화 해야할 논플레이어 오브젝트들을 여기에 등록하면 된다.




그리고 동기화 해야할 모든 오브젝트에 필수 Component가 있는데


바로 NetworkIdentityNetworkTransform 이다.


NetworkIdentity 는 오브젝트별 구별할 수 있는 일종의 고유ID라고 생각하면 된다.


NetworkTransform 은 오브젝트들을 동기화하기 위한 컴포넌트이다.


플레이 도중 다른 오브젝트가 스폰이 되었을 경우나 실시간으로 움직임을


동기화하기 위해 필요하다.






또한 유니티에서는 서버, 클라이언트 역할을 위한 


간단한 인터페이스도 제공하고 있는데 바로 Network Manager Hud 이다.


그냥 켜두면 Host, Server, Client 접속을 위한 버튼 몇개가 보인다.



다음은 스크립트에 대한 설명이다.


오브젝트에 네트워기능을 사용하기 위해서는 NetworkBehaviour를 상속받아


사용해야 한다.


기본적으로 Start(), Update()와 같은 


유니티의 기본 오브젝트인 MonoBehaviour 와 비슷하며 


OnStartServer(), OnStartClient()와 같은 CallBack 함수를 이용할 수 있다.




SyncVar


동기화에 있어서 중요한 개념이다.


NetworkBehaviour 의 멤버변수이며 동기화 할 수 있는 변수를 선언할때 사용한다.


서버와 클라이언트끼리 데이터를 직렬화하여 따로 패킷을 보낼 필요없이


이 변수만 갱신해 주면 된다는 뜻이다.


오브젝트가 스폰될때 플레이어들에게 자동으로 최신 상태를 업데이트 해주며


유니티 메뉴얼에 따르면 OnStartClinet()가 호출되기 전에 적용된다고 한다.


중요한점은 동기화 방향은 서버에서 클라이언트이다.






class Player : NetworkBehaviour

{


    [SyncVar]

    int health;


    public void TakeDamage(int amount)

    {

        if (!isServer)

            return;


        health -= amount;

    }

}

또한 SyncVar는 int, float, string과 같은 기본 타입에 대해 사용할 수 있다.


class나 list와 같은 자료구조는 여기서 사용할 수 없다.





RPC


Remote Procedure Call 의 약자로 


원격에 있는 위치에서 동일한 코드를 사용할 수 있는 기술이다.


네트워크 프로그래밍에서 아주 유용한 개념이므로 잘 숙지하면 좋다.


자세한 설명은 위키피디아 참조 - 원격 프로시저 호출



예를 들어 이럴때 사용한다.


모든 오브젝트의 스폰은 서버에서 담당한다고 앞에서 설명을 했다.


따라서 클라이언트가 특정키를 눌렀을때 미사일이 발사된다고 하면


이런식으로 하면 된다.



 [Command]

    void CmdDoFire(float lifeTime)

    {

        GameObject bullet = (GameObject)Instantiate(

            bulletPrefab, 

            transform.position + transform.right,

            Quaternion.identity);



        NetworkServer.Spawn(bullet);

    }


키를 누르는건 분명 클라이언트이지만 


CmdDoFire() 가 실행되는 곳은 서버이다.


이처럼 RPC를 사용하기 위해서는 몇가지 문법을 지켜줘야 하는데


먼저 함수앞에 [Command]를 붙여줘야 한다.


그리고 함수명은 'Cmd' 라는 접두어로 시작되어야 한다.



반대로 서버에서 클라이언트에게 호출을 하게 할 수 있는데


마찬가지로 함수앞에 [ClientRPC] 를 붙여주고


함수명이 'Rpc' 로 시작되어야 한다.





 [ClientRpc]

    void RpcDamage(int amount)

    {

        Debug.Log("Took damage:" + amount);

    }



네트워크 메세지


NetworkClient m_client;


myClient.RegisterHandler(MsgType.Connect, OnConnected);


이런식으로 클라이언트가 연결이 되었을때 OnConnected() 함수를 Call 하는


메세지 등록 기능도 제공이 된다.




using UnityEngine;

using UnityEngine.Networking;

using UnityEngine.Networking.NetworkSystem;


public class Begin : NetworkBehaviour

{

    const short MyBeginMsg = 1002;


    NetworkClient m_client;


    public void SendReadyToBeginMessage(int myId)

    {

        var msg = new IntegerMessage(myId);

        m_client.Send(MyBeginMsg, msg);

    }


    public void Init(NetworkClient client)

    {

        m_client = client;

        NetworkServer.RegisterHandler(MyBeginMsg, OnServerReadyToBeginMessage);

    }


    void OnServerReadyToBeginMessage(NetworkMessage netMsg)

    {

        var beginMessage = netMsg.ReadMessage<IntegerMessage>();

        Debug.Log("received OnServerReadyToBeginMessage " + beginMessage.value);

    }

}




이런식으로 사용자 정의 메세지도 등록 가능하다.







[Client]


이름에서 알 수 있듯이 클라이언트에서만 실행 가능한 코드를 이용할때 사용한다.



[ClientCallback]


사실상 [Client] 와 동일하다.


하지만 서버와 같은 권한이 없는 오브젝트에서 호출을 할때 경고메세지가 발생하게


되는데, 이걸로 선언을 하면 경고메세지가 스팸처럼 여러번 발생하지 않는다.


Update()와 같은 자주 호출되는 부분에서 사용한다.


하지만 유니티 5.3에서 테스트한 결과 [Client] 를 사용해도 경고메세지 카운트만


올라가지, 여러번 메세지가 스크롤되면서 발생하진 않는다.



[Server]


마찬가지로 서버에서만 호출할 수 있는 함수를 작성할때 사용



[ServerCallback]


[Server] 와 기능은 동일하지만, 어려번 경고 메세지를 발생하지 않는다.




유니티 네트워크 프로그래밍에서 이정도만 알면 


기본적인 테스트는 할 수 있다.


자세한건 레퍼런스를 참조하면서 하면 된다.


한글화도 80%이상 되어서 보는데 크게 불편하진 않다.


물론 번역이 변역기 수준이라는건 어쩔 수 없;;





그렇다면 이제 실습









3. 예제코드




첨부파일에 들어 있는 Move 예제 소스이다.(소스는 하단 링크에 있음)


방향키를 눌러 Sphere의 움직임만 동기화하는 모습을 볼 수 있다.


(참고로 유니티 5.3에서 실행)


영상에서 NetworkManager의 Send Rate를 조절하는 부분이 있는데,


초당 패킷을 몇번 날리는지 설정하는 부분이다.


내부적으로 들어가면, 초당 OnSerializeNetworkView 를 몇번 호출하여 


오브젝트 싱크를 맞추는지 설정하는 부분이며 


수치가 변경되는 즉시 RPC에도 영향을 미친다고 한다.


오브젝트가 스폰이 된 이후 어떠한 영향도 받지 않으면 


이부분은 0으로 맞추어 부하를 줄이는 것이 좋다.


움직임이 적은 오브젝트는 수치를 낮추고 빠른 오브젝트는 약간 높여서


적당히 맞추는게 좋다.



public class ExampleClass : MonoBehaviour { void Awake() { Network.sendRate = 25; } }// 이런식으로 수동으로 설정도 가능하다.




핵심코드는 Ball 오브젝트에 첨부된 이게 전부이다.



[ClientCallback]

void Update ()

{

if (!isLocalPlayer)

return;

if (Input.GetKey(KeyCode.Space))

{

CmdNudge(NudgeDir.Jump);

}

if (Input.GetKey(KeyCode.LeftArrow))

{

CmdNudge(NudgeDir.Left);

}

if (Input.GetKey(KeyCode.RightArrow))

{

CmdNudge(NudgeDir.Right);

}

if (Input.GetKey(KeyCode.UpArrow))

{

CmdNudge(NudgeDir.Up);

}

if (Input.GetKey(KeyCode.DownArrow))

{

CmdNudge(NudgeDir.Down);

}

}


[Command]

public void CmdNudge(NudgeDir direction)

{

switch (direction)

{

case NudgeDir.Left:

GetComponent<Rigidbody>().AddForce(new Vector3(-nudgeAmount,0,0));

break;

case NudgeDir.Right:

GetComponent<Rigidbody>().AddForce(new Vector3(nudgeAmount,0,0));

break;


case Nudg

 

eDir.Up:

GetComponent<Rigidbody>().AddForce(new Vector3(0,0,nudgeAmount));

break;

case NudgeDir.Down:

GetComponent<Rigidbody>().AddForce(new Vector3(0,0,-nudgeAmount));

break;

case NudgeDir.Jump:

GetComponent<Rigidbody>().AddForce(new Vector3(0,nudgeAmount,0));

break;

}

}




 



키 입력은 클라이언트에서만 가능하도록 처리하고 


실제 오브젝트를 움직이는건 RPC를 이용하여 서버에서 동작하도록 하게 하였다.


앞에서 여러번 설명했지만, 동기화 방향은 서버에서 클라이언트이다.







4. 마무리



이렇게 코드 몇줄만으로도 네트워크 테스트가 가능하며,


오브젝트의 스폰, 움직임을 자동 동기화 해줄수 있다는 점이


유니티5 네트워크 기능의 장점이라고도 할 수 있겠다.



영상으로 소개한 예제코드는 하단 링크를 통해 받을 수 있으며


아래 링크를 따라가면 더 많은 예제소스가 있으니 참고하면 된다.


당연히 유니티5에 추가된 기능이므로 에디터는 5버전이상을 사용해야 하며


가급적 5.3 이상을 권장한다.


그럼 즐거운 프로그래밍 되시길~



첨부파일:

move.zip



네트워킹 레퍼런스: http://docs.unity3d.com/kr/current/Manual/UNetReference.html


샘플프로젝트: http://forum.unity3d.com/threads/unet-sample-projects.331978/


추가 튜토리얼 동영상:

https://www.youtube.com/watch?v=JlKf0h0K5PU