티스토리 뷰

반응형

 

무한의 계단 게임을 저번에 이어서 설명하겠습니다!

Github 소스코드와 파일 주소는 마지막 포스팅에 있습니다.

많은 의견 주시고 저번 글을 못보신 분은 먼저 읽어주세요😄

 

 

 

2020/04/05 - [Unity 게임 개발/무한의 계단] - [Unity 게임 개발 고수 되기 #02.무한의계단] 01. 플레이어, 계단, 코인, UI, 게이지 구현하기

 

[Unity 게임 개발 고수 되기 #02.무한의계단] 01. 플레이어, 계단, 코인, UI, 게이지 구현하기

이번에는 NFLY 스튜디오에서 2015년도에 출시하여 많은 인기를 끌었던 모바일게임 무한의계단을 만들어보려고 합니다. 지금까지도 많은 사랑을 받고있고, 묘한 중독성이 있는 인디게임 입니다! 그런데 무한의계단..

codingwell.tistory.com

 

 

 

 

 

 


 

 

1. 배경음악 끊기지 않게 구현하기

게임을 흥미롭게 만들기 위해 배경음악을 넣는데요!

Scene을 다시 로드하거나 바꾸게 될 경우에

배경음악이 끊기면서 처음부터 다시 시작하지 않고 계속 이어지도록 구현해보겠습니다.

 

--> 오브젝트가 Scene을 새로 Load하더라도 파괴되지 않도록 하는 함수가 있습니다.

DontDestroyOnLoad 함수 인데요! 매개변수로 오브젝트를 받고, 오브젝트를 파괴시키지 않습니다.

 

일단, Bgm 이라는 오브젝트를 만들고 AudioSource를 만들어 배경음악을 넣습니다. 

참고로 DontDestroyOnLoad 함수는 최상위에 있는 오브젝트에만 작동하므로 최상위에 배치해주세요. (자식 No)

그리고 그 오브젝트에 DontDestroy 스크립트를 하나 만들어 넣습니다.

 

 

 

 

** 여기서 중요한 사실이 있습니다 **

DontDestroyOnLoad 함수를 그냥 사용할 경우, 씬 Load를 할 때마다 오브젝트가 중복 생성되는 문제가 생깁니다.

이를 방지하기 위해서 싱글턴 패턴 (Singleton Pattern) 을 사용합니다.

 

 

싱글턴 패턴이란?

"전역 변수를 사용하지 않고 객체를 하나만 생성 하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴"

즉, 하나의 객체로 중복 생성 없이 객체를 유지할 수 있습니다.

그럼 한번 DontDestroy 스크립트를 볼까요?

public class DontDestory : MonoBehaviour
{ 
    public static DontDestory Instance;
    AudioSource bgm;

    //Singleton Pattern
    private void Awake() {
        if (Instance != null) {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    public void BgmPlay() {
        if (Instance == null)
            bgm = gameObject.GetComponent<AudioSource>();
        else
            bgm = Instance.GetComponent<AudioSource>();
        bgm.enabled = true;
    }

    public void BgmStop() {
        if (Instance == null)
            bgm = gameObject.GetComponent<AudioSource>();
        else
            bgm = Instance.GetComponent<AudioSource>();
        bgm.enabled = false;
    }
}

정적 변수를 활용하여 싱글턴 패턴을 사용합니다.

그리고 Awake함수에서 이미 있는 인스턴스인지 아닌지 확인하여 중복 생성을 막습니다.

아래 BgmPlay와 BgmStop 함수는 배경음악을 틀고 끄기 위한 함수입니다.

 

 

 

 

 

 

 

2. JSON을 이용한 데이터베이스 구현

선택한 캐릭터, 구매한 캐릭터, 캐릭터 가격, 랭킹, 설정 등 필요한 데이터들을 저장해야 합니다.

유니티에서 데이터베이스를 구현하는 방법은 여러가지가 있습니다.

저는 그중에서도 JSON을 선택해 구현했습니다.

 

 

먼저, 에셋 스토어에서 JSON .NET For Unity를 다운받고 Import 합니다.

 

 

 

 

 

DSLManager라는 오브젝트와 넣어줄 스크립트를 만들었습니다.

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using System.IO;

public class Character {
    public string E_Name, K_Name;
    public int price;
    public bool selected, purchased;

    public Character(string E_Name, string K_Name, int price, bool selected, bool purchased) {
        this.E_Name = E_Name;
        this.K_Name = K_Name;
        this.price = price;
        this.selected = selected;
        this.purchased = purchased;
    }
}

public class Ranking {
    public int score, characterIndex;

    public Ranking(int score, int characterIndex) {
        this.score = score;
        this.characterIndex = characterIndex;
    }
}

public class Inform {
    public int money;
    public bool bgmOn, soundEffectOn, vibrationOn, Retry;

    public Inform(int money, bool bgmOn, bool soundEffectOn, bool vibrationOn, bool Retry) {
        this.money = money;
        this.bgmOn = bgmOn;
        this.soundEffectOn = soundEffectOn;
        this.vibrationOn = vibrationOn;
        this.Retry = Retry;
    }
}


public class DSLManager : MonoBehaviour {
    List<Character> characters = new List<Character>();
    List<Ranking> rankings = new List<Ranking>();
    List<Inform> informs = new List<Inform>();

    private void Awake() {
        //Store data initially
        if (!File.Exists(Application.persistentDataPath + "/Characters.json")) {
            characters.Add(new Character("BusinessMan", "회사원", 0, true, true));
            characters.Add(new Character("Rapper", "래퍼", 500, false, false));
            characters.Add(new Character("Secretary", "비서", 500, false, false));
            characters.Add(new Character("Boxer", "복서", 1000, false, false));
            characters.Add(new Character("CheerLeader", "치어리더", 1000, false, false));
            characters.Add(new Character("Sheriff", "보안관", 2000, false, false));
            characters.Add(new Character("Plumber", "배관공", 2000, false, false));

            rankings.Add(new Ranking(0, 7));
            rankings.Add(new Ranking(0, 7));
            rankings.Add(new Ranking(0, 7));
            rankings.Add(new Ranking(0, 7));

            informs.Add(new Inform(0, true, true, true, false));

            DataSave();
        }

        DataLoad();
    }


    //#.Data Save & Load
    public void DataSave() {
        string jdata_0 = JsonConvert.SerializeObject(characters);
        string jdata_1 = JsonConvert.SerializeObject(rankings);
        string jdata_2 = JsonConvert.SerializeObject(informs);
       
        byte[] bytes_0 = System.Text.Encoding.UTF8.GetBytes(jdata_0);
        byte[] bytes_1 = System.Text.Encoding.UTF8.GetBytes(jdata_1);
        byte[] bytes_2 = System.Text.Encoding.UTF8.GetBytes(jdata_2);

        string format_0 = System.Convert.ToBase64String(bytes_0);
        string format_1 = System.Convert.ToBase64String(bytes_1);
        string format_2 = System.Convert.ToBase64String(bytes_2);       

        File.WriteAllText(Application.persistentDataPath + "/Characters.json", format_0);
        File.WriteAllText(Application.persistentDataPath + "/Rankings.json", format_1);
        File.WriteAllText(Application.persistentDataPath + "/Informs.json", format_2);
    }



    public void DataLoad() {
        string jdata_0 = File.ReadAllText(Application.persistentDataPath + "/Characters.json");
        string jdata_1 = File.ReadAllText(Application.persistentDataPath + "/Rankings.json");
        string jdata_2 = File.ReadAllText(Application.persistentDataPath + "/Informs.json");
      
        byte[] bytes_0 = System.Convert.FromBase64String(jdata_0);
        byte[] bytes_1 = System.Convert.FromBase64String(jdata_1);
        byte[] bytes_2 = System.Convert.FromBase64String(jdata_2);

        string reformat_0 = System.Text.Encoding.UTF8.GetString(bytes_0);
        string reformat_1 = System.Text.Encoding.UTF8.GetString(bytes_1);
        string reformat_2 = System.Text.Encoding.UTF8.GetString(bytes_2);
        
        characters = JsonConvert.DeserializeObject<List<Character>>(reformat_0);
        rankings = JsonConvert.DeserializeObject<List<Ranking>>(reformat_1);
        informs = JsonConvert.DeserializeObject<List<Inform>>(reformat_2);
    }

일단 저장하고 싶은 데이터에 맞게 클래스를 만들고 생성자까지 작성합니다.

그리고 DSLManager 클래스에서 List<클래스>형 변수를 선언합니다.

 

Awake함수에서 초기저장을 위한 코드를 먼저 작성했습니다. 데이터 파일이 있어야 할 경로에

json파일이 없다면 데이터들을 List에 Add하고 DataSave 함수를 호출하여 데이터를 저장합니다.

그리고는 DataLoad 함수를 호출해 데이터를 로드합니다. 

**이 때 경로는 모바일에서도 가능한 persistentDataPath를 사용했습니다**

 

DataSave 함수는 리스트들을 암호화하고 지정경로에 JSON으로 변환하여 저장합니다.

DataLoad 함수는 지정경로의 암호화된 JSON파일들을 다시 복호화하고 변환하여 로드합니다.

 

 

 

 

 

 

그리고 DSLManager 클래스에 데이터를 저장하고 로드하는 여러가지 함수를 만듭니다.

(랭킹, 돈, 캐릭터 구매 , 캐릭터 선택 등을 저장하고 로드하는 함수들)

이전에 실행했던 게임의 내용들을 저장하고 그대로 불러와야 하기 때문에

게임을 시작할 때 Awake 함수에서 DataLoad를 호출한 뒤에 필요한 함수들을 호출하여 초기 셋팅을 합니다. 

 

대표적으로 예를 들어서, 캐릭터 선택창에서 선택했던 캐릭터 종류를 저장해놓고 불러오는 것, 랭킹 순위를 불러오는 것,

배경음/효과음/진동 설정 On/Off 여부를 저장하고 불러오는 것 등이 있습니다.

 

이 함수들과 로직을 하나하나 설명하기에 너무 많기 때문에 Github의 소스와 파일을 참고해주세요.

 

 

 

 

 

 

 

 

 

3. 버튼 동작 연결하기

UI에 수많은 버튼들이 있습니다.

버튼을 눌렀을 때 실행해야 할 동작들을 연결해야 합니다.

 

 

저는 버튼을 누를 때 크기 변화를 주어서 누르는 듯한 효과를 주었고, 효과음이 나게 했습니다. 

그러기 위해서 GameManager 스크립트에 함수를 만들었습니다.

오르기 버튼/방향전환 버튼 등과 같은 특정 버튼들은 필요한 행동을 하도록 if문으로 추가 작성했습니다.

public void BtnDown(GameObject btn) {
	btn.transform.localScale = new Vector3(0.8f, 0.8f, 0.8f);
	if (btn.name == "ClimbBtn")  player.Climb(false);
	else if (btn.name == "ChangeDirBtn") player.Climb(true);
}


public void BtnUp(GameObject btn) {
	btn.transform.localScale = new Vector3(1f, 1f, 1f);
	if (btn.name == "PauseBtn") {
	    CancelInvoke();  //Gauge Stopped
	    isGamePaused = true;
	} else if (btn.name == "ResumeBtn") {
	    GaugeReduce();
	    isGamePaused = false;
	}
}

 

 

스크립트를 추가 작성 후에 

모든 버튼에 EventTrigger 컴포넌트를 추가하여 PointerDown과 PointerUp 이벤트에 함수를 적용했습니다.

위 스크립트의 두 함수들은 매개변수로 오브젝트를 받으므로, 해당 버튼의 오브젝트를 드로그앤드랍 해주시면 됩니다.

 

 

 

 

 

특정 몇 개의 버튼 연결을 설명하겠습니다.

너무 많기 때문에 몇가지만 작성할 테니 참고하여 다른 버튼도 구현하시면 되겠습니다.

① 일단 메인메뉴 화면 (MainMenuUI) 에서 재생버튼 연결을 보겠습니다.

재생 버튼을 누르게 되면 MainMenuUI는 더이상 보이지 않게 되고,

게임이 진행되기 때문에 GameProgressUI가 보이게 됩니다.

이를 버튼의 OnClick에 반영해보겠습니다.

 

 

 

 

 

 

 

 

 

 

재생 버튼의 인스펙터 창에서 OnClick에 MainMenuUI와 GameProgressUI를 드로그앤드랍 합니다.

그리고 GameObject - SetActive 함수를 선택한 뒤에

보이지 않아야할 UI 오브젝트는 비활성화, 반대는 활성화 해주시면 됩니다.

 

 

 

 

② GameProgressUI의 오르기(Climb) 버튼입니다.

Climb 버튼도 마찬가지로 GameManager의 함수를 실행하도록 하고,  

버튼을 누르기 시작하면 버튼 바로 위에 있는 설명이 안보이도록 해야합니다.

 

 

 

 

 

 

 

 

 

 

 

다음과 같이 적용합니다.

 

 

 

 

③ GameOverUI의 메인메뉴 버튼입니다.

게임오버가 되고 메인메뉴 버튼을 누르면, 

MainMenuUI만 보이는 첫 화면으로 돌아가야겠죠?

즉, Scene을 다시 Load하면 됩니다.

 

 

 

 

 

 

 

 

 

 

GamaManager 스크립트에 함수를 추가하겠습니다.

먼저 코드 상단에 아래 using 문을 써주세요.

그리고 함수를 만들어 SceneManager 클래스의 LoadScene 함수를 호출합니다. 

using UnityEngine.SceneManagement;
 public void LoadScene(int i)
{
        SceneManager.LoadScene(i);
}

 

 

아래와 같이 매개변수인 인덱스는 버튼을 연결할 때 받으면 되겠습니다.

이 때 Scene의 인덱스는 유니티의 File - BuildSetting - Scenes In Build 창에서

올바르게 순서대로 지정되어있는지 확인해주세요. 

 

버튼 3개를 대표적으로 설명했고, 나머지 버튼들도 참고하여 구현하면 됩니다!

 

 

 

 

 

 

 

 

 

 

4. 캐릭터 선택 창 구현하기

현재 Scene이 2개입니다. 지금까지의 Scene이 아닌 캐릭터 선택 Scene을 구현하겠습니다.

 

일단, Hierarchy 입니다.

게임진행 Scene에서 메인카메라, DSLManager, BackGround는 복사하여 붙여넣고,

CharacterManager 오브젝트와 스크립트를 추가합니다.

 

 

 

 

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

public class CharacterManager : MonoBehaviour
{
    public int index;
    string[] characterNames = { "회사원", "래퍼", "비서", "복서", "치어리더", "보안관", "배관공" };
    public DSLManager dslManager;
    public GameObject selectBtn, purchaseBtn;
    AudioSource sound;
    public Image characterImage;
    public Text characterName, price;

    private void Awake() {
        index = dslManager.GetSelectedCharIndex();
        sound = GetComponent<AudioSource>();
        sound.mute = !dslManager.GetSettingOn("SoundBtn");
        ArrowBtn("null");
    }


    //Change the character's image, name and price when flipping from side to side
    public void ArrowBtn(string dir)
    {
        if (dir == "Right") {
             if (++index == dslManager.characterSprite.Length-1) index = 0; }

        if (dir == "Left") { 
             if (--index == -1) index = dslManager.characterSprite.Length - 2; }

        //Change the character information of the index
        characterImage.sprite = dslManager.characterSprite[index];
        characterName.text = characterNames[index];
        price.text = "₩" + dslManager.GetPrice().ToString();

        //Determining the type of button according to purchase
        selectBtn.SetActive(dslManager.IsPurchased(index));
        purchaseBtn.SetActive(!dslManager.IsPurchased(index));
    }
}

CharacterManager클래스에서는 Awake 함수로 필요한 초기 셋팅을 합니다.

그리고 왼쪽/오른쪽 화살표 버튼 연결에 관한 함수 ArrowBtn이 있습니다.

 

 

왼쪽/오른쪽 화살표 버튼을 누르면 순서대로 캐릭터 목록이 보여야합니다.

인덱스를 증감하여 인덱스로 각 캐릭터의 종류를 구분합니다.

각 캐릭터 이미지의 sprite, 이름 text, 가격 text를 DSLManager클래스의 데이터로부터 불러와 로드합니다.

 

각 캐릭터의 이미지 sprite는 DSLManager의 CharacterSprite에서 지정해두었습니다.

캐릭터 순서에 잘 맞게 sprite를 드로그앤드랍 해주었습니다. 

 

마지막 인덱스는 그냥 투명 sprite로, 랭킹UI에서 순위의 점수가 0점일 때를 위해 넣었습니다.  

 

 

 

 

 

그리고 구매하지 못한 캐릭터는 선택 버튼 대신 가격과 함께 구매버튼이 나타나야합니다.

이를 위해서 해당 캐릭터의 구매여부 데이터를 불러와 활성화/비활성화를 지정합니다.

구매한 캐릭터(L) / 구매하지않은 캐릭터(R)

 

 

다 작성한 후, 왼쪽 버튼의 인스펙터 창에서 EventTrigger 컴포넌트를 추가합니다.

PointerUp 이벤트에 위의 함수를 지정하고 "Left"라고 매개변수를 써줍니다.

오른쪽 버튼도 똑같이 지정하고 "Right"로 써줍니다.

 

선택버튼과 구매버튼은 DSLManager에서 데이터를 저장하도록 처리합니다.

전체 소스코드를 참고해주세요.

 

 

 

 

 

 

 

 

 

5. 모바일 빌드하기

모바일 빌드 환경설정은 다음 링크를 참고하여주세요.

https://ozlael.tistory.com/78

 

유니티 안드로이드 빌드 환경 설정 가이드

안드로이드 개발 환경 설정 -------- 주의 --------- 이 글의 내용은 더 이상 필요가 없는 설정입니다. 이제는 유니티 허브에서 모듈 추가 시 체크만 해주면 안드로이드 빌드 설정은 유니티가 알아서 해줍니다. 아..

ozlael.tistory.com

 

여기서 주의할 점!!

JSON을 위한 에셋을 사용하기 위해서 설정해야할 것이 있습니다. 

Edit - ProjectSettings - Player - Other Settings 에서 

Api Compatibility Level 를 .Net 4.x로 바꾸어야 합니다.

 

 

 

 

 

 

 

 

 

 

 

6. GitHub 소스코드와 파일 링크, 게임 실행 영상

https://github.com/choijoohee213/Infinite-Stairs

 

choijoohee213/Infinite-Stairs

무한의계단 Mobile Game. Contribute to choijoohee213/Infinite-Stairs development by creating an account on GitHub.

github.com

 

 

 

 

게임 실행 Youtube 영상입니다. 

 

 

 

 

 

 

이것으로 무한의계단 설명을 마치겠습니당ㅎ,ㅎ

봐주셔서 감사합니다!😍

 

 

 

 

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday