6. ⭐ (마무리) 리그 오브 레전드 유저신고 대시보드 (streamlit)

chaechae·2023년 5월 30일
0
post-thumbnail

(썸네일: 탈주한 유저)

😀먼저, 지금까지 완성된 streamlit 대시보드도 같이 보여드리면서
저번 포스팅에 언급한 유저의 매치 참여도 부분은 중간에 더 설명해드리려고 합니다!

1. 소환사 아이디 입력

사이드바에 유저의 아이디를 입력할 수 있도록 만들었습니다.
유저 신고 사유에 대표적인 목록을 추가했고, 흔히 게임이 끝나고 신고하는 그 컨셉으로 만들었습니다.😅

2. 유저의 해당경기 참여도 요약

경기가 끝나고 유저신고를 하게되면 아래 처럼 해당 경기에 대한 유저의 정보 및 전반적인 참여도와 스킬활용 등을 보실 수 있습니다.


(예시사진: 유투버 해물파전님의 최근경기입니다.)

ㆍ경기요약 및 Radar Charts

info

챔피언, 스펠, 아이템 , KDA 등등 흔히 통계사이트에서 볼 수 있는 정보들 뿐만 아니라,
AFK 여부를 볼 수 있습니다. 저번에 자리비움에 관련해서 포스팅 했던 것 처럼 5분 이상일 경우 '5분' , 10분 이상일 경우 '10분' 으로 표시됩니다.

Radar Charts

매치에서 가장 기본적으로 보는 변수들을 radar charts 로 나타냈습니다. min-max 스코어를 이용해서 단위를 통일했습니다. (해당매치에서 모든 참가자들의 평균과 비교합니다.)

3. 참여도 점수 / 스킬

참여도 / RANK

유저의 참여도를 좀 더 세부적으로 나눠봤습니다. radar chart 의 경우 대표적인 변수를 단순히 비교했지만, 참여도 점수의 경우 크게 Attack(공격) Object(오브젝트) Utility(서포터) 세개로 나눠집니다.

ㆍ참여도 계산을 어떻게 해야할까?

리그 오브 레전드에는 게임시작전 원하는 포지션을 정하고 매치경기가 시작됩니다.
즉, 포지션별로 해야하는 역할들이 어느정도 정해져있기 때문에 경기가 끝나고 잘했는지 못했는지 봐야하는 변수들도 어느정도?는 포지션별로 다르게 봐야한다고 생각했습니다.

이에 관해서 저의 첫번째 포스팅 경기평점부분에 자세히 설명했었는데요!

한 마디로 단순히 KDA, 딜량, CS, 시야점수 만 보는게아니라 (물론 중요하지만) 각 포지션별 중요하다고 생각 되는 변수에 가중치를 좀 두어서 계산해야되지 않을까? 라는 저의 생각입니다! 실제로 매치가 끝나고 측정되는 RANK 시스템이 어떻게 돌아가는지 모르겠지만요 🤔

그러기 위해서는 먼저 아래와 같이 공격성, 오브젝트, 보조 지표 변수를 선별해야했습니다.

① 공격성 지표

챔피언과 상호작용 했을 때 나오는 결과와 관련된 변수들로 선정했습니다.
특히, 탑/미드/원딜 포지션에서 중요하다고 생각합니다.

② 오브젝트 지표

오브젝트에는 크게 타워,터렛/ 몬스터,미니언이 있습니다. 그리고 GOLD의 경우 이와 가장 관련된 지표라고 생각되어서 포함했습니다. 특히, 정글 포지션에서 중요하다고 생각합니다.

③ 보조/CC/서포터 지표

팀원을 보호했음을 가장 잘 나타내는 변수들로 선정했습니다. 특히, 서포터 포지션에서 중요하다고 생각합니다.

💻 CODE

