UE5/Studylog

[UE5/AWS] Studylog 7 - AWS Gamelift [ 2 ]

sulfurman 2026. 5. 17. 22:09

목차

     

    반갑습니다.

    오늘은 원래 유데미 AWS Gamelift 강의를 완강하고 그에대한 기록을 작성하려 했지만,
    강의 후반부부터는 제 프로젝트와 결이 맞지않아, 필요한 부분만 선택 수강한 뒤 개발한 매치메이킹 시스템을 소개하려고 합니다.


    1. 개요

    매치메이킹을 구현하기위해 가장 중요한 요소들을 생각해보면 크게 두가지로,
    클라이언트 검증올바른 매칭이 가장 먼저 떠올랐다.

    해당 클라이언트가 DB로부터 적법하게 할당된 클라이언트임을 확인해야하고,
    그런 클라이언트들이 매칭에 필요한 프로퍼티를 가지고 매칭이 올바른 형태로 시작되어야 할 것이다.
    자세한 내용은 글을 작성하면서 설명할 예정이다.

     

     도출된 두가지의 중요한 요소를 중심으로 구현하기 위해
    AWS의 여러 서비스들과(Lambda, DynamoDB, Event Bridge), UE5 그리고 테스트 환경으로 Node.js를 사용하게 되었다.

     


    2. 스팀 세션 티켓 발급

    1. 스팀 세션 티켓

    가장 먼저 클라이언트 검증을 구현해보자.

    당연한게도 검증이 필요한 이유는, 데이터 위변조를 막기 위해서이다.
    지금 실행한 클라이언트가 위변조된 프로그램인지 혹은, 계정인지를 확인하는 것은 온라인 게임으로써 불가피하다.

     

    이 방식으로 가장 좋은 방법중 하나가 바로 회원제, 즉 ID와 비밀번호를 이용한 로그인 방법이다.
    하지만 내 프로젝트는 Steam 플랫폼을 염두해놓았기 때문에 스팀 세션 티켓을 이용할 예정이다.

     

     스팀 세션 티켓은 사용자의 신원을 인증하기 위해 정당한 스팀 사용자임을 확인하기 위해 발급하는 고유한 암호화 데이터이다.
    클라이언트는 스팀이 제공하는 API를 통해 티켓을 생성 및 송신하고,
    스팀 서버는 해당 티켓을 수신하고 검증한다.
    이때 검증한 클라이언트만 적법한 클라이언트로 인정하게 된다.

     

    개발자는 이 API를 클라이언트에 잘 녹여내면 된다.
    그런데 가장 큰 문제는 스팀웍스에 가입되어야 한다...
    스팀웍스는 스팀에 게임을 출시하는 개발자들을 위한 커뮤니티 및 지원 센터라고 할 수 있는데,
    이곳에 가입하려면 기본적으로 100달러와 사업자 등록이 필요하다.

    돈은 내면 되긴하는데, 사업자 등록이 되어있지 않기 때문에,
    일단 테스트 환경으로 더미 스팀 세션 티켓을 발급하도록 코드를 짰다.

     

    2. 구현 방식

    스팀 세션 티켓 발급 시퀀스 다이어그램

    1. 클라이언트 요청 및 환경 확인

    클라이언트가 API 서버로 스팀 티켓, ID, 본인 닉네임 정보를 담아 POST 요청을 보내고,
    이를 수신한 람다는 AWS System Manager에서
    Parmeter Store에 저장된 변수를 조회해서, Prod(배포 환경), Dev(개발 환경)인지 확인한다.

     

    2. 환경에 따른 동작 방식 결정

    테스트 환경과 실제 배포 환경 구분을 위해
    Prod(배포 환경), Dev(개발 환경)을 나누었다.
    해당 변수를 바꾸기만 해도 모든 람다함수의 동작 방식이
    자동으로 바뀌면서 해당 환경에 맞게 설정된다.


    Dev 환경에선 더미 스팀 세션 티켓이 발급되고, (이때는 티켓 유효성은 항상 참인 셈)
    Prod 에서는 실제 스팀 서버로 HTTP 요청을 통해 티켓의 유효성을 직접 검증한다.

     

    3. DynamoDB 조회 및 데이터 처리

    이후 모든 검증이 끝나고, 확정된 플레이어 ID를 키 값으로 DynamoDB 테이블을 조회하는데, (Users_Prod 또는 Users_Dev)
    이때 조회 결과 존재 유무에 따라 신규 유저와 기존 유저로 나누어진다.

    신규 유저는 자동으로 DB에 새로운 계정 데이터를 생성하고, 기존 유저는 로그인 시간만 업데이트한다.

     

    4. JWT 발급 및 클라이언트 응답

    해당 유저 특정까지 마친 서버는 이제 JSON에 JWT 토큰을 담아서 클라이언트에게 응답하게 되면
    스팀 세션 티켓을 통한 사용자 인증은 끝나게 된다.
    이제 클라이언트는 가지고 있던 JWT 토큰을 통해 서버와 안전하게 소통이 가능하다.


    3. 매치메이킹

    이제 서버는 접속한 모든 클라이언트가 합법적인 JWT를 가지고 있음을 알기에,
    이를 통해 클라이언트의 데이터 위변조를 막으면서 소통할 수 있게 된다.

    구현방식

    1. 매치메이킹 시작

    클라이언트가 저장된 JWT를 포함된 HTTP 요청을 API 서버에 보내게 된다.
    요청을 받은 람다함수는 JWT를 검증하여 playerId를 추출하고, 해당 Id를 통해
    DynamoDB를 조회함으로써 사용자의 MMR(경쟁전 점수)를 가져온다.

     

    AWS GameLift FlexMatch에는 매칭 대기열을 위해 티켓을 구성하는 기능이 있는데,
    각 클라이언트가 대기열에 정상적으로 대기를 하고있다는 증명을 위함이다.

     

    람다함수는 playerId, MMR을 Players라는 배열에 담아서 Gamelif FlexMatch에 보낸 뒤,
    이 요청을 받은 FlexMatch는 대기열에 해당 플레이어를 등록하고, 고유의 문자열을 반환하는데
    이게 바로 티켓이다.

     

    그리고 람다는 TicketId를 DynamoDB MatchmakingTickets 테이블에 Searching 상태로 저장한 뒤
    클라이언트에게 반환한다.

     

    2. GameLift FlexMatch의 매치메이킹 이벤트 처리

     GameLift는 클라이언트의 직접적인 개입 없이 백그라운드에서 매칭을 수행하며, 상태 변환은
    AWS EventBridge를 통해 비동기적으로 DB에 반영된다.

     

    GameLift FlexMatch의 매치메이킹 방식은 다음과 같은 내용으로 매칭을 수행한다.

     

    {
        "ruleLanguageVersion": "1.0",
        "playerAttributes": [
            {
                "name": "skill",
                "type": "number",
                "default": 50
            }
        ],
        "teams": [
            {
                "name": "TeamA",
                "maxPlayers": 1,
                "minPlayers": 1
            },
            {
                "name": "TeamB",
                "maxPlayers": 1,
                "minPlayers": 1
            }
        ],
        "rules": [
            {
                "name": "SkillDistanceRule",
                "description": "양 플레이어의 실력 차이가 10 이하일 때만 매칭",
                "type": "distance",
                "measurements": [
                    "avg(teams[TeamA].players.attributes[skill])"
                ],
                "referenceValue": "avg(teams[TeamB].players.attributes[skill])",
                "maxDistance": 10
            }
        ],
        "expansions": [
            {
                "target": "rules[SkillDistanceRule].maxDistance",
                "steps": [
                    {
                        "waitTimeSeconds": 10,
                        "value": 20
                    },
                    {
                        "waitTimeSeconds": 20,
                        "value": 50
                    },
                    {
                        "waitTimeSeconds": 30,
                        "value": 100
                    }
                ]
            }
        ]
    }

     

    - 규칙 세트 평가: 사전에 설정해둔 규칙세트를 통해 대기열에 있는 티켓들을 스캔한다.

    규칙 세트에는 팀 구성 인원수, MMR 차이의 허용 범위 등이 있으며, 이 규칙으로 매칭을 성사 시킨다.

     

    - 세션 할당: 조건을 만독하는 플레이어 그룹이 형성 되면, 해당 그룹 데이터를 게임 세션 대기열로 넘기게 된다.
    해당 그룹을 실제 데디케이티드 서버로 할당하게 되는 단게이다.

     

    3. 매치메이킹 상태 확인 (폴링) 및 서버 할당

    두 손에 TicketId를 꼭 쥐고 있는 클라이언트는 3초에 한번씩 이 티켓을 서버에게 들이 밀면서
    매칭이 되었는지 확인하는데 (이걸 폴링이라 한다), 이때도 계속해서 JWT를 검증한다.
    매칭 확인은 DB에서 티켓 정보를 조회하고, 그 티켓의 Status 값을 통해 확인할 수 있다.
    (Searching, Completed, Placing, Failed)

     

    이렇게 폴링이 지속되다, Gamelift에서 상태변화가 일어나게 되면서 Status가 Completed로 업데이트된다면,
    3초 안에 클라이언트는 이를 확인하고, Gamelift로 받은 서버 데이터를 이용해 서버로 접속을 시도한다.
    당연히 이때도 Gamelift는 클라이언트의 유효성을 확인하고, 검증이 끝나면 서버에 접속하게 된다.

     

    4. 테스트

    스팀 세션 티켓 발급과 매치메이킹이 정상 작동하는지
    크게 두가지 환경에서 테스트를 진행했다.

    1. 에디터 환경

    에디터 테스트 환경

    -  Anywherefleet

    - 서버 프로세스 1대, Standalone 클라이언트 4대

    스팀 세션 티켓 발급

     

    각 클라이언트가 DB에 정상적으로 생성되었음이 확인되었다.
    이제 MMR(SkillRating)을 조작하여 점수차가 존재할때, 매치메이킹 상태를 테스트해보자
    현재 규칙 세트에 따르면, 점수차가 1000점이상 나면 매칭이 잡혀선 안된다.

    왼쪽상단, 오른쪽상단 클라이언트의 점수를 2000으로 세팅하고 매칭을 테스트해보자

     

    1. 왼쪽상단(2000점), 오른쪽하단(1000점) 매치메이킹 시작

     

     

     

    예상대로 거의 1분이 되어가도 매칭이 잡히지 않는 모습이다.

    이제 같은 2000점인 오른쪽 상단 클라이언트 매치메이킹을 시작해보자

     

    2. 오른쪽 상단(2000점) 매치메이킹 시작

     

     

    매칭 시작 10초 후 바로 매칭이 잡힌 뒤, 서버 프로세스에 잘 할당이 되었고, 

    오른쪽하단(1000점)은 설정된 매칭한계시간을 초과하자 매칭에 실패했다.

     

    2. Node.js 스크립트 환경

    에디터는 최대 4명의 클라이언트만 실행 가능하고, 엄청난 컴퓨팅 리소스가 필요해서 대규모 테스트에는 부적합하다.
    그래서 Node.js 스크립트 환경에서 대규모 부하 테스트를 진행해 볼 것이다.


    결국은 서버에 클라이언트가 MMR에 따라 매칭이 성사되고,
    그에따라 서버 프로세스가 할당 된다면 매치메이킹 시스템이 정상적으로 작동한다는 것이 증명되는 셈이다.
    따라서, 굳이 실제 클라이언트가 아닌, 가상 봇들을 생성하고 매칭에 참여할 수 있도록 설정해 테스트를 진행한다.

     

    Node.js 스크립트 환경

    - Anywherefleet

    - 서버 프로세스 30대, 매칭 봇 60명에서 브론즈 30%, 실버 50%, 골드 20%의 비율

    - 1대1 대전, 초기 점수 차이 10 이하일 때 매칭, 이후 20, 50, 100으로 점진적으로 확대

     

     

    결과


    성공 인원 56명, 실패 인원 4명

    성공한 인원들 전부 알맞는 점수풀에서의 상대가 매칭되었고,

    실패한 인원들은 전부 점수차가 커서 매칭이 실패했기에, 문제없이 매칭이 되었음을 확인했다.

     

    한 1000명정도 테스트를 진행해보려고 했지만, 아마 컴퓨터가 버티지 못할 것 같아 아쉽게도 무산되었다.

    그렇다고 EC2를 쓰자니 돈이 나가서 다른 방식을 찾아야 할 듯 하다.


    마치며

    머리가 녹아내릴 것만 같으면서도 동시에 시야가 확장된 느낌입니다.

    그래서 이번에 배운 기술로 인벤토리, 마켓을 구현해 볼 예정입니다.

    UI도 꾸준히 건들고 있지만, 클라이언트는 도통 손이 가지 않네요.

    결국 클라이언트는 아이디어 싸움이기에 개발이라기보다, 창작에 가까워서 그런 것 같습니다.

     

    그럼 이만 다음 글에서 뵙겠습니다.