쯔꾸르식 유니티 게임 공부

39.타이틀 구현

ruripanda 2025. 3. 26. 19:43

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Menu : MonoBehaviour
{
    public static Menu instance;

    public GameObject go;
    public AudioManager theAudio;

    public string call_sound;
    public string cancel_sound;

    public OrderManager theOrder;

    public GameObject[] gos;

    private bool activated;

    public void GoToTitle()
    {
        for (int i = 0; i < gos.Length; i++)
            Destroy(gos[i]);
        go.SetActive(false);
        activated = false;
        SceneManager.LoadScene("title");
    }

    private void Awake()
    {
        if (instance == null)
        {
            theOrder = FindAnyObjectByType<OrderManager>();
            DontDestroyOnLoad(this.gameObject);
            instance = this;
        }
        else
        {
            Destroy(this.gameObject);
        }
    }

    public void Exit()
    {
        Application.Quit();
    }

    public void Continue()
    {
        activated = false;
        go.SetActive(false);
        theOrder.Move();
        theAudio.Play(cancel_sound);
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            activated = !activated;

            if (activated)
            {
                theOrder.NotMove();
                go.SetActive(true);
                theAudio.Play(call_sound);
            }
            else
            {
                go.SetActive(false);
                theAudio.Play(cancel_sound);
                theOrder.Move();
            }
        }
    }
}
using System.Collections;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    private PlayerManager thePlayer;
    private CameraManager theCamera;
    private FadeManager theFade;
    private Menu theMenu;
    private DialogueManager theDM;
    private Camera cam;

    public GameObject hpbar;
    public GameObject mpbar;

    public void LoadStart()
    {
        StartCoroutine(LoadWaitCoroutine());
    }

    IEnumerator LoadWaitCoroutine()
    {
        yield return new WaitForSeconds(0.5f);

        thePlayer = FindAnyObjectByType<PlayerManager>();
        theCamera = FindAnyObjectByType<CameraManager>();
        theFade = FindAnyObjectByType<FadeManager>();
        theMenu = FindAnyObjectByType<Menu>();
        theDM = FindAnyObjectByType<DialogueManager>();
        cam = FindAnyObjectByType<Camera>();

        Color color = thePlayer.GetComponent<SpriteRenderer>().color;
        color.a = 1f;
        thePlayer.GetComponent<SpriteRenderer>().color = color;

        theCamera.target = GameObject.Find("Player");
        theMenu.GetComponent<Canvas>().worldCamera = cam;
        theDM.GetComponent<Canvas>().worldCamera = cam;
        hpbar.SetActive(true);
        mpbar.SetActive(true);
        theFade.FadeIn();
    }
}
using System.Collections.Generic;
using UnityEngine;
using System.IO;                                        //세이브파일 입출력
using System.Runtime.Serialization.Formatters.Binary;   //시리얼라이즈 직렬화
using UnityEngine.SceneManagement;
using System.Collections;
using NUnit.Framework.Constraints;                      //scene사용

public class SaveNLoad : MonoBehaviour
{
    [System.Serializable]
    public class Data //세이브를 담을 곳
    {
        public float PlayerX;
        public float PlayerY;
        public float PlayerZ;//Vector3는 직렬화가 안됨

        public int playerLv;
        public int playerHP;
        public int playerMP;

        public int playerCurrentHP;
        public int playerCurrentMP;
        public int playerCurrentEXP;

        public int playerHPR;
        public int playerMPR;

        public int playerATK;
        public int playerDEF;

        public int added_atk;
        public int added_def;
        public int added_hpr;
        public int added_mpr;

        public List<int> playerItemInventory;
        public List<int> playerItemInventoryCount;
        public List<int> playerEquipItem;

        public string mapName;
        public string sceneName;

        public List<bool> swList;
        public List<string> swNameList;
        public List<string> varNameList;
        public List<float> varNumberList;
    }

