본문 바로가기
Development/C#

멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 78일차

by Mobics 2025. 3. 20.

 

목차


    오목 게임 만들기

    25.03.20

    Ranking Popup Panel 만들기

    : 지난 시간에 이어서 제작

     

    >> 현재 User 랭킹 구현

    • 현재 User의 데이터를 받아서 가장 상단에 표시되도록 구현
    • 현재 User의 랭킹도 랭킹 목록에 위치하도록 구현 --> 기존 Player Data를 담아둔 배열을 List로 변환하여 User Data를 넣어주고 다시 배열로 전환하여 구현

    --> 'Ranking Panel Controller'에 각각 바인딩

     

    >> Panel이 중복으로 열리지 않게끔 수정

    : rankingPanelObject가 이미 존재하는지 체크하고 이미 있으면 추가로 생성하지 않도록 구현

     

    >> 등수 표기

    : 랭킹 목록에 있는 Player의 랭킹 뿐만 아니라 가장 상위에 있는 User의 랭킹도 반영돼야 한다.

     

    1. 'User Rank Cell'의 자식으로 'Rank Text' 생성

    --> Anchor는 Alt + Shift

     

    2. 'User Image', 'RankName Text', 'Victories Text'의 위치 및 사이즈 조정

     

    3. 'Rank Cell' Prefab에도 똑같이 만들어서 적용

    : 적용된 모습 (Rank Cell Prefab은 임시로 배치)

     

    4. 코드로 적용

    • MainPanelController.cs
    • RankingPanelController.cs
    • RankCellController.cs

     

    5. 함수에 맞게 각각 바인딩

     

    Main에 Merge하기

    : 이때까지 Merge한 사람이 나밖에 없어서 Merge하는 데에는 큰 문제가 없었지만, Merge가 되고나서 아래와 같은 에러가 발생했다.

    --> 예상하기로는 원래 main branch에서는 PanelController.cs가 '02. Scripts-Main' 폴더에 있었는데, 내가 branch를 만들고 랭킹 시스템을 구현하는 중에 '02. Scripts-UI' 폴더로 옮겼었다. 이게 merge 되면서 일으킨 문제같다.

     

    >> 해결방법

    원래는 파일명과 클래스명이 다를 때 나오는 에러라는데, 나는 파일명과 클래스명이 동일했다. 더 자세히 찾아보니 컴파일 에러라고 해서 그냥 클래스를 삭제한 뒤, 다시 만드니 문제가 해결됐다.

     

    └ 구현된 모습

     

    최종 코드

    >> MainPanelController.cs

    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    
    public class MainPanelController : MonoBehaviour
    { 
        [SerializeField] private Canvas canvas;
        [SerializeField] private GameObject rankingPanelPrefab;
        
        private GameObject _rankingPanelObject;
    
        private ScoreInfo _userScore = new ScoreInfo() // 임시 유저 정보
        { Nickname = "Gildong", WinCount = 53, LoseCount = 25, DrawCount = 7, Rank = 10 };
    
        private ScoreInfo[] _playerScores = new ScoreInfo[] // 임시 유저 더미 정보
        {
            new ScoreInfo { Nickname = "Test01", WinCount = 5, LoseCount = 5, DrawCount = 0, Rank = 17 },
            new ScoreInfo { Nickname = "Test02", WinCount = 57, LoseCount = 32, DrawCount = 6, Rank = 9 },
            new ScoreInfo { Nickname = "Test03", WinCount = 14, LoseCount = 23, DrawCount = 2, Rank = 16 },
            new ScoreInfo { Nickname = "Test04", WinCount = 26, LoseCount = 16, DrawCount = 4, Rank = 15 },
            new ScoreInfo { Nickname = "Test05", WinCount = 67, LoseCount = 40, DrawCount = 4, Rank = 13 },
            new ScoreInfo { Nickname = "Test06", WinCount = 48, LoseCount = 28, DrawCount = 9, Rank = 10 },
            new ScoreInfo { Nickname = "Test07", WinCount = 74, LoseCount = 86, DrawCount = 14, Rank = 13 },
            new ScoreInfo { Nickname = "Test08", WinCount = 3, LoseCount = 1, DrawCount = 0, Rank = 17 },
            new ScoreInfo { Nickname = "Test09", WinCount = 113, LoseCount = 109, DrawCount = 23, Rank = 8 },
            new ScoreInfo { Nickname = "Test10", WinCount = 1024, LoseCount = 1011, DrawCount = 50, Rank = 6 },
        };
    
        /// <summary>
        /// '대국 시작' 버튼을 눌렀을 때 호출되는 함수
        /// </summary>
        public void OnClickPlayButton()
        {
            GameManager.Instance.StartGame();
        }
    
        public void OnClickExitButton()
        {
            
        }
    
        /// <summary>
        /// '로그아웃' 버튼을 눌렀을 때 호출되는 함수
        /// </summary>
        public void OnClickLogoutButton()
        {
            
        }
    
        #region Main Menu 버튼 클릭 함수
    
        /// <summary>
        /// '랭킹' 버튼을 눌렀을 때 호출되는 함수
        /// </summary>
        public void OnClickRankingButton()
        {
            if (_rankingPanelObject != null) return;    // 이미 RankingPanel이 있다면 추가로 생성되지 않도록
            
            if (canvas != null)
            {
                _rankingPanelObject = Instantiate(rankingPanelPrefab, canvas.transform);
                var rankingPanelController = _rankingPanelObject.GetComponent<RankingPanelController>();
                rankingPanelController.CreateRankCells(_playerScores, _userScore);
            }
        }
    
        /// <summary>
        /// '내 기보' 버튼을 눌렀을 때 호출되는 함수
        /// </summary>
        public void OnClickNotationButton()
        {
            
        }
    
        /// <summary>
        /// '상점' 버튼을 눌렀을 때 호출되는 함수
        /// </summary>
        public void OnClickShopButton()
        {
            
        }
    
        /// <summary>
        /// '설정' 버튼을 눌렀을 때 호출되는 함수
        /// </summary>
        public void OnClickSettingsButton()
        {
            
        }
        
        #endregion
    }

     

    >> RankingPanelController.cs

    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using TMPro;
    using UnityEngine;
    using UnityEngine.UI;
    
    public struct ScoreInfo // 임시 코드
    {
        public string Nickname;
        public int Rank;
        public int WinCount;
        public int LoseCount;
        public int DrawCount;
    }
    
    [RequireComponent(typeof(PanelController))]
    public class RankingPanelController : PanelController
    {
        // 현재 플레이 중인 유저의 정보를 넣어줄 Image와 Text
        [SerializeField] private Image userImage;
        [SerializeField] private TMP_Text userRankText;
        [SerializeField] private TMP_Text ranknameText;
        [SerializeField] private TMP_Text victoriesText;
        
        [SerializeField] private GameObject rankCellPrefab;
        [SerializeField] private Transform content;
    
        /// <summary>
        /// 현재 플레이중인 유저의 정보를 받아 Cell을 만들어주는 함수
        /// </summary>
        private void InsertUserRankCell(ScoreInfo scoreInfo, int userRank)
        {
            int totalCount = scoreInfo.WinCount + scoreInfo.LoseCount + scoreInfo.DrawCount;
            float winRateFloat = rankCellPrefab.GetComponent<RankCellController>().GetWinRate(scoreInfo);
            int winRate = Mathf.RoundToInt(winRateFloat);
            
            userRankText.text = userRank.ToString();  // 유저의 랭킹 숫자를 문자열로 변환하여 대입
            ranknameText.text = $"{scoreInfo.Rank}G {scoreInfo.Nickname}";
            victoriesText.text =
                $"{totalCount}T {scoreInfo.WinCount}W {scoreInfo.LoseCount}L {scoreInfo.DrawCount}D ({winRate}%)";
        }
    
        /// <summary>
        /// Content의 자식으로 Cell을 만들어주는 함수
        /// </summary>
        /// <param name="scoreInfos"></param>
        public void CreateRankCells(ScoreInfo[] scoreInfos, ScoreInfo userScoreInfo)
        {
            int rankNumber = 1;         // 랭킹 숫자를 표시하기 위해 선언
            int userRankNumber = 99;    // 유저 랭킹 초기화
            
            // User Score를 전체 Player Score에 추가하여 생성
            var allScores = new List<ScoreInfo>(scoreInfos) { userScoreInfo }; 
            
            // Rank가 낮을수록 먼저 표시되도록 정렬하고 Rank가 같으면 승률이 높으면 먼저 표시되도록 정렬 후 배열화
            var sortedScores = allScores.OrderBy(player => player.Rank)
                .ThenByDescending(player => rankCellPrefab.GetComponent<RankCellController>().GetWinRate(player))
                .ToArray();
            
            foreach (var scoreInfo in sortedScores)
            {
                if (scoreInfo.Nickname == userScoreInfo.Nickname)
                {
                    userRankNumber = rankNumber; // 유저의 랭킹 저장
                }
                
                var rankCellObject = Instantiate(rankCellPrefab, content);
                var rankCellController = rankCellObject.GetComponent<RankCellController>();
                rankCellController.SetCellInfo(scoreInfo);
                rankCellController.rankText.text = rankNumber.ToString();   // 랭킹 숫자를 문자열로 변환하여 대입
                rankNumber++;   // 랭킹 숫자를 높여줌
            }
    
            InsertUserRankCell(userScoreInfo, userRankNumber);
        }
    }

     

    >> RankCellController.cs

    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class RankCellController : MonoBehaviour
    {
        [SerializeField] private Image userImage;
        [SerializeField] private TMP_Text ranknameText;
        [SerializeField] private TMP_Text victoriesText;
    
        private int _totalCount;
        
        public TMP_Text rankText;
        
        /// <summary>
        /// RankCell에 들어갈 정보를 서버로부터 받아서 넣어주는 함수
        /// </summary>
        public void SetCellInfo(ScoreInfo scoreInfo)
        {
            float winRateFloat = GetWinRate(scoreInfo);
            int winRate = Mathf.RoundToInt(winRateFloat);
            
            // TODO: 서버로부터 정보를 받아서 넣기
            ranknameText.text = $"{scoreInfo.Rank}G {scoreInfo.Nickname}";
            victoriesText.text = 
                $"{_totalCount}T {scoreInfo.WinCount}W {scoreInfo.LoseCount}L {scoreInfo.DrawCount}D ({winRate}%)";
        }
    
        /// <summary>
        /// 승률을 구하는 함수
        /// </summary>
        /// <param name="scoreInfo">다른 User의 데이터(임시 코드)</param>
        /// <returns>float형식의 승률</returns>
        public float GetWinRate(ScoreInfo scoreInfo)
        {
            _totalCount = scoreInfo.WinCount + scoreInfo.LoseCount + scoreInfo.DrawCount;
            if (_totalCount == 0) return 0f;
            
            return (scoreInfo.WinCount / (float)_totalCount) * 100;
        }
    }