서버 동기화 방식

C# 2021. 7. 23. 12:28
728x90

Peer-to-Peer Lockstep 방식(RTS)

▶ 모든 Peer들의 상태값을 항상 완벽하게 일치 시킨다.

동일한 input을 동일한 타이밍에 주면 모든 Peer들의 결과값은 동일하다.

 

구현

 unit의 상태값(hp,위치)을 동기화하지 않고, 유저의 input(유닛생산,유닛선택,유닛이동,유닛이동및공격)만 동기화 한다.

한 Peer의 input을 동일한 타이밍에 모든 Peer에게 전달하기 위해 input delay를 준다.

스타크래프트의 경우 200ms라고 한다.

200ms안에 모든 Peer들에게 input을 전달한다.

Peer가 100ms만에 전달 받았다면 100ms후에 받은 input을 수행하고, 50ms만에 받았다면 150ms후에 수행하는 식이다.

이 딜레이를 속이기 위해서 유닛을 이동시키면 당장 이동하지는 않지만 제자리에서 소리나 애니메이션을 바로 한다.

(ex. 뮤탈 : 제자리에서 날개짓 시작 및 소리를 낸다.)

 

장점

구현이 간단하다

모든 Peer의 상태가 동일하기 때문에, Server같은 신뢰할수 잇는 중간 매개체를 둬서 Peer들의 상태를 시뮬레이션 할 필요가 없다.

모든 Peer의 상태가 동일하기 때문에, Peer들간의 상태값 불일치를 자연스럽게 좁혀줄 보간 처리가 필요 없다.

 

단점

Peer의 모든 input이 모든 Peer에게 전달 됨으로서 맵핵이 나올수 밖에 없다.

게임에 랜덤요소가 없어야 하거나, 있더라도 초기 게임을 시작하기 전에 random함수의 seed값을 동일하게 맞춰야 한다.

모든 Peer의 상태를 동일하게 하기 위해서, 모든 Peer가 input을 줄때까지 기다려야 한다.

가장 안 좋은 Peer의 latency가 모든 Peer의 latency가 된다.

한 Peer라도 input을 주지 않으면 모든 Peer의 게임 진행이 멈춘다.

게임이 시작된 상태에서 중간에 진입할수 없다.

Server같은 신뢰 할수 있는 대상이 전투상태값을 시뮬레이션 하고 있지 않기 때문에 게임 중간에 상태값을 줄 수 없다.

 

Client/Server 방식(FPS, MMORPG)

모든 Client들의 상태값을 항상 완벽하게 일치 시키려고 하지 않는다.

 대신에 Client들 사이에 Server라는 신뢰할수 있는 모델을 두고, Client들의 상태값이 Server의 상태값에 수렴하도록 만든다.

 

구현

 Client는 Server에게 유저의 상태값(hp,위치)이 아닌 행동(스킬 사용, 어떤 방향으로 이동)을 알리고 Server는 해당 행동을 받아서 상태값을 시뮬레이션 한다.

 Server는 다른 Client들에게 유저의 행동뿐만 아니라 시뮬레이션한 상태값도 같이 알린다.

 Client가 반응성을 위해 선처리한 상태가 Server와 다를수 있음으로 상태값도 같이 보내서 Client가 보간할수 있게 한다.

반응성을 높이기 위해 다른 유저에게 당장 영향을 주지 않은 행동들은 Server의 응답을 기다리지 않고, Client에서 먼저 선처리한 후 Server의 결과를 받아서 보간한다.

 이동 : 이동 자체가 다른 유저에게 당장 영향을 주지는 않음으로 Client에서 선처리 하고 후에 보간한다.

 공격 : 다른 유저에게 당장 영향을 줄 수 있는 행동이라서 Client에서 선처리 하지 않고 Server에 요청후 응답을 기다렸다 처리한다.

칼을 들어 올리는 모션(input delay)동안 Server에 요청해서 다른 Client들에게 공격을 알린후, 공격에 대한 효과 적용은 Client들과 Server모두 같은 순간에 이루어 지도록 한다.

 

장점

 유저의 행동이 Server를 거쳐서 다른 Client에게 전달될때 제한된 정보만 전달해서 보안성을 높일수 있다.

 Server에서 랜덤요소를 결정해서 알려줄수 있다.

 Client중 하나가 응답하지 않는다고 해서 모든 다른 Client가 기다릴 필요가 없다.

 게임이 시작된 상태에서 중간에 진입할수 있다.

 

단점

 Server가 Client의 행동들을 다 시뮬레이션 해야 한다.

 Client가 Server와의 상태값 불일치를 어색하지 않게 좁혀줄 보간 처리를 잘 해야 한다.

728x90
Posted by 정망스
,
728x90
public static IEnumerable<AAA> GetData(AAAType type)
{
   var groupList = new List<int>();
   int categoryID = (int)type + 1;

   return Database.Shared.GetDatas<AAA>()
                                 .Where((data) =>
                                 {
                                     if (groupList.Contains(data.GroupID) || data.CategoryID != categoryID)
                                          return false;

                                         groupList.Add(data.GroupID);
                                          return true;
                                 })
                                 .OrderBy(data => data.GroupID);
}

이 코드에는 문제가 있다.

 

힌트는 클로저

디버그로 저 함수를 통해 받은 컬렉션의 갯수를 찍어보았다.

var aaa = Data.GetData(AAAType.A);

Debug.Log(aaa.Count());
Debug.Log(aaa.Count());

첫번째 : 30개
두번째 : 0개

???..

왜 두번째에서 0개가 나오는가 .... 

이유는 LINQ는 지연평가가 이루어 지므로 