그다음 포지션마다 각 지표비중을 다르게 두어서 아래와 같이 가중치를 정의해주고, 표준화해서 점수를 1~10 점으로 나타냈습니다.

   # top middle botom
    weights = {
        # ----- 공격성 지표 비중 40%
                'totalDamageDealtToChampions': 0.2, 
                'soloKills': 0.15,    
                'multikills': 0.05, 
                'kda': 0.1, 

        # ----- 오브젝트 지표 비중 30% 
                'goldEarned':0.05,
                'totalCS': 0.05,
                'damageDealtToTurrets': 0.1,
                'damageDealtToObjectives': 0.1, 
                'dragonTakedowns':0.01,
                'baronTakedowns':0.01,
                 
        # ----- 보조/CC/서포터 지표 비중 30 %
                'damageSelfMitigated': 0.15, 
                'visionScore': 0.1,  
                'enemyChampionImmobilizations':0.05 , 
                'effectiveHealAndShielding':0.01 
           } 
 
    # jungle
    weights_JUNGLE = {
        # ----- 공격성 지표 비중 30%
                'totalDamageDealtToChampions': 0.15, 
                'soloKills': 0.1,  
                'multikills': 0.05, 
                'kda': 0.1, 

        # ----- 오브젝트 지표 비중 40%
                'damageDealtToTurrets': 0.1,
                'damageDealtToObjectives': 0.1, 
                'dragonTakedowns':0.05,
                'baronTakedowns':0.05,
                'totalCS': 0.05,  
                'goldEarned':0.05,

        # ----- 보조/CC/서포터 지표 비중 30%
                'damageSelfMitigated': 0.05,
                'visionScore': 0.1,  
                'enemyChampionImmobilizations':0.05 , 
                'effectiveHealAndShielding':0.01

            } # (0.01)'effectiveHealAndShielding': 0.01
    
 	# supporter
    weights_utility = {
        # ----- 공격성 지표 비중 20%
                'kda': 0.15,  
                'totalDamageDealtToChampions': 0.05, 
                'soloKills': 0.01, 
                'multikills': 0.01,  

        # ----- 오브젝트 지표 비중 15%
                'goldEarned':0.05,
                'totalCS': 0.001,   
                'damageDealtToObjectives': 0.05, 
                'damageDealtToTurrets': 0.05 , 
                'dragonTakedowns':0.001,
                'baronTakedowns':0.001,

        # ----- 보조/CC/서포터 지표 비중 65%
                'visionScore': 0.2,                  
                'effectiveHealAndShielding' : 0.2,
                'enemyChampionImmobilizations': 0.2,
                'damageSelfMitigated': 0.05, 
                } 
    
    

def score3 (match_score):
    score_attack = match_score[['championName',
                                'totalDamageDealtToChampions','soloKills','multikills','kda']]
    score_object = match_score[['championName','damageDealtToTurrets','damageDealtToObjectives','dragonTakedowns',
                                'baronTakedowns','totalCS','goldEarned']]
    score_util = match_score[['championName','visionScore','effectiveHealAndShielding',
                                'enemyChampionImmobilizations','damageSelfMitigated' ]]                


    score_attack['attack_score'] = score_attack.sum(numeric_only=True, axis=1)
    score_object['object_score'] = score_object.sum(numeric_only=True, axis=1)
    score_util['util_score'] = score_util.sum(numeric_only=True, axis=1)
    score_3 = score_attack[['championName', 'attack_score']].merge(score_object[['championName', 'object_score']], on='championName').merge(score_util[['championName', 'util_score']], on='championName')

    # MinMaxScaler를 사용해 attackscore, objectscore, utilscore 값을 1부터 10까지의 값으로 변환
    scaler = MinMaxScaler(feature_range=(1, 10))
    score_3[["attack_score", "object_score", "util_score"]] = scaler.fit_transform(score_3[["attack_score", "object_score", "util_score"]])
    return score_3

스킬활용

유저가 kill/death/assist 했을 때 기록된 skill 흔적을 가져와서 그래프로 나타내봤습니다. 해당 유저가 스킬을 얼마나 맞춰서 KDA 결과를 냈는지 확인이 가능합니다. 스킬활용은 아무래도 게임을 적극적으로 참여했다는 증거가 되므로 넣었습니다.

