본문 바로가기

portfolio

[리뉴얼] 5- ViewList와 Sector

 

 

개요

A라는 플레이어와 B라는 플레이어가 있다고 가정해보자 A플레이어는 C대륙에 있고 B플레이어는 D대륙에 있다고 할 때 서로 둘은 물리적으로 만날 수가 없는 위치이다. 이러한 두 플레이어는 서로 패킷을 주고받을 필요가 없기 때문에 이러한 플레이어 위치에 따른 패킷 전송을 제한하는 ViewLIst와 Sector를 사용한다.

 

먼저 Sector로 맵을 나눠서 파란색 원(A) 기준으로 주변에 있는 객체들을 NearList에 집어넣는다.

 

 

그리고 NearList에 있는 객체들 중에서 파란색 원(A)의 시야(보편적으로 클라이언트 화면에 보이는 시야)에 들어오는 B와는 서로 패킷을 주고받으며 시야 밖에 있는 C에 대해서는 서로 패킷 전송을 하지 않는다.

 

 

 Sector 전

 

1
2
3
4
5
6
7
    //내 주변에 있는 NPC 정보 push
    for (int i = 0; i < NUM_NPC; ++i) {
        int npc_id = i + MAX_USER;
        if (clients[npc_id].in_used == false)continue;
        if (GetNearObject(id, npc_id) == false)continue;
        nearList.insert(npc_id);
    }

 

전에는 Sector 개념이 없어서 모든 클라이언트에 대해 가까운 거리에 있는지 모두 체크하였다. 이렇게 하면 멀리 있는 클라이언트임에도 불가하고 N-1 만큼 for문을 돌아야 하기 때문에 효율성이 떨어진다.

 

Sector 후

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   //해당 Sector에 있는 Object 저장
    std::unordered_set <ObjectIDType> nearList;
 
    //해당 Sector 주변에 있는 8방향의 Sector 정보를 NearList에 저장
    for (PositionType i = 0; i < NUMBER_OF_SEARCH; ++i) {
        short x = newCellX + searchDirection_[i].first;
        short y = newCellY + searchDirection_[i].second;
        if (x < 0 || y < 0)continue;
 
        for (auto i : cells_[x][y]) {
            if (IsNearPlayerAndPlayer(i, id) == false)continue;
            nearList.emplace(i);
        }
    }

 

이후에는 본인이 들어가 있는 Sector에서 좌, 우, 상, 하 , 대각선을 포함해 근접해 있는 클라이언트를 대상으로 가까운 거리에 있는지 체크하기 때문에 저번에 비하면 반복문을 도는 횟수가 줄었다. 

 

대신에 고정 분할이기 때문에 한 곳에 많은 클라이언트가 몰린다면 효율이 떨어진다는 단점이 있다. 이러한 단점은 3D에서는 옥트리, 2D에서는 쿼드 트리를 사용해 공간분할을 진행한다.

 

ViewList 전

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
            clients[iter].m_lock.lock();
            //상대 viewList에 내가 있으면
            if (clients[iter].viewlist.count(id)) {
                //이동한 플레이어의 정보를 시야안에 있는 플레이어에게 전송
                clients[iter].m_lock.unlock();
                send_position_pakcet(iter, id);
            }
 
            //상대 viewList에 내가 없으면
            else {
                //viewList에 등록
                clients[iter].viewlist.insert(id);
                clients[iter].m_lock.unlock();
 
                //이동한 플레이어의 정보를 시야안에 있는 플레이어에게 전송
                send_put_player_packet(iter, id);
            }

 

ViewList는 이전에는 count() 멤버 함수를 사용했으며 상호 배제는 Mutex를 사용해 Read Write 구별 없이 Lock을 하였다. 

 

ViewList 후

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  players_[iter]->srwLock_.Readlock();
            if (players_[iter]->viewLIst_.find(id) != players_[iter]->viewLIst_.end()) {
                //이동한 플레이어의 정보를 시야안에 있는 플레이어에게 전송
                players_[iter]->srwLock_.Readunlock();
 
                NETWORK::SendMoveObject(players_[iter]->socket_,
                    players_[id]->x_, players_[id]->y_, id, OBJECT_DEFINDS::OTHER_PLAYER,
                    textureDirection);
            }
 
            //상대 viewList에 내가 없으면
            else {
                //read UnLock
                players_[iter]->srwLock_.Readunlock();
 
                //View List에 등록
                players_[iter]->srwLock_.Writelock();
                players_[iter]->viewLIst_.insert(id);
                players_[iter]->srwLock_.Writeunlock();
 
                //이동한 플레이어의 정보를 시야안에 있는 플레이어에게 전송
                NETWORK::SendAddObject(players_[iter]->socket_,
                    players_[id]->x_, players_[id]->y_, id, OBJECT_DEFINDS::OTHER_PLAYER);
            }

 

변경 후에는 좀 더 직관적인 find() 멤버 함수를 사용했다. 여기서는 문제가 없지만 multiset, multimap에서는 값을 찾았음에도 계속 개수를 새기 때문에 find()가 더 적합하다고 한다.

 

상호 배제는 단순히 Mutex가 아닌 SRWLock을 사용해서 Read와 Write를 구별해서 진행하였다.

 

현재 SRWLock을 RAII 기반으로 포팅한게 아니라서 직접 Lock, UnLock을 해주는 번거로움이 있고 실수도 유발할 수 있기 때문에 시간이 된다면 RAII 기반으로 다시 제작할 예정이다.

 

실행 모습

 

저번과 다르게 이번에는 직접 공유기에 네트워크를 물려서 네트워크를 타고 이동을 시연한 모습이다.

 

 

이 화면이 이번 구현의 핵심 모습이다. 상대 플레이어가 시야에서 사라지면 SendRemoveObject 패킷을 보내고 다시 시야에 들어오면 SendAddObject 패킷을 보내고 시야 내에서 이동 패킷을 보내는 것을 확인할 수 있다. 이렇게 시야 밖에 있다면 이동 패킷을 보내지 않게 하는 것이 시야 처리이다.

 

후기

 

중요하다고 생각하는 작업 중 하나인 시야 처리가 끝이 났다. 사실 네트워크 테스트도 두 명 이서만 테스트해봤기 때문에 어떤 멀티스레드 오류가 날지 아직 모르기 때문에 100% 완벽하다고는 할 수 없다. 

아르바이트 도 하고 있고 포트폴리오 작업만 하고 있다 보니 자연스럽게 블로그의 글 젠이 느려지는 거 같다.  이다음에는 A*를 포함한 AI의 이동과 AI의 공격 및 피격 내용을 구현해 올릴 예정이다.

 

 

리뉴얼 관련 글

--서버--

[portfolio] - [리뉴얼] 4- AcceptEx 및 이동 동기화

--클라이언트--

[portfolio] - [리뉴얼] 3- 공간 분할을 이용한 Sector Class

[portfolio] - [리뉴얼] 2- 경량 패턴을 이용한 World Class

[portfolio] - [리뉴얼] 1- 중재자 패턴을 이용한 Network Class

[portfolio] - [리뉴얼] 개요 및 목표(깃 주소 추가)

--리뉴얼 작업 전 포트폴리오

2019/12/16 - [portfolio] - DirectX & IOCP

 

 

* 리소스는 알피지 만들기 툴 리소스를 사용했으며 영리 목적으로 사용하지 않을 것입니다.