Count()를 호출할때 평가를 하게 되는데 첫번재 Count()를 실행하여 GetData의 LINQ가 처리 될때는 groupList가 처음 생성되고 조건에 맞게 처음 Add가 되어 정상적으로 Count가 잘 나온것이였고

 

두번째 Count()를 호출할때는 groupList가 클로져로 사용되었고 캡쳐 되었다 보니 지워지지 않고 그대로 유지 된 채로 존재하고 groupList에는 이미 해당하는 데이터에 groupID가 들어있어서 위의 조건인

if (groupList.Contains(data.GroupID) || data.CategoryID != categoryID)
    return false;

에 의해 전부다 false로 팅겨내어서 아무런 데이터가 없는 채로 반환된것이였다..

 

인지하고 잘 쓰자 ... 

728x90
Posted by 정망스
,
728x90

결론적으로 format이 builder보다 속도면에선 느린듯하다.

 

막대한 데이터를 가지고 반복문을 돌려서 테스트 할시에 걸리는 시간이 format 눈에 뛸정도로 오랜 시간이 걸리는것 같았다.

 

그런데 반복문이 아니라 잠깐 string을 구성할때에는 둘의 성능차이는 10%미만이라고 하니 이럴때는 사용하고 싶은걸 사용하면 될듯하고

 

반복문같이 여러번 해야할 경우에는 builder를 사용해야겠다. 빌더가 가비지 컬렉션 면에서도 더 효율적이라고 하는것 같당.

728x90
Posted by 정망스
,
728x90

enum과, struct는 값 타입이기 때문에 힙에 할당을 하지 않으므로 가비지가 생성되지 않는다.

하지만 Dictionary를 사용함으로써 enum이나 structkey로 사용할 경우 가비지가 발생되게 된다.

 

이유는 Dictionary에서는 키값이 같은지 여부를 판단할 때 System.Collections.Generic 네임스페이스 내부에 존재하는 IEqualityComparer 인터페이스를 사용하는데

 

따로 Dictionary에 비교자 객체를 집어 넣지 않는다면 비교자 객체의 기본값은 EqualityComparer<T>.Default가 되고 이 Default 프로퍼티는 T타입이 System.IEquatable<T>을 따로 구현하지 않았을 경우에 EqualityComparer<T>를 반환하는데, 얘는 Object.Equals, Object.GetHashCoe를 기본 비교 함수들로 사용한다는 것이다.

enum과 struct는 비교 함수들이 따로 구현되어 있지 않기 때문에 key로 사용하게되면

결국 저 두 함수를 통하여 enum이든 struct든 값이 object로 박싱이 되버리니 가비지가 생기게 된다는 것이다.

 

해결책은 EqualityComparer<T> 인터페이스를 상속받는 클래스를 선언해서 비교하게 하는 방법이다

이것은 struct나, enum 똑같다. 

 

public enum SomeType 
{ 
    EnumA = 1, 
    EnumB = 2, 
} 

public class SomeTypeComparer IEqualityComparer<SomeType>

    bool IEqualityComparer<SomeType>.Equals(SomeType a, SomeType b) { return a == b; } 
    int IEqualityComparer<SomeType>.GetHashCode(SomeType obj) { return (int)obj; } 
}

 

위와 같이 IEqualityComparer<T> 인터페이스를 상속받는 클래스를 선언하고

Dictionary 인스턴스를 생성할때 생성자에 인스턴스를 넣어주면 된다.

 

Dictionary<SomeTypeint> dic = new Dictionary<SomeTypeint>(new SomeTypeComparer());

728x90
Posted by 정망스
,
728x90

 

맵에 존재하는 봇들을 없애는 게임을 만들어보면서 언리얼 엔진 경험을 해보았다..

 

약 일주일간 구글링, 언리얼 공식문서 및 공식 카페 분들에게 질문하며 만들었다. 사용법은 금방 익숙해 질 수 있었다

 

하지만 내부 제공되는 기능들, 블루 프린트가 아닌 c++로 최대한 만들어 보려 했기에 제공되는 함수의 종류나 사용법등에 익숙치 않아 시간을 잡아 먹었다.

 

그래도 언리얼 공식 문서가 잘 정리되있어서 필요한 정보를 찾아보기에는 수월했다.

 

늘 언리얼 엔진은 어떻게 개발하는걸까 궁금하긴했는데 어쩌다 해볼 시간이 주어져 하게 되었다. ㅋㅋ

 

아직 내가 모르는 많은 기능들이나 사용법이 있겠지만, 대충 이런거구나 하고 느낌 정도는 알 수 있는 시간이었다.

 

엔진 버전 : 4.14.3

 

구현 목록 :

플레이어 생성 및 이동,공격 (이동키 :W,A,S,D,  카메라 조정 : 마우스, 공격키 : 왼쪽 Shift)

 

플레이어 무기 생성 및 변경 (변경키 : C)

 

적 생성 및 이동,공격 AI (BehaviorTree)

 

발사체 발사 및 충돌 처리 (플레이어 경우 무기가 총일때 발사체, 적 봇은 원거리 공격 적일 경우 발사체)

 

플레이어 체력, 총알 갯수, 점수 계산 처리 (체력이 0일경우 게임 끝, 총알은 Max가 6개 다쓰고 나면 일정 시간 후 재충전, 적을 죽일때마다 100점씩)

 

적 타격시 폭발 이펙트 처리

 

UI처리

      게임 시작시 플레이어 체력, 총알 갯수, 점수 표시.

      게임이 종료시 메뉴 화면(재시작 or 나가기)

 

728x90
Posted by 정망스
,


맨 위로
홈으로 ▲위로 ▼아래로 ♥댓글쓰기 새로고침