ㆍ스킬로그 가져오기

의도적으로 죽음 포스팅때 유저가 death 했을 때 누구에게 맞았고, 어떤 스킬을 사용해서 딜교환을 했는지 기록 되고 있는거 기억하시나요ㅎㅎ!? 그래서 kill/death/assist 했을때 사용한 스킬데이터를 가져왔습니다.

필요했던 과정

저는 스킬 이름을 한글로 바꿔주기위해 JSON에서 불러온 이름을 기준으로 진행했는데, 필요없다면 그냥 있는 그대로 가져와도 될듯합니다.

스킬을 한글로 바꿔주기위해 champion json 을 따로 불러와서 스킬정보를 저장하고 , skill log 에 남는 spellName 과 merge해서 한글로 나타냈습니다.

그런데 한글로 바꾸면서 까다로웠던 점이 있었습니다. json에서 불러들어온 해당 챔피언의 spellname 의 경우 앞글자가 대문자로 나타나기 때문에 모두 .lower() 해서 소문자로 바꿔주는게 필요했습니다.

또한 챔피언 중에서 요네의 경우 q스킬이 3타까지 가능합니다. 렝가 강화q도 비슷한 형태이죠. skill log에 남는 데이터의 경우 전부 YoneQ 형태가 아닌 1타부터 3타까지 모두 구분하고 있었습니다. yoneq1 yoneq2 yoneq3 처럼 말이죠.

저는 yoneq1,2,3 형태의 스킬인 경우 모두 Q 스킬이기 때문에 'yoneq' , '필멸의 검' 으로 바꿔주고 집계했습니다. 또한 기본공격이 주가 되는 챔피언도 있기 때문에 기본공격도 따로 추가했습니다. 보통 기본공격의경우 championName + basicattack 형태로 남아있었습니다.

🚨 부족한점

① 공격형 스킬이 아닌 , 팀원에게 버프를 걸어주는 (힐/방어막/공격속도 등) 스킬의 경우 로그에 남지 않기때문에 측정이 불가능했습니다. (스킬이 아닌 'effectivehealandshield' 라는 변수에 total 얼마나 걸어줬는지에 대한 정보는 있습니다.)

② 크게 QWER 4개의 스킬이름으로 집계하려니 더 어렵게 되버린...?
챔피언 중에서 '니달리', '아펠리오스' 같이 변신하거나 무기자체를 변경하는 경우, 스킬은 QWER 이지만 사실 8개 이상의 스킬이 있는 셈입니다. 그렇기 때문에 skill log에 남은 spellName과 champion.json 에서 제공하는 spellname을 merge 하기 조금 애매했습니다. 이 또한 한글로 바꾸려다가 괜히 더 어려워진것같습니다.😅 (그냥 표기된 그대로 할걸 그랬나?)

-> 이런 case들 을 제외하고 직관적으로 spellName이 있는 경우 정확하게 측정이 가능합니다.


4. GOLD 핵 부분

비정상적인 GOLD 로그가 발견되면 해당 구간에 어떤 로그들이 남았는지 시각화해서 보여줍니다. 비정상적으로 판단되는 행동이 없는 경우 테이블 형태로 확인할 수 있습니다. (자세한 내용은 GOLD 포스팅에서 보실 수 있습니다!)


ㆍAFK 미니맵 애니메이션 / 머문 라인비율

유저의 자리비움/탈주 를 확인하기위해 추가했습니다. 유저의 전반적인 이동경로 및 라인별로 얼만큼 머물렀는지 확인할 수 있습니다. ( 자세한 내용 보러가기 ! AFK, 미니맵 애니메이션 )

※ 탈주했을 경우

5. 타워/챔피언에게 의도적으로 죽는경우

신고받은 유저가 DEATH 했을 때 딜교환을 얼만큼 했고, 어떻게 죽었는지 확인 가능합니다.
전체/ 15분전/ 15분이후 로 나눠서 해당 챔피언이 가한 피해량은(흰색) , 받은 피해량(파란색-챔피언/붉은색-타워/몬스터) 으로 나타냈습니다. (자세한 포스팅보기)

