스파르타코딩클럽_Unity개발과정

5주차 팀 프로젝트: 아마도.. 궁수의 전설 (1)

ybbro 2025. 5. 12. 23:03

저는 UI를 담당하였습니다.

요 며칠간은 UI 작업하느라 TIL에 정말 쓸 내용이 없었습니다.

 

https://ybbro.tistory.com/27

 

유니티) 화면 해상도 대응

최근 간단한 미니 게임을 만들어 공유하였으나제가 작업한 해상도와 유저의 화면 해상도의 차이가 있어버튼이 겹치거나 이미지가 삐져나오는 등 UI 배치에 문제가 발생하였습니다. 이를 해결

ybbro.tistory.com

예전에 공부한 내용 그대로 여러 화면 해상도에 대응하기 위한 기법들을 사용하였습니다.

위 포스팅 내용 외에는 기술적인 면보다는 끈기와 눈썰미가 요구되는 작업이었습니다.

 

하지만 UI/UX는 게임에서 유저 플레이 체감에 중요한 요소이기에 정성을 다해 만들었습니다.

 

1. 전투 UI 화면 구성

 

1) 상단 UI

- 일시정지 버튼

 - 레벨, 경험치 바

 - 획득 재화 표시

 

2) 일시정지 UI

- 전투 일시 정지 기능

- 획득한 스킬 확인 기능

- 메인 화면으로 복귀 버튼

- 일시정지 해제(계속하기) 버튼

 

3) 스킬 선택(레벨업) UI

- 선택 도중 일시정지

- 레벨업 or 스테이지 클리어 상황, 3가지 스킬 중 하나 획득 가능

 

4) 게임오버 UI

- 메인화면으로 나가기 버튼

 

5) 스테이지 클리어 UI
- 메인 화면으로 나가기 버튼
- 다음 스테이지 이동 버튼

 

 

2. 실행 및 오류 수정

플레이 도중 스킬 선택에 제대로 3개의 랜덤 스킬이 들어오는 것을 확인
팀원님 작업 부분 해석 후 에러 발생 상황 해결!

 

획득한 스킬의 아이콘을 일시정지 화면에서 확인

 

 

3. 사용 코드

팀원님들과의 원활한 협업 및 작업량이 많아질 때 제가 헤매지 않기 위해 전반적으로 주석을 상세하게 달았습니다.

코드를 거의 해석할 필요 없이 주석만 쭉 읽어도 어떤 변수, 함수, 기능인지 쉽게 파악할 수 있습니다.

1) UIManager_Battle.cs

* 특이사항
(1) 사용자가 프로그램 창 사이즈 변경 시 정해진 비율대로만 바뀌게끔

      (현재 위아래 사이즈 조정에만 대응. 급한 작업이 끝나면 좌우도 대응되도록 마저 수정)

(2) UI 크기에 맞게 자동으로 버튼들의 폰트 사이즈 각각 설정 후 작은 사이즈로 통일
      (UI에 통일감을 줌. 폰트 사이즈 차이로 완성도가 크게 떨어져 보이는 느낌을 받았기에 작성)

using UnityEngine;
using TMPro;

public class UIManager_Battle : SceneOnlyManager<UIManager_Battle>
{
    [SerializeField] Transform Panel_Pause, Panel_LevelUp, Panel_GameOver;
    
    // 레벨, 골드 : 게임 플레이 중 써주기 위함
    // 계속하기, 나가기 버튼의 텍스트는 글자 크기를 맞춰주기 위해 가져옴
    [SerializeField] 
    TextMeshProUGUI 
        gold_text,
        level_text,
        continue_button_text, 
        exit_button_text;

    // 체력바
    [SerializeField] RectTransform hpBar;

    float targetAspectRatio; // 너비/높이로 게임 화면 비율 계산한 값

    protected override void Awake()
    {
        // 다른 스크립트에서도 쉽게 접근 가능하게끔 인스턴스 생성
        base.Awake();

        // 게임 실행시의 해상도 비율을 기억
        if (TryGetComponent(out UnityEngine.UI.CanvasScaler canvasScaler))
        {
            Screen.SetResolution((int)canvasScaler.referenceResolution.x, (int)canvasScaler.referenceResolution.y, false);
            targetAspectRatio = canvasScaler.referenceResolution.x / canvasScaler.referenceResolution.y;
        }
        else
            Debug.Log("캔버스 스케일러가 없어요");

        // 일시정지 화면의 버튼 텍스트 크기 통일
        Pause_Buttons_TextSize_Unify(continue_button_text, exit_button_text); 
    }

