본문 바로가기

- Programming/- C#

★ 11. c# 네트워크 개발 p10

반응형

==================================================================

모든 출처는

- 유니티 개발자를 위한 C#으로 온라인 게임 서버 만들기 - 저자 이석현, 출판사 한빛미디어

그리고 URL :

http://lab.gamecodi.com/board/zboard.php?id=GAMECODILAB_Lecture_series&no=79

==================================================================


C#으로 게임 서버 만들기 - 8. 세균전 게임 - 클라이언트 연동 - II


패킷 수신

ENTER_GAME_ROOM_REQ 패킷을 서버에 요청ㅇ한 뒤에는 서버로부터 응답이 오기만을 기다리면 됩니다.

세균전 게임은 1:1로 진행되는 게임이기 때문에 내가 게임 방 입장을 요청했다고 바로 게임을 할 수 있는 것이 아닙니다.

상대방 누군가가 나와 똑같은 패킷을 요청하여 서로 매칭이 이루어져야 비로소 게임을 시작할 수 있는 것이죠.

서버측 소스 코드를 작성할 때 ENTER_GAME_ROOM_REQ 패킷을 핸들링하여 게임 방을 생성하고 클라이언트에게

응답을 보내주던 부분이 기억 나시는지요?

최소한 두 명의 클라이언트가 ENTER_GAME_ROOM_REQ 패킷을 요청하면

서버는 매칭을 성사시키고 두 명의 클라이언트에게 각각 START_LOADING 패킷을 전달합니다.

그렇다면 START_LOADING 패킷을 수신하는 코드가 클라이언트에 존재해야겠죠.

서버로부터 이 패킷을 전달 받았다면 매칭이 성사된 것으로 생각하고 게임 방에 입장하도록 처리해봅시다.

CMainTitle.cs 스크립트의 on_recv 메소드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// 패킷을 수신 했을 때 호출됨.
/// </summary>
/// <param name="msg"></param>
public void on_recv(CPacket msg)
{
    // 제일 먼저 프로토콜 아이디를 꺼내온다.
    PROTOCOL protocol_id = (PROTOCOL)msg.pop_protocol_id();
 
    switch(protocol_id)
    {
        case PROTOCOL.START_LOADING:
            {
                byte player_index = msg.pop_byte();
                Debug.Log(player_index);
 
                this.battle_room.gameObject.SetActive(true);
                this.battle_room.start_loading();
                gameObject.SetActive(false);
            }
            break;
    }
}
cs

프로토콜 아이디를 꺼내와 START_LOADING 패킷이 맞다면 게임 방에 입장하도록 처리합니다.
먼저 서버에서 할당해준 자기 자신의 플레이어 인덱스를 꺼내옵니다.
서버에서 START_LOADING 패킷을 만들 때 입력한 순서대로 데이터를 꺼내와야 합니다.

1
2
3
CPacket msg = CPacket.create((Int16)PROTOCOL.START_LOADING);
msg.push(player.player_index);    // 본인의 플레이어 인덱스를 알려준다.
player.send(msg);
cs
[START_LOADING 패킷을 만드는 서버측 코드]

서버에서 byte 타입의 player_index를 넣어서 보내왔으므로 클라이언트에서도 msg.pop_byte 메소드를 이용하여
byte 타입의 player_index를 꺼내옵니다.
그 뒤 battle_room 오브젝트를 활성화시키고 start_loading 메소드를 호출하여 게임 방에 입장합니다.
마지막으로 현재 자신의 게임 오브젝트를 비활성화 시켜서 더이상 작동되지 않도록 합니다.

수신된 패킷의 전달
패킷을 수신하는 on_recv 메소드에서 게임 방 입장 처리가 이루어지는 것을 확인하였습니다.
그렇다면 이 on_recv 메소드는 어떤 과정을 거쳐서 호출되는 것일까요?
CMainTitle.cs 스크립트를 초기화 하는 Start 메소드를 통해서 그 과정을 파헤쳐 보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
CNetworkManager network_manager;
bool is_connected = false;
 
void Start()
{
    this.user_state = USER_STATE.NOT_CONNECTED;
    this.bg = Resources.Load("images/title_blue"as Texture;
    this.battle_room = GameObject.Find("BattleRoom").GetComponent<CBattleRoom>();
    this.battle_room.gameObject.SetActive(false);
 
    this.network_manager = GameObject.Find("NetworkManager").GetComponent<CNetworkManager>();
    this.network_manager.message_receiver = this;
}
cs

CMainTitle.cs 스크립트의 Start 메소드입니다. Start 메소드는 유니티 엔진에서 자동으로 호출해주며
주로 스크립트의 초기화를 담당하는 코드가 들어갑니다.
여기서도 마찬가지로 각종 리소스들을 초기화 하는 코드가 들어갑니다. 그 중에서 NetworkManager를 얻어와서
message_receiver를 설정해 주는 부분을 유심히 볼 필요가 있습니다.

1
2
this.network_manager = GameObject.Find("NetworkManager").GetComponent<CNetworkManager>();
this.network_manager.message_receiver = this;
cs


먼저 GameObject.Find 메소드를 통해서 CNetworkManager.cs 스크립트를 얻어옵니다.
NetworkManager는 씬을 구성할 때 만들어 놓은 오브젝트이며 CNetworkManager.cs 스크립트를
연결시켜 놓았던 것을 기억하실겁니다.
이 스크립트를 얻어온 뒤 message_receiver = this 를 통해서 CMainTitle.cs 스크립트를
message_receiver로 설정해주는 코드가 들어갑니다.
이렇게 message_receiver를 설정해주면 서버로부터 수신된 패킷을 CNetworkManager.cs 스크립트가
수신 처리하고 CNetworkManager.cs의 on_message 메소드에서 SendMessage를 통하여 해당 receiver의
on_recv 메소드가 호출되는 것입니다.

서버에서 패킷 전송 - CNetworkManager.cs의 on_message 호출 - message_receiver.SendMessage("on_recv", msg) 호출

이런 과정을 통해서 서버로부터 전송되어 온 메시지가 NetworkManager를 거쳐
해당 스크립트의 on_recv 메소드까지 호출될 수 있는 것입니다.
NetworkManager의 message_receiver는 MonoBehaviour 타입으로 되어 있기 때문에
우리가 생성한 CMainTitle.cs 스크립트의 인스턴스를 참조로 받아올 수 있는 것입니다.

서버가 보내온 패킷을 처음 수신 하는 부분은 CNetworkManager.cs의 on_message 메소드입니다.
이 메소드에서 바로 프로토콜 아이디를 꺼내와 로직 처리를 수행할 수도 있지만 SendMessage 메소드를 통해서
다른 오브젝트로 전달해주는 이유는 네트워크 코드와 로직 처리 코드를 분리시키기 위함입니다.
CNetworkManager.cs 스크립트는 다른 프로젝트에서도 재사용할 수 있도록 설계되었기 때문에
게임 로직에 관련된 코드는 최대한 적게 들어가도록 하는 것이 좋습니다.
SendMessage 메소드를 통해서 패킷 전달이 한 단계 더 거쳐서 처리되는 단점도 있지만
이정도의 처리는 순식간에 진행되므로 깔끔한 설계와 구현을 위해서 충분히 희생할 수 있는 부분입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using FreeNet;
using VirusWarGameServer;
 
public class CMainTitle : MonoBehaviour {
 
    Texture bg;
    CBattleRoom battle_room;
 
    CNetworkManager network_manager;
    bool is_connected;
 
    void Start()
    {
        this.is_connected = false;
        this.bg = Resources.Load("images/title_blue"as Texture;
        this.battle_room = GameObject.Find("BattleRoom").GetComponent<CBattleRoom>();
        this.battle_room.gameObject.SetActive(false);
 
        this.network_manager = GameObject.Find("NetworkManager").GetComponent<CNetworkManager>();
 
        this.network_manager.message_receiver = this;
    }
 
    /// <summary>
    /// 서버에 접속된 이후에 처리할 루프
    /// 마우스 입력이 들어오면 ENTER_GAME_ROOM_REQ 프로토콜을 요청하고
    /// 중복 요청을 방지하기 위해서 현재 코루틴을 중지시킨다.
    /// </summary>
    /// <returns></returns>
    IEnumerator after_connected()
    {
        while (true)
        {
            if (this.is_connected)
            {
                if (Input.GetMouseButtonDown(0))
                {
                    CPacket msg = CPacket.create((short)PROTOCOL.ENTER_GAME_ROOM_REQ);
                    this.network_manager.send(msg);
 
                    StopCoroutine("after_connected");
                }
            }
 
            yield return 0;
        }
    }
 
    void OnGUI()
    {
        if (this.is_connected)
        {
            GUI.DrawTexture(new Rect(00, Screen.width, Screen.height), this.bg);
        }
    }
 
    /// <summary>
    /// 서버에 접속이 완료되면 호출됨
    /// </summary>
    public void on_connected()
    {
        this.is_connected = true;
        StartCoroutine("after_connected");
    }
 
    /// <summary>
    /// 패킷을 수신 했을 때 호출됨.
    /// </summary>
    /// <param name="msg"></param>
    public void on_recv(CPacket msg)
    {
        // 제일 먼저 프로토콜 아이디를 꺼내온다.
        PROTOCOL protocol_id = (PROTOCOL)msg.pop_protocol_id();
 
        switch(protocol_id)
        {
            case PROTOCOL.START_LOADING:
                {
                    byte player_index = msg.pop_byte();
                    Debug.Log(player_index);
 
                    this.battle_room.gameObject.SetActive(true);
                    this.battle_room.start_loading();
                    gameObject.SetActive(false);
                }
                break;
        }
    }
}
 
cs
[CMainTitle.cs의 전체 소스 코드]

아직 게임 방에 입장하여 플레이하는 로직은 구현되지 않았기 때문에 ENTER_GAME_ROOM_REQ 패킷을 요청하여도
클라이언트에서는 아무 변화가 없는 것처럼 보입니다.
하지만 서버 소스 코드에 브레이크 포인트를 걸어서 디버깅을 해보면 ENTER_GAME_ROOM_REQ 패킷을 수신하고
매칭 처리 로직이 수행되는 것을 확인하실 수 있을겁니다.
다음 시간에는 클라이언트의 입력과 서버 쪽에 코딩된 게임 로직을 연동하는 부분을 살펴보겠습니다.
감사합니다.


반응형

'- Programming > - C#' 카테고리의 다른 글

★ 13. c# 네트워크 개발 p12  (0) 2017.02.16
★ 12. c# 네트워크 개발 p11  (0) 2017.02.16
★ 10. c# 네트워크 개발 p9  (0) 2017.02.14
★ 9. c# 네트워크 개발 p8  (1) 2017.02.13
★ 8. c# 네트워크 개발 p7  (0) 2017.02.13