(의도적인 죽음으로 예측되는 유저의 데이터)

🤪마무리/느낀점

이로서 유저 신고 대시보드 만들기가 끝났습니다..! 부족한것도 많고 결국 애매한 부분도 많지만
글 정리하는것 까지 약 두달반정도 걸린 것 같습니다. api, json 라는것도 알게되고, streamlit도 처음 사용하면서 의도치 않게 css, html도 공부가 되었구요.😅

(특히, 참여도/스킬 부분 layout을 만들때, Design 컴포넌트를 쉽게 사용할 수 있도록 해주는 'MUI' 자바스크립트 라이브러리를 알게 되어서 사용했습니다. streamlit 에서도 사용 가능하더군요. 너무 이야기가 길어질까봐 설명은 생략했습니다. 혹시 궁금하신 분은 streamlit 커뮤니티에서 보실 수 있습니다!)
그리고, 완성된 streamlit을 외부에서 접속 할 수 있더라구요! 정리해서.. 나중에 올려보도록 하겠습니다!

한 때 롤을 그냥 플레이만하는 유저였지만 이렇게 운영진 입장에서 바라보면서 문제점을 어떻게 해결하면 좋을지 고민하는게 스스로 되게 재밌는 경험이었습니다. (이렇게 실시간으로 남는 게임 데이터를 저같은 백수도 볼 수 있다는게 참 좋은것 같아요. 감사합니다 RIOT..)

사실 어떤 대단한 분석방법론(?)을 쓰는게 아니기도하고, 게임 안에서 끊이지 않는 문제점들을 주제로한 간단한 프로젝트라고 생각했는데, 그럼에도 고민하는 과정은 결국 간단하지 않았습니다. 운영진 입장에서 악의적인 유저를 제재하는 건 꼭필요하고 유저 관리를 잘했냐 못했냐에 따라 게임평판 뿐만 아니라 어느정도 회사 매출에 직결되는 부분이라 생각이 들었습니다. 그래서 "잘 구분해서 제제해야 한다" 라는 압박감이 저에게도 느껴졌습니다. 막연히 생각할게 아니라 조심스러워 지더군요. 게임커뮤니티에 들어가서 직접 물어보기도 하고 , 글을 찾아보기도 하고 어떻게해야 해결할 수 있을지 고민하는데 시간을 많이 쓴것 같습니다..

결론적으로 완벽하게 제재하기는 어렵다고 느꼈지만. 게임경기가 끝나고
유저의 신고가 들어왔을 때 해당 데이터를 바로 볼 수 있도록 대시보드를 만들었다는 점
결국 사람의 판단이 필요한 부분 을 굳이 게임을 다 돌려보지 않고 어느정도확인 할 수 있다는 점 에서 작은도움이 되지 않을까 생각이 듭니다ㅎㅎ..

to be continued..?

확실히 리그오브레전드 api를 몇달간 뜯어보니까 어떤 변수가 있는지, 어떻게 기록되는지 전부 뇌에 새겨져 있기때문에, 이대로 버리기 아깝다는 생각이 들었습니다. 24시간 api key를 받는게 귀찮아서 아예 개발자 등록도 했구요. (personal key 보다 제한이 적어서 좀 더 원활하게 데이터를 수집할 수 있더라구요)

그래서 이번에는 랭크별로 데이터를 수집해서 좀 더 '데이터분석'스러운(?) 재밌는 주제로 찾아오려고 합니다 (물론 아직 주제는 안정했다능ㅎ... 뭐 갑자기 다른거 할수도 있구요.) 끝까지.. 부족한 글 읽어주셔서 감사합니다!! 😀


profile
게임 혹은 다양한 컨텐츠가 있는 곳을 좋아합니다. 시리즈를 참고하시면 편하게 글을 보실 수 있습니다🫠

0개의 댓글