    // RectTransform 자체가 변경되었을 때 호출 >> 빌드 후 창 크기 변경 때 호출
    void OnRectTransformDimensionsChange()
    {
        MaintainAspectRatio();
    }

    /* 사용자가 프로그램 창 크기를 조절할 때 설정한 화면 비율에 맞게 늘어나거나 줄어들게끔
     * 제작 사유 : 빈 부분 보이면 몰입감을 해칠 수 있음
     * 호출 시점 : 창 크기가 바뀔 때마다
     * 
     * 상하로 당기는 건 대응이 되나 좌우는 대응이 되지 않아 시도하다 꼬여서 원복... >> 생각보다 깔끔하게 되지는 않네요
     * !!!!! : 당장 급하진 않으니 나중에 수정
     */
    void MaintainAspectRatio()
    {
        // 현재 프로그램창 너비, 높이
        int newWidth = Screen.width,
            newHeight = Screen.height;

        // 초기 화면비에 맞춰주기
        newWidth = Mathf.RoundToInt(newHeight * targetAspectRatio);

        // 해상도 설정
        Screen.SetResolution(newWidth, newHeight, false);
    }

    // 일시정지 UI 활성화
    public void Enable_Pause() => Panel_Pause.gameObject.SetActive(true);

    // 레벨업 UI 활성화
    public void Enable_LevelUp() => Panel_LevelUp.gameObject.SetActive(true);

    // 게임오버/스테이지 클리어 UI 활성화
    public void Enable_GameOver() => Panel_GameOver.gameObject.SetActive(true);

    // 골드 획득량 표시 변화
    public void SetGoldText(int gold) => gold_text.text = gold.ToString();

    // exp 바 채워진 양 표시 변화
    public void SetExpRatio(float ratio) => hpBar.localScale = new Vector3(ratio, 1, 1);

    // 전투에서 나가기 버튼
    // 전투 UI의 모든 메인 화면으로 나가기 버튼에서 해당 메서드 참조(똑같은 기능)
    public void Button_Exit()
    {
        // !!!!! 공용으로 쓰는 씬 전환 매니저를 만들 것이라면 그걸 가져다 쓰기. 따로 없다고 하면 여기서 씬 전환해보자
        // 현재 버튼에 연결은 해둔 상태
        Debug.Log("나가기!");
    }

    // 레벨 표시 변경
    public void SetLevelText(int nextLevel) => level_text.text = $"Lv. {nextLevel}";

    /*
       일시정지 UI의 전투 나가기, 계속하기 버튼의 안내 텍스트 사이즈 통일
       테스트 결과 게임 실행 때 1번만 맞춰주면 이후에 창 크기를 줄이거나 늘려도 텍스트 사이즈가 동일함을 확인
       제작 사유 : 글자 크기 차이가 나면 없어보임
    */
    public void Pause_Buttons_TextSize_Unify(TextMeshProUGUI text1, TextMeshProUGUI text2)
    {
        // 폰트 자동 사이즈 옵션을 둘 다 켜주기
        text1.enableAutoSizing = text2.enableAutoSizing = true;

        // 폰트 자동 사이즈 옵션 적용 시 나가기, 계속하기 버튼의 텍스트 사이즈
        float
            continue_text_size = text1.fontSize,
            exit_text_size = text2.fontSize;

        // 텍스트 사이즈가 작은 쪽으로 통일 
        if (continue_text_size > exit_text_size)
        {
            // 자동 텍스트 사이즈 옵션 해제하고, 텍스트 사이즈 통일
            text1.enableAutoSizing = false;
            text1.fontSize = exit_text_size;
        }
        else
        {
            text2.enableAutoSizing = false;
            text2.fontSize = continue_text_size;
        }
    }

    protected override void OnDestroy()
    {
        base.OnDestroy();
    }
}

 

 

2) UI_Pause

* 특이사항

팀원님들 각자 깃 브랜치에서 작업 후 합쳤기에

소통해가며 얻은 정보로 연결할 준비를 미리 해뒀다가

머지 이후 매개 변수만 기입하여 빠르게 연결

using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;

public class UI_Pause : MonoBehaviour
{
    [SerializeField] Transform Panel_Skills;

    // 스킬의 인덱스들을 모은 것
    List<int> skills_index = new List<int>();

