본문 바로가기

- Programming/- C#

★ 9. c# 네트워크 개발 p8

반응형

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

모든 출처는

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

그리고 URL :

http://lab.gamecodi.com/board/zboard.php?id=GAMECODILAB_Lecture_series&page=2&sn1=&divpage=1&sn=off&ss=on&sc=on&select_arrange=hit&desc=asc&no=69

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


C#으로 게임 서버 만들기 - (강좌 7에 이어서)턴 종료 처리


턴 종료 처리


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
/// <summary>
/// 클라이언트에서 턴 연출이 모두 완료 되었을 때 호출된다.
/// </summary>
/// <param name="sender"></param>
public void turn_finished(CPlayer sender)
{
    change_playerstate(sender, PLAYER_STATE.CLIENT_TURN_FINISHED);
 
    if (!allplayers_ready(PLAYER_STATE.CLIENT_TURN_FINISHED))
    {
        return;
    }
 
    // 턴을 넘긴다.
    turn_end();
}
 
/// <summary>
/// 턴을 종료한다. 게임이 끝났는지 확인하는 과정을 수행한다.
/// </summary>
void turn_end()
{
    // 보드판 상태를 확인하여 게임이 끝났는지 검사한다.
    if (!CHelper.can_player_more(this.table_board, get_opponent_player(), this.players))
    {
        game_over();
        return;
    }
 
    // 아직 게임이 끝나지 않았다면 다음 플레이어로 턴을 넘긴다.
    if (this.current_turn_player < this.players.Count -1)
    {
        ++this.current_turn_player;
    }
    else
    {
        // 다시 첫번째 플레이어의 턴으로 만들어준다.
        this.current_turn_player = this.players[0].player_index;
    }
 
    // 턴을 시작한다.
    start_turn();
}
cs


서버로부터 세균의 이동 통보를 받은 클라이언트들은 서로 각자의 화면에서 세균의 이동과 애니메이션 등을 처리하게 됩니다.

모든 처리가 끝난 클라이언트는 서버에 처리 완료 되었다는 메시지를 보내야합니다.

서버에서는 모든 클라이언트의 화면 처리가 끝났는지 체크한 뒤 턴 종료 처리를 수행합니다.


이 부분에서도 클라이언트는 단독으로 턴을 넘기지 않고 서버에 요청한 뒤 허락이 떨어지기만을 기다리고 있게 됩니다.

서버는 이 때 최대한 빨리 처리를 하여 클라이언트들에게 다음 작업을 진행하라고 말해줘야하는 것이지요.

이 처리 시간이 길어질수록 유저는 렉을 느끼게 되고 서버가 안좋다는 등의 판단을 하게 됩니다.

따라서 서버의 로직을 작성할 때는 최대한 빠른 시간 안에 수행될 수 있도록 최적화 하는 것이 좋습니다.

클라이언트에서는 화면의 버벅거림을 해소하기 위한 최적화를 진행하지만

서버에서는 유저들의 대기 시간을 최소한으로 줄여주기 위한 최적화를 진행하게 되는 것입니다.

물론 이런 상황에서도 안전성이 제일 중요하다는 것은 말할 필요가 없겠지요.

반응 시간을 줄인다고 꼭 필요한 체크를 안하고 넘어간다면 해킹에 무방비가 되어

결국엔 또 다시 체크하는 코드를 넣을 수 밖에 없게 됩니다.


위치 계산에 도움 되는 메소드 구현

게임의 보드 판은 8*8 형식의 격자 형태로 구성되어 있습니다. 하지만 CGameRoom에 정의된 보드 판 데이터는 리스트 형식으로

변수만 존재할 뿐입니다. 따라서 리스트 형식을 격자 형식으로, 격자 형식을 리스트 형식으로 변환하는 메소드를 만들어 두면

코딩할 때 편리하게 작업할 수 있습니다.

이런 메소드들을 CHelper라는 클래스에 모아 놨습니다. 코딩에 도움을 준다는 의미에서 지은 이름입니다.

이 클래스의 메소드들은 특정 클래스에 엮여 있는 것이 아닌 계산의 편의를 위해 만들어졌기 때문에 static으로 선언하여

언제 어디서든 호출 가능하도록 구현해 놨습니다.


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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
public static class CHelper
{
    static byte COLUMN_COUNT = 8;
 
    /// <summary>
    /// 포지션을 (row, col) 형식의 좌표로 변환한다.
    /// </summary>
    /// <param name="position"></param>
    /// <returns></returns>
    static Vector2 convert_to_xy(short position)
    {
        return new Vector2(calc_row(position), calc_col(position));
    }
 
    /// <summary>
    /// (row, col) 형식의 좌표를 포지션으로 변환한다.
    /// </summary>
    /// <param name="row"></param>
    /// <param name="col"></param>
    /// <returns></returns>
    public static short get_position(byte row, byte col)
    {
        return (short)(row * COLUMN_COUNT + col);
    }
 