    private PlayerManager thePlayer;
    private PlayerStat thePlayerStat;
    private DatabaseManager theDatabase;
    private Inventory theInven;
    private Equipment theEquip;
    private FadeManager theFade;

    public Data data;

    private Vector3 vector;

    public void CallSave()
    {
        theDatabase = FindAnyObjectByType<DatabaseManager>();
        thePlayer = FindAnyObjectByType<PlayerManager>();
        thePlayerStat = FindAnyObjectByType<PlayerStat>();
        theEquip = FindAnyObjectByType<Equipment>();
        theInven = FindAnyObjectByType<Inventory>();

        data.PlayerX = thePlayer.transform.position.x;
        data.PlayerX = thePlayer.transform.position.y;
        data.PlayerX = thePlayer.transform.position.z;

        data.playerLv = thePlayerStat.character_Lv;
        data.playerHP = thePlayerStat.hp;
        data.playerMP = thePlayerStat.mp;
        data.playerCurrentHP = thePlayerStat.currentHP;
        data.playerCurrentMP = thePlayerStat.currentMP;
        data.playerCurrentEXP = thePlayerStat.currentEXP;
        data.playerATK = thePlayerStat.atk;
        data.playerDEF = thePlayerStat.def;
        data.playerHPR = thePlayerStat.recover_hp;
        data.playerMPR = thePlayerStat.recover_mp;
        data.added_atk = theEquip.added_atk;
        data.added_def = theEquip.added_def;
        data.added_hpr = theEquip.added_hpr;
        data.added_mpr = theEquip.added_mpr;

        data.sceneName = thePlayer.currentSceneName;

        Debug.Log("기초 데이터 성공");

        data.playerItemInventory.Clear();
        data.playerItemInventoryCount.Clear();
        data.playerEquipItem.Clear();

        for(int i = 0; i < theDatabase.var_name.Length; i++)
        {
            data.varNameList.Add(theDatabase.var_name[i]);
            data.varNumberList.Add(theDatabase.var[i]);
        }
        for (int i = 0; i < theDatabase.switch_name.Length; i++)
        {
            data.swNameList.Add(theDatabase.switch_name[i]);
            data.swList.Add(theDatabase.switches[i]);
        }

        List<Item> itemList = theInven.SaveItem();
        for(int i =0; i < itemList.Count; i++)
        {
            Debug.Log("인벤토리 아이템 저장완료" + itemList[i].itemID);
            data.playerItemInventory.Add(itemList[i].itemID);
            data.playerItemInventoryCount.Add(itemList[i].itemCount);
        }

        for(int i =0; i < theEquip.equipItemList.Length; i++)
        {
            data.playerEquipItem.Add(theEquip.equipItemList[i].itemID);
            Debug.Log("장착 아이템 저장 완료 : " + theEquip.equipItemList[i].itemID);
        }

        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(Application.dataPath + "/SaveFile.dat");

        bf.Serialize(file, data);
        file.Close();

        Debug.Log(Application.dataPath + "의 위치에 저장했습니다");
    }