    // 활성화 때 획득한 스킬들에 맞춰 스킬 아이콘을 출력하게끔
    private void OnEnable()
    {
        // 시간 비율을 0으로 >> 게임 일시정지
        Time.timeScale = 0;
        // 플레이어 스킬 리스트만 가져오면 동작하게끔 만들어 둠
        SkillIconsInit(SkillManager.Instance.SelectedSKills);
    }

    void SkillIconsInit(List<ISkill> playerSkills)
    {
        // 기존의 스킬 수
        int previous_skillCount = skills_index.Count;
        // 이전보다 많아진 스킬들에 대해
        for (int i = previous_skillCount; i < playerSkills.Count; i++)
        {
            // 해당 위치에 추가할 스킬
            int tmpSkill = playerSkills[i].Id;
            // 새로 얻은 스킬을 리스트에 추가
            skills_index.Add(tmpSkill);
            // 새로 얻은 스킬을 표시할 비활성화 상태의 자식 오브젝트
            Transform skillIconObject = Panel_Skills.GetChild(i);
            // 스킬 테두리/스킬 배경/스킬 아이콘 계층 순서로 되어 있음
            // 스킬 아이콘 Image 컴포넌트에 접근
            if (skillIconObject.GetChild(0).GetChild(0).TryGetComponent(out Image iconImage))
            {
                // 해당 스킬에 알맞는 아이콘 설정
                iconImage.sprite = TableManager.Instance.GetTable<SkillTable>().GetDataByID(tmpSkill).SkillIcon;
                // 활성화하여 스킬 아이콘을 화면에 표시
                skillIconObject.gameObject.SetActive(true);
            }
        }
    }

    // 일시정지 패널 비활성화
    public void Disable_PausePanel()
    {
        // 시간 비율을 원 상태로 돌리고
        Time.timeScale = 1;
        // 패널 비활성화
        gameObject.SetActive(false);  
    }
}

 

 

3) UI_LevelUP.cs

* 특이사항

팀원님이 작성한 코드, 구조 분석을 통한 기능 연결

기존의 약속과 차이점을 캐치하고 의사소통을 통해 빠르게 오작업 여지를 줄임

using UnityEngine;
using TMPro;
using System;
using System.Collections.Generic;
using UnityEngine.UI;

public class UI_LevelUP : MonoBehaviour
{
    // 유저들에게 정보를 보여주기 위한 UI 오브젝트들
    [SerializeField] SkillOption[] skillOptions;

    // 스킬 선택지에 나온 스킬들의 인덱스. 선택지 3개 고정이라기에 크기 고정
    int[] skillOptionIndexs = new int[3];

    private void OnEnable()
    {
        // 레벨업 고르는 동안은 일시정지
        Time.timeScale = 0;
        SkillOptionInit();
    }

    // 레벨업 패널이 등장할 때, 레벨업 선택지 초기화
    void SkillOptionInit()
    {
        // 랜덤하게 뽑은 3개의 스킬을 가져오게끔
        HashSet<SkillData> skillDatas = new HashSet<SkillData>(SkillManager.Instance.GetSkillToSelect());

        // HashSet은 인덱스가 없으므로 여기서 i를 선언하여 사용
        int i = 0;
        foreach (SkillData skillData in skillDatas)
        {
            // 각 버튼에 어떤 스킬이 들어가 있는지 인덱스 저장
            skillOptionIndexs[i] = skillData.Id;

            // UI 표시 정보 변경
            skillOptions[i].name.text = skillData.Name;
            skillOptions[i].icon.sprite = skillData.SkillIcon;
            skillOptions[i].info.text = skillData.Info;

            // 다음 인덱스로
            i++;
        }
    }

    public void Button_OptionSelect(int buttonIndex)
    {
        // 선택한 버튼과 일치하는 스킬 인덱스를 통해 플레이어 스킬 추가
        SkillManager.Instance.SelectSkill(skillOptionIndexs[buttonIndex]);
        // 일시정지 해제
        Time.timeScale = 1;
        // 스킬 선택이 끝났으니 레벨업 패널 비활성화
        gameObject.SetActive(false);
    }

    // 스킬 선택 버튼에 스킬 정보를 표시할 UI오브젝트들
    [Serializable]
    public class SkillOption
    {
        public TextMeshProUGUI name;
        public Image icon;
        public TextMeshProUGUI info;
    }
}