    /// <summary>
    /// 포지션으로부터 세로 인덱스를 구한다.
    /// </summary>
    /// <param name="position"></param>
    /// <returns></returns>
    public static short calc_row(short position)
    {
        return (short)(position / COLUMN_COUNT);
    }
 
    /// <summary>
    /// 포지션으로부터 가로 인덱스를 구한다.
    /// </summary>
    /// <param name="position"></param>
    /// <returns></returns>
    public static short calc_col(short position)
    {
        return (short)(position % COLUMN_COUNT);
    }
 
    /// <summary>
    /// cell 인덱스를 넣으면 둘 사이의 거리 값을 리턴해준다.
    /// 한칸이 차이나면 1, 두칸이 차이나면 2
    /// </summary>
    /// <param name="from"></param>
    /// <param name="to"></param>
    /// <returns></returns>
    public static short get_distance(short from, short to)
    {
        Vector2 pos1 = convert_to_xy(from);
        Vector2 pos2 = convert_to_xy(to);
        return get_distance(pos1, pos2);
    }
 
    public static short get_distance(Vector2 pos1, Vector2 pos2)
    {
        Vector2 distance = pos1 - pos2;
 
        short x = (short)Math.Abs(distance.x);
        short y = (short)Math.Abs(distance.y);
 
        // x, y 중 큰 값이 실제 두 위치 사이의 거리를 뜻한다.
        return Math.Max(x, y);
    }
 
    public static byte howfar_from_clicked_cell(short basic_cell, short cell)
    {
        short row = (short)(basic_cell / COLUMN_COUNT);
        short col = (short)(basic_cell % COLUMN_COUNT);
        Vector2 basic_pos = new Vector2(col, row);
 
        row = (short)(cell / COLUMN_COUNT);
        col = (short)(cell % COLUMN_COUNT);
        Vector2 cell_pos = new Vector2(col, row);
 
        Vector2 distance = (basic_pos - cell_pos);
        short x = (short)Math.Abs(distance.x);
        short y = (short)Math.Abs(distance.y);
        return (byte)Math.Max(x, y);
    }
 
    /// <summary>
    /// 주위에 있는 셀의 위치를 찾아서 리스트로 리턴
    /// </summary>
    /// <param name="basic_cell"></param>
    /// <param name="targets"></param>
    /// <param name="gap"></param>
    /// <returns></returns>
    public static List<short> find_neighbor_cells(short basic_cell, List<short> targets, short gap)
    {
        Vector2 pos = convert_to_xy(basic_cell);
        return targets.FindAll(obj => get_distance(pos, convert_to_xy(obj)) <= gap);
    }
 
    /// <summary>
    /// 게임을 지속 할 수 있는지 체크한다.
    /// </summary>
    /// <param name="board"></param>
    /// <param name="current_player"></param>
    /// <param name="all_player"></param>
    /// <returns></returns>
    public static bool can_play_more(List<short> board, CPlayer current_player, List<CPlayer> all_player)
    {
        foreach (short cell in current_player.viruses)
        {
            if (CHelper.find_available_cells(cell, board, all_player).Count > 0)
            {
                return true;
            }
        }
        return false;
    }
 
    /// <summary>
    /// 이동 가능한 셀을 찾아서 리스트로 돌려준다.
    /// </summary>
    /// <param name="basic_cell"></param>
    /// <param name="total_cells"></param>
    /// <param name="players"></param>
    /// <returns></returns>
    public static List<short> find_available_cells(short basic_cell, List<short> total_cells, List<CPlayer> players)
    {
        List<short> targets = find_neighbor_cells(basic_cell, total_cells, 2);
 
        players.ForEach(obj =>
        {
            targets.RemoveAll(number => obj.viruses.Exists(cell => cell == number));
        });
 
        return targets;
    }
}
cs


세균전 게임을 설계하고 상세한 구현 부분까지 코딩해봤습니다.

서버측 로직이라 화면에 보이는 것이 없어 조금 지루했을지도 모르겠습니다.

아직 점수 계산 등의 처리는 구현이 안되어 추후 클라이언트 연동 작업할 때 다시 진행하도록 하겠습니다.

다음 강좌에서는 유저의 접속과 매칭 시스템을 구현하고 멀리 떨어진

유저들이 어떻게 게임 방 안에 들어가 함께 게임을 즐길 수 있는지

그 원리를 파헤쳐 보도록 하겠습니다.

감사합니다.

반응형

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

★ 11. c# 네트워크 개발 p10  (0) 2017.02.15
★ 10. c# 네트워크 개발 p9  (0) 2017.02.14
★ 8. c# 네트워크 개발 p7  (0) 2017.02.13
★ 7. c# 네트워크 개발 p6  (0) 2017.02.12
★ 6. c# 네트워크 개발 p5  (0) 2017.02.10