    public void CallLoad()
    {
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Open(Application.dataPath + "/SaveFile.dat", FileMode.Open);

        if(file != null && file.Length > 0)
        {
            data = (Data)bf.Deserialize(file);

            theDatabase = FindAnyObjectByType<DatabaseManager>();
            thePlayer = FindAnyObjectByType<PlayerManager>();
            thePlayerStat = FindAnyObjectByType<PlayerStat>();
            theEquip = FindAnyObjectByType<Equipment>();
            theInven = FindAnyObjectByType<Inventory>();
            theFade = FindAnyObjectByType<FadeManager>();

            theFade.FadeOut();

            thePlayer.currentSceneName = data.sceneName;

            vector.Set(data.PlayerX, data.PlayerY, data.PlayerZ);
            thePlayer.transform.position = vector;

            thePlayerStat.character_Lv = data.playerLv;
            thePlayerStat.hp = data.playerHP;
            thePlayerStat.mp = data.playerMP;
            thePlayerStat.currentHP = data.playerCurrentHP;
            thePlayerStat.currentMP = data.playerCurrentMP;
            thePlayerStat.currentEXP = data.playerCurrentEXP;
            thePlayerStat.atk = data.playerATK;
            thePlayerStat.def = data.playerDEF;
            thePlayerStat.recover_hp = data.playerHPR;
            thePlayerStat.recover_mp = data.playerHPR;

            theEquip.added_atk = data.added_atk;
            theEquip.added_def = data.added_def;
            theEquip.added_hpr = data.added_hpr;
            theEquip.added_mpr = data.added_mpr;

            theDatabase.var = data.varNumberList.ToArray();
            theDatabase.var_name = data.varNameList.ToArray();
            theDatabase.switches = data.swList.ToArray();
            theDatabase.switch_name = data.swNameList.ToArray();

            for(int i = 0; i < theEquip.equipItemList.Length; i++)
            {
                for(int x =0; x < theDatabase.itemList.Count; x++)
                {
                    if (data.playerEquipItem[i] == theDatabase.itemList[x].itemID)
                    {
                        theEquip.equipItemList[i] = theDatabase.itemList[x];
                        Debug.Log("장착된 아이템을 로드했습니다 : " + theEquip.equipItemList[i].itemID);
                        break;
                    }
                }
            }

            List<Item> itemlist = new List<Item>();

            for (int i = 0; i < data.playerItemInventory.Count; i++)
            {
                for (int x = 0; x < theDatabase.itemList.Count; x++)
                {
                    if (data.playerItemInventory[i] == theDatabase.itemList[x].itemID)
                    {
                        itemlist.Add(theDatabase.itemList[x]);
                        Debug.Log("인벤토리 아이템을 로드했습니다 : " + theDatabase.itemList[x].itemID);
                        break;
                    }
                }
            }

            for(int i =0; i<data.playerItemInventoryCount.Count; i++)
            {
                itemlist[i].itemCount = data.playerItemInventoryCount[i];
            }

            theInven.LoadItem(itemlist);
            theEquip.ShowText();

            StartCoroutine(WaitCoroutine());
        }
        else
        {
            Debug.Log("저장된 세이브파일이 없습니다");
        }

        file.Close();
    }

    IEnumerator WaitCoroutine()
    {
        yield return new WaitForSeconds(2f);

        GameManager theGM = FindAnyObjectByType<GameManager>();
        theGM.LoadStart();

        SceneManager.LoadScene(data.sceneName);
    }
}
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Title : MonoBehaviour
{
    private FadeManager theFade;
    private AudioManager theAudio;

    public string click_sound;

    private PlayerManager thePalyer;
    private GameManager theGM;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        theFade = FindAnyObjectByType<FadeManager>();
        theAudio = FindAnyObjectByType<AudioManager>();
        thePalyer = FindAnyObjectByType<PlayerManager>();
        theGM = FindAnyObjectByType<GameManager>();
    }

    public void StartGame()
    {
        StartCoroutine(GameStartCoroutine());
    }

    IEnumerator GameStartCoroutine()
    {
        theFade.FadeOut();
        theAudio.Play(click_sound);
        yield return new WaitForSeconds(2f);
        Color color = thePalyer.GetComponent<SpriteRenderer>().color;
        color.a = 1f;
        thePalyer.GetComponent<SpriteRenderer>().color = color;
        thePalyer.currentSceneName = "Start";

        theGM.LoadStart();
        SceneManager.LoadScene("Start");
    }

    public void ExitGame()
    {
        theAudio.Play(click_sound);
        Application.Quit();
    }
}

이번 시간에는 게임 타이틀을 구현해보겠다

흔히 RPG게임을 처음 시작하면 시작, 계속하기, 나가기 등을 보여주는 화면이 있다

그게 타이틀 화면이다

 

일단 보라색으로 체크해놓은 오브젝트들을 다른 씬에 넣기위해 프리팹화 해놓는다

여기서 주의 할 것이 있는데 프리팹화 시켜놓으면 우리가 연결해놓은 오브젝트 슬롯들이 빈값으로 바뀐다

그러므로 프리팹화 해놓은 뒤 필자가 올린 글들을 참고하면서 빈값으로 바뀐 슬롯들을 전부 연결한다

 

그리고 EventSystem에 대해서 처음 설명해주셨는데 이게 없으면 버튼 클릭이 되지 않는다고 하신다

 

그리고 강좌대로 진행했으면 Canvas가 2개가 나올탠데

Menu가 있는 Canvas는 MenuCanvas로 이름을 바꿔주고

다른 캔버스는 SystemCanvas로 바꿔준다

 

그 뒤 우리가 프리팹으로 만들은 오브젝트들을 지워주고

 

새 씬 title를 만들고 새로운 씬에 프리팹화 해놓은 것들을 전부 집어 넣는다

 

Player오브젝트에 빈 슬롯이다 채워 넣자

 

그리고 플레이어 매니저의 이 부분은 지워주자

title에 있기 때문에 의미가 없다

 

다음은 MenuCanvas의 슬롯을 연결해주자

 

다음은 Equipment에 EquipWeapon 슬롯이다

 

이제 title로 돌아와서...

Player오브젝트의 컬러 a값을 0으로 만들어준다

이유는 우리가 타이틀화면에서는 표현할 필요가 없기 때문에 투명하게 만들어주는 것이다

 

이제 Scene에 케이디님이 예전에 올려주신 리소스에서 title back ground를 넣어주자

이제 Transform을 position을 0,0,0으로 맞춰준다

 

그리고 MainCamera도 스크린샷을 참고해서 조절해주자

 

그리고 title - list와 title-name도 위처럼 추가해주고

 

Order in Layer을 1로 바꿔준다

백그라운드 위에 보여야하게 때문에 우선순위를 올려준 것이다

그 뒤 Scale를 둘다 0.8로 맞춰준다

그냥 사용하면 스케일이 커서 우리가 원하는 그림이 안나온다

 

그뒤 씬에서 적절하게 위치를 조절해준다

 

그리고 강좌대로 따라한 분들은 왼쪽 위에 HP,MP게이지가 있을텐데 비활성화해주자

 

그리고 새로운 canvas를 만들어주고

 

Button을 만들어준다

그뒤 안의 Text 오브젝트를 없애주고

 

Button을 복사해서 위처럼 Start, Load, Exit를 만들어준다

이제 만들어준 버튼들을 Start는 들어간다에 위치해주고

Load는 기억한다에 위치해주고 Exit는 돌아간다에 위치해준다

그리고 사이즈는 160 * 50 으로 모든 버튼 돌일하게 맞춰준다

그리고 Color의 a값을 0으로 만들어서 투명하게 해준다

 

그뒤 버튼의 Button 컴포넌트에서 표시한 Highlighted Color과 Pressed Color 부분의 a값을 50으로 바꿔준다

이렇게 해주면

 

마우스 위에 버튼을 갔다대면 이렇게 투명도가 바뀌어서 버튼이 선택된 효과가 생긴다

그뒤 Canvas에 MainCamera를 스크린샷에 보이는 슬롯에 넣어준다

필자가 기억하기로는 Canvas가 MianCamera의 영역에 보이게 연결해주는 것이다

그뒤 Sorting Layer을 UI로 바꿔주고 Order in Layer을 100으로 잡아준다

이렇게 잡아준 이유는 그 어떤 UI보다 더 높이 올라와야하기 때문에 잡아준 것이다

이제 위 스크린샷을 참고해서 캔버스 속성을 바꿔주자

UI Scale Mode 를 Scale With Screen Size 로 바꿔주고

사이즈를 960 * 540 으로 바꿔준다

 

