게임 테마의 판타지 소설에는 의례 게임 시스템에서 다이렉트로 퀘스트가 공유되고는 한다. 개개인의 플레이어에게 다이렉트로 퀘스트를 쏘는 것처럼 보이는 것도 가능하긴하다. 그래도 일련의 퀘스트 시리즈를 송수신해서 게임에 반영하기 위해서는 클라이언트에서도 서버에서도 이를 반영하는 내용이 필요하긴 하다.

 

<대강 노트>

 

퀘스트 추상화

- ID

- StartNPC

- EndNPC

- 보상 종류

- 제목

- 내용

- 이벤트 Handler

- 이전 퀘스트

- 다음 퀘스트

 

- 보상 증강 요청

- 퀘스트 목록에 끼워 넣기

- 트랙 등록

- 트랙 확인

 

임시 퀘스트 유형 분류

- 이벤트 : 몹 러쉬

- 집계 : 아이템, 몹, 거리, 생산, 습득

- 시간: 제한 시간, 타임어택

 

귀찮아서 엔진으로 처리하기보단 c/c++로 작성 예정

 

Multiplexing에 대해 공부하면서 몇 가지 의문이 생겼다. 간단하게 요약하자면 멀티 플렉싱이 파일 스크립터(?)들을 용처에 맞게 나누어 저장하여 폴링(polling)으로 이벤트가 발생했는지를 확인하여 이를 처리하는 방식이라고 이해를 했다. 폴링이라는 단어를 처음 접한 곳은 그래픽스 수업을 들으면서 였다. 그래픽스를 C 레벨로 배우기도 했고 어플리케이션 단위(고급 언어)보다는 어셈블리에 더 가깝다고 생각했기에 깊게 파고 들진 못했다. 그냥 배열 중에 true 비트 값 감지해서 처리하는 정도라고 개념만 알고 넘어갔기 때문이다.

이 폴링에 대해 다시 생각하게 된 계기는 select 함수에 대해 공부를 하면서이다. select함수는 파일 디스크립터들을 모아둔 fd_set에서 이벤트가 발생한 디스크립터들을 두고 다른 이들은 모두 0으로 재설정되기 때문이다. 이후 그 결과가 반영된 fd_set에서 FD_ISSET으로 설정된 값을 찾아 처리를 하게 된다. 이 과정이 폴링과 유사한 것 같다(사실 이 작업이 폴링일지도).

그래서 결론으로 돌아오자면 배열로 하는 알고리즘이 필요한 이유는 이때 폴링작업을 하기 때문이다. 실제 사용사례를 예측해보면, 파일 스크립터 전부가 소켓의 역할을 한다고 가정하면 수많은 데이터 처리를 해야하고, 많아지면 많아질수록 처리 시간이 늘어나기 때문이다. 그렇다고 쪼개서 한다고 가정하면 언제부터 쓰레드로 나누어서 작업을 해야하는지와 처리 여부를 스레드나 프로세스와 공유하기 위해 lock 작업이 필요한지 아닌지를 경험한 적이 없어서 불안하다. 때문에 싱글 스레드, 싱글 프로세스는 배열을 어떤 식으로 관리하는 지에 따라 성능적인 측면이 효용이 있을 것 같다.

이 글은 스팀에서 플레이했던 펠월드의 동시 접속 인원수로 기사가 나온 때부터 시작된 고민에 대한 나름의 해석과 추측이다.

 

뉴스를 접하기 전에는 동시접속자를 집계하는 게임은 MMO 성향을 가진 게임뿐인 줄 알았다. 데디케이트 서버나 리슨서버를 알게 되었을 때도 그들은 host에 연결되는 것이지 게임사의 서버에 접속을 하는 것은 아니라고 생각했기 때문이다. 펠월드의 게임형태도 데디케이트나 리슨 서버의 형태를 띄고 있다고 생각했기 때문에 동시 접속자 수를 집계를 할 필요가 있나? 라는 생각이 먼저 들었다. 저번 포스팅에서도 기술하였다시피 로그인 정보나, host의 IP 정도 정보를 받아오는 정도이지 않을까 싶었다. 게임사에서 public으로 관리하는 서버는 직접적인 접속이 발생하니까 동접자 수가 중요할 수도 있지만 펠월드 게임 형태 상 모든 유저가 공식서버에서 플레이를 하는 것은 아니다. 거의 대부분의 유저들은 싱글플레이나 지인들과 플레이하는 정도의 사적인 서버를 사용할 것이다.

그럼 또다른 의문이 생긴다. 게임사가 동접자 수를 집계할 수 있는가에 대한 것이다. 플레이어가 싱글로 하던 멀티로 하던 그들의 플레이가 게임사에 집계된다는 말은 조금 이상하긴하다. 싱글 플레이는 대부분 게임 플레이를 어떻게 하던 공유되지 않는 것이라고 생각하니까. 그렇다고 집계를 하냐 마냐는 게임의 형태에 따라 다르다. 인앱결제나 게임 내 재화가 큰 가치가 있는 경우는 감시가 필요할 것이다. 아무튼 싱글 플레이로 플레이어가 공유하고 싶은 정보는 기껏해야 로그인과 구매 여부 정도 일 것이다.

로그인과 구매 정보라고 생각하면, 사용자 입장에서 게임하는 줄곧 켜져있는 프로그램이 있다. 스팀이나 에픽등 게임을 구매한 스토어이다. 게임을 실질적으로 다운 받고 켰을 때, 게임 안에서 로그인을 요청하는 경험은 거의 없다. 왜냐하면, 게임을 다운받을 수 있다는 것 자체가 게임 스토어에서 구매를 했다는 보증이기 때문이다. 그럼 스팀에서 언제 어떤 식으로 게임 상황을 집계하길래 동접자 수가 있는지에 대해 조금 추측을 해보고자 한다.

가장 쉽게 생각할 수 있는 지점은 스팀 오버레이 기능이다. 스팀 오버레이를 보면 내가 하고 있는 게임과 함께 온라인인 친구들이 플레이하고 있는 게임을 확인할 수 있다. 이는 스팀 서버와 이 정보를 주고 받는다는 이야기이다. 다음으로 생각할 수 있는 지점은 스팀 클라우드이다. 우리는 플레이를 할 때 게임의 저장상황이 개인 컴퓨터의 로컬에 저장되곤 한다. 게임을 종료했을 때 스팀 클라우드 동기화 중이라는 아이콘을 본적 있을 것이다. 로컬로 저장된 내용을 클라우드에 추가적으로 복사하면서, 사용자에게 어디서 플레이하던 이전 플레이 기록을 불러와 사용할 수 있게 하는 서비스이다. 마지막으로 생각나는 것은 업적 시스템이다. 게임을 플레이하는 도중 게임사가 정해 놓은 몇 개의 업적을 클리어할 때, 왼쪽 아래에 무언가 뜨는 것을 본 적이 있을 것이다. 이 역시 스팀에 내 플레이 이력에 기록된다.

그럼 팰월드의 동접자 수가 스팀에도 의미가 있냐고 물으면 있을 것 같다. 개인적인 생각으로는. 몇가지 상황을 생각해보자면, 스팀의 활성화된 창구가 늘어났을 수도 있겠다 싶었다. 나만 해도 컴퓨터를 켜놓는 모든 순간 게임을 하고 있진않는다. 오른쪽 아래에서 스팀이 백그라운드 프로그램으로 표시되었을 때도, 내가 스팀에서 게임을 하지 않는 이상 내가 무슨 게임을 하는 지에 대해 공유되진 않을 것이다. 그럼 나한테 연결된 스팀의 소켓 역시 대기 상태가 되지 않을까 싶다. 그런 소켓을 놀리면 로그인한 디바이스만큼 관리해야하는 서버도 저장해야하는 정보도 많아질 것이다. 소켓들을 놀리지 않기 위해서 총량을 관리하겠지만, 플레이하고 있는 플레이어들은 필연적으로 활성화된 연결일 것이다. 이 활성화된 연결 자체가 늘어 소켓들을 늘려야 했다면, 높은 동접자 수가 큰 의미가 있었을 것이다. 다른 경우는 기술적인 조치 없이, 플레이어들의 게임 파이가 유래없이 변경되었을 경우이다. 상위권에 공고히 있던 게임들이 밀려나고 새로 출시된 게임이 그 위를 차지한다면 의미가 있을 것 같다. 

고려해야할 요소

- 연결 성향

- 연결 주체

- (추가사항)

 

게임 속 socket 연결 예상

1. TCP 연결에 클라 역할: 게임 사의 서버에 접속 요청, 로그인 요청 등

2. TCP 연결에 서버 역할: 게임 사 서버에서 클라이언트 연결(= 동시 접속자와 직결?)

3. UDP 연결에 request 역할: Dedicated나 Listen 서버에 연결 요청 후, 게임 내 위치 정보 송수신.

4. UDP 연결에 response 역할

> 게임 방(P2P 연결 방식)에 새로 들어온 인원에 대한 연결?

 

주요 요소

- 연결할 PC가 여러 개인가?

- 요청을 받는 쪽인가 하는 쪽인가?

- 주소를 저장해야 하는가 아닌가?

=> 머리가 따라가질 못하니까 일단 최대한 범용성을 전재로 목표치만 구성

 

목표

-  위치 패킷 수신 & 재송신

> 연결 성향: 1:1, UDP, Request 방식

=> socket들을 굳이 배열로 지금 다룰 필요는 없음

 

 

주요 주제
클래스화
 
기술 명세(절차 순)
- (클라이언트) ip와 port 입력
- (DLL) window 상태 초기화 => WSAStartup은 클라 통틀어 한번? 아님 열린 소켓이 있어도 해도되는가? 
- (DLL) 클라용 소켓(TCP) 열기
- (DLL) 서버용 소켓(UDP) 열기
- (DLL) 클라용 소켓(UDP) 열기
- (DLL) 입력 받은 ip와 port 설정
- (DLL) TCP connect <보류>
- (DLL) 서버용 UDP bind 
- (DLL) 클라용 UDP connect
---- 이후 디테일은 나중에 -----
- (클라?) 패킷 만들기
- (DLL) 데이터 송수신

클래스 임시 구성
변수
- 소켓 목록
- 최근 연결 주소
- 게임 서버 주소
- TCP/UDP  타입
- 데이터 패킷 포인터
- static, WSADATA?
함수
- window 초기화 설정
- 소켓 열기(by type)
- 소켓 옵션 get, set
- 연결 후 소켓 목록에 저장
- 연결 요청 수신 후 소켓 목록에 저장


 
 

기본 목표

1. 실시간으로 다른 컴퓨터에 조정할 액터의 위치를 전송한다.

2. 패킷을 받으면 벡터로 분해해서 프롬프트 창에 띄운다.

3. 다른 컴퓨터에서 입력 값에 따라 변화하는 벡터 값을 수신한다.

4. 이를 수신하여 벡터로 재구성하여 위치에 반영한다.

5.  이 모든 과정을 수행할 수 있는 라이브러리 만들기

 

개발환경

- 게임 엔진: Unreal Engine 5.1

- 게임 플레이 하는 OS: Window

- 패킷을 수신 받을 프롬프트 OS: Ubuntu

 

개발 방식

이를 수행하기 위해 라이브러리를 만들기로 결정했다. 만든 라이브러리를 Unreal C++에서 임포트 하여 사용할 예정이다.

지난 포스팅에 라이브러리를 어떤 식으로 구상해야하는지에 대해 3가지 정도로 추릴 수 있었다. 클래스, 인스턴스, 내부에서만 활용하는 구조체까지 밖으로 꺼내기. 이 중에서 무엇이 맞는 방식인지에 대해 많은 고민이 있었다. 곰곰이 생각해보니 라이브러리의 사용 이유가 라이브러리만 교체하면 이를 활용하는 프로그램의 소스코드에서는 아무것도 바꾸지 않고 바뀐 방식대로 활용하기 위함이라는 사실을 깨닳을 수 있었다.

 일단 세번째 방식은 생각할 필요도 없다. 남은 방식은 클래스화와 인스턴스화인데, 고민하는 포인트는 하나이다. 클래스 방식의 구현은 기본적으로 확장성과 재사용을 염두해두고 사용한다. 네트워크 연결을 전부 취합하여 관리한다고 생각하면, 클라이언트당 하나의 객체를 활용한다. 그렇다면 클래스를 활용하는 의미가 있냐는 것이다. 그런 맥락이라면 인스턴스화가 맞는데, 가급적 객체지향 프로그래밍을 지향하고 싶기 때문에 영 내키지가 않는다.

 클래스화를 목표로 일단 구성을 해보고자 한다. 클래스화를 목표로 네트워크 연결을 전부 취합하지 않고 각각의 클래스가 개별적인 통신 목표를 가지고 소켓들을 관리하는 방향으로 구성하고자 한다.

 

+ Recent posts