본문 바로가기
Development/Unity BootCamp

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

by Mobics 2025. 3. 19.

 

목차


    ※ 25.03.18(76일차)에는 예비군 작계 훈련으로 빠짐


    오목 게임 만들기

    25.03.19

    Ranking Popup Panel 만들기

    : 'Popup Panel'의 Prefab Variant로 생성

     

     급수에 따라 높은 급수가 더 위에 위치하도록 구현 (동일 급수인 경우 승률이 높은 유저가 위에 위치)

     

    >> 'Panel'의 자식으로 'Scroll View' 생성

    --> Anchor는 Alt + Shift

     

    >> 'Content'에 'Content Size Fitter', 'Vertical Layout Group' 추가

     

    >> Scrollbar Vertical

    : Color는 Alpha 값을 0으로 설정

    --> Scrollbar Horizontal은 삭제

     

    >> Handle

     

    >> Divider

    --> Anchor는 Alt + Shift

     

    └ 랭킹을 구현하기 위한 Cell 만들기

    >> 'Panel'의 자식으로 'User Rank Cell' 생성

    : UI-Image로 생성, User 정보는 가장 위에 배치하기 위해

     

    ※ Width는 'Content'의 자식으로 생성해서 위치를 조정했을 때 나온 수치

    --> Anchor는 Alt + Shift

     

    >> 'User Rank Cell'의 자식으로 'User Image', 'RankName Text', 'Victories Text' 생성

    --> Anchor는 Alt + Shift

     

    >> 만든 'User Rank Cell'을 'Content'의 자식으로 복붙 후, 이름을 'Rank Cell'으로 바꾸고 Prefab화

    : 이후 Hierarchy에서 삭제

     

    >> 코드 작성

    • RankCellController.cs : RankCell의 내용을 넣어주는 Class
    • RankingPanelController.cs : 'Content'의 자식으로 RankCell을 만들어주는 함수

    --> 아직 서버가 구현되지 않았기 때문에 현재는 임시로 더미 유저 데이터를 만들어서 랭킹을 구현

     

    >> 각각 맞게 함수 추가 및 바인딩

    1. Ranking Popup Panel

     

    2. Rank Cell

     

    3. Main Panel

     

    └ 구현된 모습

     

    앞으로 개선 사항

    UserData를 받아서 'User Rank Cell'에 대입

    ※ Panel이 중복으로 열리지 않도록 조치해야 한다.

    ※ Panel 외부를 클릭하면 Panel이 닫히도록 구현

     

    최종 코드

    >> 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 ScoreInfo _playerScore = new ScoreInfo() // 임시 유저 정보
        {
            Nickname = "Test01",
            WinCount = 8,
            LoseCount = 2,
            DrawCount = 0,
            Rank = 17
        };
    
        private ScoreInfo[] _playerScores = new ScoreInfo[] // 임시 유저 더미 정보
        {
            new ScoreInfo { Nickname = "Test01", WinCount = 8, LoseCount = 2, 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 = 13 },
            new ScoreInfo { Nickname = "Test04", WinCount = 26, LoseCount = 16, DrawCount = 3, Rank = 13 },
        };
    
        /// <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 (canvas != null)
            {
                var rankingPanelObject = Instantiate(rankingPanelPrefab, canvas.transform);
                var rankingPanelController = rankingPanelObject.GetComponent<RankingPanelController>();
                //rankingPanelController.CreateRankCell(_playerScore);
                rankingPanelController.CreateRankCells(_playerScores);
            }
        }
    
        /// <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 UnityEngine;
    
    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
    {
        [SerializeField] private GameObject rankCellPrefab;
        [SerializeField] private Transform content;
    
        /// <summary>
        /// Content의 자식으로 Cell을 만들어주는 함수
        /// </summary>
        public void CreateRankCell(ScoreInfo scoreInfo)
        {
            var rankCellObject = Instantiate(rankCellPrefab, content);
            var rankCellController = rankCellObject.GetComponent<RankCellController>();
            rankCellController.SetCellInfo(scoreInfo);
        }
    
        /// <summary>
        /// Content의 자식으로 Cell을 만들어주는 함수, 여러 임시 Player 더미를 만들어서 테스트하기 위함
        /// </summary>
        /// <param name="scoreInfos"></param>
        public void CreateRankCells(ScoreInfo[] scoreInfos)
        {
            // Rank가 낮을수록 먼저 표시되도록 정렬하고 Rank가 같으면 승률이 높으면 먼저 표시되도록 정렬
            var sortedScores = scoreInfos.OrderBy(player => player.Rank)
                .ThenByDescending(player => rankCellPrefab.AddComponent<RankCellController>().GetWinRate(player))
                .ToArray();
            
            foreach (var scoreInfo in sortedScores)
            {
                var rankCellObject = Instantiate(rankCellPrefab, content);
                var rankCellController = rankCellObject.GetComponent<RankCellController>();
                rankCellController.SetCellInfo(scoreInfo);
            }
        }
    }

     

    >> 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;
        
        /// <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;
        }
    }