이제 Title 스크립트를 만들어주자

 

Title 스크립트에서 선언할 변수 목록이다

우리에게 이미 친숙한 것들이다

그리고 13번째 줄에 유튜브만 보고 따라오면 작성하지 않을 수 있는 GameManager theGm;이 있는데

이 부분은 필자가 맨위에 올려놓은 GameManager 스크립트를 보고 작성해주자 이게 없으면 강좌가 진행이 안된다

GameManager을 작성하면 Player오브젝트에 붙여놓는 걸 잊지말자

 

덤으로 SaveNLode 스크립트 맨 아래에...

210번줄부터 230줄까지 전부 추가해주자

 

이게 없으면 강좌진행이 안된다 필자 이거 찾는데 머리 깨지는 줄 알았다

다시 GameManager 스크립트로 돌아와서...

 

24번째 줄은 StartGame()인데 29번줄에 코루틴 함수를 불러오는 것이다

31번째 줄에 FadeOut로 화면을 검게 바꿔주고 소리를 출력한뒤

33번줄에 2초 정도 기다린 뒤

34번 줄에 Player오브젝트에서 SpriteRenderer 컴포넌트를 받아온뒤 a값을 1로 바꿔준다

36번줄에 다시 이걸 적용해주고

37번줄에 불러올 씬을 Player오브젝트에서 받아오는 것인데

currentSceneName에 "Start";를 넣어준다

 

그뒤 39번줄에는 GameManager에서 LoadStart()함수를 실행시켜주는 것이다

 

그리고 40번 줄에는 "Start" 라고 명명된 Scene를 로드해준다

 

43번에는 게임을 종료해주는 함수이다

유니티엔진 상에서는 게임종료는 구현한 걸 보여주지 못하니 일단 만들어만 놓자

 

이렇게 만들어진 Title 스크립트는

이번에 새로 만든 Canvas에 넣어주고 Click_sound에는 ok라고 적힌 사운드를 넣어주자

 

이제 우리가 만들어 놓은 Start버튼을 구현해보자

Title를 넣은 Canvas를 빈 슬롯에 넣어주고

Title.StartGame를 세팅해주자 이세 스타트는 세팅이 끝났다

 

Load는 Player오브젝트가 가지고 있으므로 Player을 빈슬롯에 넣어주고 SaveNLoad.CallLoad로 세팅해준다

 

Exit는 빈슬롯에 Canvas를 넣어주고 Title.ExitGame를 세팅해주자

이렇게하면 타이틀 버튼 구현은 전부 끝났다

 

케이디님은 이렇게하고 F5키와 F9키로 구현한 세이브 로드를 지우셨는데 이건 각자 생각에 맡긴다

 

 

이제 FadeManager에서 검은색으로 감싸는 부분이 잘 작동하는지 확인해보자

이부분은 케이디님은 잘 안되는 곳이 있어서 다 수정하셨다

수정하신 방법은 Scale를 10 * 10으로 확대하셨다

 

그리고 MenuCanvas와 SyistemCanvas의 RenderMode를 Screen Space - Camera로 바꿔주고

Render Camera슬롯에 MainCamera를 넣어준다

이렇게 해주면 Canvas가 카메라 영역에 고정된다

 

그런데 문제는 우리가 씬을 이동하면 Main카메라를 다시 놓친다는 부분이다 그래서 게임을 스타트하면 슬롯이 비게 되는데

우리는 GameManager에서 잡게 세팅할 것이다

 

이게 GameManager 스크립트의 전문이다

9번째 줄과 10번째 줄은 우리가 Menu, System Canvas에 달린 스크립트를 불러오는 것이다

그리고 11번째 줄에 우리가 사용할 MainCamera를 변수로 선언했고

25에서 30번 줄에 이걸 모두 세팅해준다

 

그뒤 36번줄에서 theCamera.target = GameObject.Find("Player"); 로 카메라의 타겟을 Player오브젝트로 설정하고

37과 38에 Canvas 컴포넌트를 불러온 후 빈 슬롯에 카메라를 넣어준다

 

이렇게 하면 빈슬롯에 카메라가 자동으로 세팅된다

 

그런데 이렇게만 하면 우리가 세팅해준 FadeManager에서 이미지 사이즈가 부족할 수 있다

 

FX - White와 FX - Black의 스케일을 10 * 10 으로 만들어준다

 

타이틀 구현은 이걸로 끝났다

그런데 문제가 있는데 Lode를 사용해서 게임에 들어가면 플레이어의 a값이 0이라서 플레이어가 안보인다

이걸 보이게 해야한다

 

 

GameManager에서 32번줄에서 34번줄에 이걸 전부 구현해놨다

32번줄에서 플레이어의 SpriteRenderer을 받아와서 알파값을 1로 바꿔주고 적용해주는 것이다

 

그런데 로드할때 심심한 감이 있기 때문에 우리는 이것도 수정할 것이다 SaveNLode 스크립트로 들어가자

체크된 FadeManager을 추가하자

 

이제 빨간색으로 체크된 부분을 작성해주면 끝난다

이것으로 FadeOut 효과가 구현 될 것이다

 

그런데 문제는 대기가 없어서 연출이 부자연 스러울 수 있다

 

SaveNLode 끝에 이걸 작성해서 연출을 자연스럽게 잡아주자

2초 딜레이를 주는 것이다

 

그리고 SaveNLode 스크립트에서 209번 줄로 묶어준다

 

그리고 EvnetSystem이 없어서 UI조작이 안될 수 있다

EventSystem을 SystemCanvas에 넣어준다

이렇게 되면 파괴되지 않는다

 

그리고 이제 타이틀로를 구현해보겠다

 

Menu 스크립트를 열어보자

3번째줄의 UnityEngine.SceneManagement; 를 추가해서 씬을 사용할 수 있게 한다

 

그리고 public void GoToTitle() 함수를 작성해준다

 

그리고 타이틀로가는 버튼을 연결해준다

 

그런데 이렇게하면 문제가 하나 생기는데

 

유니티 엔진에서 우리가 소리를 듣기 위해서는 Audio Listener이라는게 있어야 한다

그런데 이게 여러군대 중복으로 있을 것이다

이걸 AudioManager에 넣어주고 MainCamera의 Audio Listener를 지워준다

 

그리고 이렇게하고 하나 문제가 생기는데

우리가 계속하기로 게임을 실행하고 타이틀로 넘어오면 우리가 장비한 아이템은 그대로 남아서 새로하기를 하면 그 아이템이 남아있는 버그가 있다

그걸 없세주기 위해

 

Menu스크립트에 이걸 작성해주고

여기에서 23번줄과 24번줄을 작성해주자

이렇게하면 배열이 나올 탠데 문제는 케이디님이 이거 동영상으로 찍으시면서 누락시키고 지금까지 수정 안해주셨다 ;ㅂ;

 

다행이 덧글에 힌트가 있었는데

우리가 프리팹으로 만들어 놓은 Title의 오브젝트를 전부 넣어주자

 

어짜피 프리팹이기 때문에 타이틀 갔다오면 다시 로드되어 있다

 

그리고 지금까지 따라오면 또 HP게이지와 MP게이지가 문제가 있는데...

 

GameManager에서 위 두 줄을 작성해주고

 

연결해주자

 

이제 끝났다 ㅜㅜ

이렇게 해주면 영상에 나온 모든 것을 처리한 것이다

'쯔꾸르식 유니티 게임 공부' 카테고리의 다른 글

40(完).엔딩 크레딧  (0) 2025.03.28
CheckCollsion보완  (0) 2025.03.28
38.메뉴 구현  (0) 2025.03.23
인벤토리 페이지 시스템  (0) 2025.03.17
37.바이너리파일 세이브 구현  (0) 2025.03.15