12/27 본캠프#4

guno park·2023년 12월 27일
0

본캠프

목록 보기
4/77

미니프로젝트

스테이지 선택과 현재 해금한 스테이지가 구분 가능한 시작 화면 만들기, 플레이 중 해당 스테이지의 최단 기록 띄워주기

어제자 글에서 이미 다룬 내용이지만, Git용으로 만들어 내용이 달라진 부분이 있어 재포스팅함.

1. 세팅

start Scene

스테이지 선택 + 스테이지 구분 가능한 시작화면 만들기
기존의 난이도 선택지는 그대로 두고, 한 버튼을 복사해서 새로운 시작버튼을 만든다.

Main Scene

플레이 중 스테이지의 최단 기록도 띄워주어야 하기때문에 메인 씬에서도 새로운 텍스트를 만든다.

2. 코드 작성

어제와는 다르게 난이도별 클리어 기록은 Difficulty.cs에, 최단 기록은 로컬 데이터에 저장했다. 그 이유는
1. 배운 거 써보고 싶어서
2. 초기화 버튼 따로 만들고싶지 않아서
작동 순서에 따른 스크립트 순서대로 분석해보도록 하자.

Difficulty.cs

public class Difficulty
{
    public static int Diificulty; //원래 있던 부분
    public static bool easyclear = false;
    public static bool normalclear = false;    
}

난이도는 이지, 노말, 하드의 세 단계로 구분되며 이전 단계를 클리어해야 다음 단계로 넘어갈 수 있도록 설정해두었다. 그럼 클리어 기록은 이지, 노말 두개만 있으면 된다.
어제 포스팅에서 static은 Data 영역에 저장되므로 변수를 수정하거나 읽을 때 클래스 객체를 만들 필요가 없다고 얘기했다. 그렇기 때문에 씬이 이리저리 넘어가더라도 호출할 수만 있다면 그 값을 저장하고 있다.

startBtn.cs

public class startBtn : MonoBehaviour
{
    public GameObject easy;
    public GameObject normal;
    public GameObject hard;
    public void gameStart(int diff) //원래 있던거
    {
        Difficulty.Diificulty = diff;
        SceneManager.LoadScene("MainScene");
    }
    public void diffChoice()
    {
        gameObject.SetActive(false);
        easy.SetActive(true);
        normal.SetActive(true);
        hard.SetActive(true);
        if (Difficulty.easyclear != true)
        {
            normal.GetComponent<Image>().color = Color.gray;
            normal.GetComponent<Button>().enabled = false;
        }
        if (Difficulty.normalclear != true)
        {
            hard.GetComponent<Image>().color = Color.gray;
            hard.GetComponent<Button>().enabled = false;
        }
    }
}

오늘은 심플하게 드래그로 레퍼런스를 찾아줬다.
순서도는 (시작버튼 클릭 > 난이도 버튼 SetAcitve(true) > 난이도 선택 > 메인 씬 로드)의 순서다.
어차피 게임 스타트 하는 부분은 이미 완성 되어있기때문에 내가 한 일은 앞서 말한 조건에 따라 해금된 난이도를 구분하고, 선택 가능 여부를 판별하여 작동하는 구문을 작성하는 것이다.
diffchoice()를 뜯어보자면
1. 시작 버튼 비활성화 -> 2. 난이도 버튼 활성화 -> 3. 이지 ,노말 클리어 기록에 따라서 노말, 하드 난이도 버튼의 이미지 색 변경 + 버튼 컴포넌트 비활성화 (클릭이 되지 않게하기위해)
로 작성되었다.

GameManager.cs

코드가 길어 작성한 코드만 올림

    public Text besttime;
    void Start()
    {        
        InitGame();      
    }   

besttime 텍스트를 먼저 참조해주고 InitGame()이라는 함수를 실행

    public void InitGame() //카드를 실제로 생성하기 전 부분
    {       
        if (diff == Diff.Easy)
        {            
            besttime.text = $"이지 최단시간 : {string.Format("{0:0.##}",PlayerPrefs.GetFloat("easybesttime"))}";
        }
        else if (diff == Diff.Normal) {
            besttime.text = $"노말 최단시간 : {string.Format("{0:0.##}", PlayerPrefs.GetFloat("normalbesttime"))}";
        }
        else if (diff == Diff.Hard)
        {
            besttime.text = $"하드 최단시간 : {string.Format("{0:0.##}", PlayerPrefs.GetFloat("hardbesttime"))}";
        }     
}
PlayerPrefs.GetFloat를 이용해 로컬에 저장된 최단 기록을 불러옴.
이 때 숫자가 너무 길어지지 않도록 String.Format을 사용해서 소숫점 2자리까지만 사용함.
    public void isMatched()
    {        
            if (cardsLeft == 2) //카드를 다 맞춰서 게임이 끝나는 부분 -> 클리어
            {                
                if (diff == Diff.Easy){
                Difficulty.easyclear = true;
                if (PlayerPrefs.GetFloat("easybesttime") == 0 || PlayerPrefs.GetFloat("easybesttime") > time)
                        PlayerPrefs.SetFloat("easybesttime", time);
                }
                else if (diff == Diff.Normal){
                    Difficulty.normalclear = true;
                    if (PlayerPrefs.GetFloat("normalbesttime") == 0 || PlayerPrefs.GetFloat("normalbesttime") > time)
                        PlayerPrefs.SetFloat("normalbesttime", time);
                }
                else if(diff == Diff.Hard){
                    if (PlayerPrefs.GetFloat("hardbesttime") == 0 || PlayerPrefs.GetFloat("hardbesttime") > time)
                        PlayerPrefs.SetFloat("hardbesttime", time);
                }
                besttime.gameObject.SetActive(false);
            }
            cardsLeft -= 2;//남은 카드 장 수를 앞에 두면 아직 남아있는 두 장을 뒤집기도 전에 게임이 끝나버린다.            
        }    
        }
        void GameEnd()
    {        
        besttime.gameObject.SetActive(false);
    }

isMatched()에서 카드가 2장 남았을 때가 실제로 클리어했다고 판정되는 부분이기에
그곳에 enum을 이용해 난이도별 클리어 기록을 Difficulty.cs의 각 난이도별 bool에 저장해줌.
그리고 로컬에 key값을 이용해 난이도별 최단기록이 있는지 판별하고, 없거나 현재 시간보다 길다면 현재 기록을 저장.
그 후에는 다른 UI에 방해되기때문에 비활성화 해준다. 이 동작은 게임이 그냥 끝났을 때도
마찬가지로 적용된다.

추가로 배운것

오브젝트와 컴포넌트 비활성화

오브젝트

gameObject.SetActive(false);로 간단하게 끔

컴포넌트

출처 : DragonTory
컴포넌트 추가 : gameObject.AddComponent();
컴포넌트 삭제 : Animator animator = gameObject.GetComponent();
Destroy(animator);
활성 / 비활성 : Component.enabled = true / flase;

Vector3.Lerp

어제 익힌 Vector3.Lerp를 다시 정리하는 시간!
Vector3.Lerp(transform 시작지점,transform 도착지점, 0~1사이의 숫자)
인데 저 숫자 부분에는 시작지점과 도착지점 사이의 거리를 1이라 했을 때, 그 퍼센테이지만큼을 얘기한다.
보통 update()처럼 프레임이 흘러야 위치가 바뀌도록 하는 것이 매끄럽게 이동할 수 있기때문에 가능하다면 코루틴을 사용하는 것이 좋을 듯 하다.
그리고 FadeCurve를 사용하는 방법을 다시 배워왔는데
1. FadeCurve를 사용할 스크립트에 AnimationCurve fadeCurve를 선언
2. 인스펙터에서 FaedCurve 설정
3. Vector3.Lerp(시작지점,끝지점,fadeCurve.Evaluate(percent));
percent에 따른 fadeCurve의 값으로 속도의 변화를 선형이 아닌 다른 모양으로 넣어줄 수 있다.

Array에 관하여

오늘 작성한 코드 중 startBtn.cs를 다시 보면

 public GameObject easy;
 public GameObject normal;
 public GameObject hard;

이런 구문이 있다. 이걸 좀더 간결하게 바꿔보자.

public GameObject[] difficult;

이렇게 바꿔주면 3줄짜리를 한줄로 바꿀수도 있어 가독성도 좋아지고 Public이기 때문에 인스펙터에서도 배열크기를 바꿔줄수있다.

그리고 요소의 호출도 간단하다. 그 전에 썼던 코드에서 바꿔보자면

normal.GetComponent<Image>().color = Color.gray;
normal.GetComponent<Button>().enabled = false;
///////////////////////////////////////////////
difficult[1].GetComponent<Image>().color = Color.gray;
difficult[1].GetComponent<Button>().enabled = false; 

이런식으로 바꿔줄 수 있다. 비슷한 조건에 따라 동작하는, 거의 묶여있다고 생각되는 오브젝트들을 컨트롤 할 때, 많은 변수를 만들지 말고 Array로 관리해보자. 비단 GameObject뿐만 아니라 어지간한 자료형은 모두 Array로 만들 수 있는 것도 참고하자.

Casting

형변환이라고도 한다. 일반적으로 형변환을 할 때에 조건은 "말이 되야된다는 것"
예를 들어 int형의 123456을 자료형으로 바꾸면 "123456"이 된다. 반대로 String "123456"도 어찌저찌하면 int형으로 변환이 가능하다. 하지만 "부트캠프" -> int형으로 변환하지는 못한다는 이야기이다.
이 이야기를 왜 하느냐,
enum의 경우 기본적으로 각 요소에 할당된 값들이 int값이기 때문에 캐스팅이 가능하다

public enum Diff
{
  Easy, Normal, Hard
}  
Diff diff = (Diff)0;    //출력값 = Easy
Diff diff = (Diff)1;	//출력값 = Normal
Diff diff = (Diff)2;	//출력값 = Hard

이렇듯 int값에 enum으로 형변환을 해주면 그 숫자에 해당하는 enum의 값이 출력되게 된다.
출력된 값들은 Diff 자료형이므로 ToString 해주면 문자열로 그대로 사용할 수 있다.
똑같은 느낌으로
난이도가 easy일 때
Diff diff.ToString는 "easy"와 똑같다고 할 수 있다.

PlayerPrefs 더 알아보기

내가 기본적으로 알고 있는 사용법

PlayerPrefs.Get자료형(Int,Float,String)("KeyName") //값 불러오기
PlayerPrefs.Set자료형(Int,Float,String)("KeyName",Value) //값 저장하기

추가로 기억해야 할 사용법 (출처 : Dev.Park Library)

PlayerPrefs.HasKey("KeyName") //특정 키가 존재하는지 확인, bool로 반환
PlayerPrefs.DeleteKey("Key_Name"); // key 삭제하기 
PlayerPrefs.Save() //변경된 모든 키 값을 물리적인 저장공간에 저장
PlayerPrefs.DeleteAll(); // 모두 삭제하기 

Find 관련 정리

어제 작성한 easy.gameObject.Find("easy");가 null 오류가 났던 기억이 있다. 어제는 canvas의 자식오브젝트로 잡혀있어서 그런줄 알았지만, 알고보니 비활성화된 Object를 못 찾는 거였다. 그래서 여러가지 Find함수의 활용법을 정리하고자 한다. (출처 : 강정이좋아)

자주 쓸 것부터 정리

먼저 Find를 사용해 오브젝트를 찾을 때 2가지 클래스로 나뉜다. 클래스별로 정리해보자.

1)GameObject.

  1. Find 오브젝트 이름으로 검색 > 가장 처음에 나오는 오브젝트를 반환
  2. FindWithTag 태그 이름으로 검색 > 가장 처음에 나오는 오브젝트를 반환
  3. FindGameObjectsWithTag 태그 이름으로 검색 > 나타난 오브젝트들을 배열로 반환

2)Transform.

1.Find 이름으로 자식 오브젝트를 검색 > 가장 처음에 나오는 오브젝트를 반환
2. GetComponentInChildren 이 컴포넌트를 가진 처음 나타난 자식 오브젝트를 반환 //s 넣으면 배열로도 가능
3.GetComponentInParent 이 컴포넌트를 가진 처음 나타난 부모 오브젝트를 반환 //s 넣으면 배열로도 가능

$와 String.Format

어제 $를 사용할 때 float를 사용하면 길어지는 문제점이 있었는데, 공부하다보니 $를 사용하면 문자열 안에 변수나 명령어를 그대로 사용할 수 있었고, String.Format은 자릿수 설정하는 기능을 하니 섞으면 어떨까해서 작성해봤는데 잘 동작했다. 기억해두면 두고두고 써먹을 수 있을 듯.

서식지정문자열

오늘 작성한 코드 중에

besttime.text = $"이지 최단시간 : {string.Format("{0:0.##}",PlayerPrefs.GetFloat("easybesttime"))}";

라는 게 있었다. 조원분이 TIL 정리하다가 더 좋은 방법을 발견하셔서 여기 정리한다.

besttime.text = $"이지 최단시간 :" + Float.ToString("N2");
besttime.text = $"이지 최단시간 : {string.Format("{0:0.##}",Float)}";
besttime.text = $"이지 최단시간 : {Float:F2}"

위 세가지의 출력값은 똑같다. ToString과 string.Format은 앞서 설명했기에 마지막 서식지정문자열만 설명하고 넘어간다.
(출처 : 반토막의 자유일지, 세상속으로)
일반적으로 string.Format과 함께 이용하나 위처럼 변수:서식지정문자열로 간단하게 작성도 가능하며 타입으로
N : Number타입

var n = string.Format("{0:N2} {1:N4}", 1234.567, 342.35453);
var n1 = $"{1234.567:N2} {342.35453:N4}"; // 위에 앞으로 쓰겠다한 부분
Console.WriteLine(n);   // 1234.56 342.3545
Console.WriteLine(n1);  // 1234.56 342.3545

D : Decimal 타입 / 부동소수점형(float)의 정밀도 문제 해결용

var d = string.Format("{0,5:D9}", 12345); //5는 실제로 출력에 영향을 주지 않는다 > 그럼 왜있지?
var d1 = $"{12345:D9}";
Console.WriteLine(d);  // 000012345
Console.WriteLine(d1); // 000012345

E : Scientific

var e = string.Format("{0:E}", 12345.6); // 1.23456E+004
  Console.WriteLine(e);

F : Fixed Point

var f = string.Format("{0:F3}", 12345.6); 
  Console.WriteLine(f); // 12345.600g

원하는 곳에 적절하게 사용하도록 하자.
이 외에도 몇개 더 있지만 그런 경우는 :로 바로 붙이는 것보다 String.Format하는 편이 더 좋아보인다.
예시)전화번호 형태로 표시하기

double num2 = 01012345678;
String str_num = String.Format("{0:###-####-####}",num2);
//10-1234-5678
str_num = String.Format("{0:0##-####-####}",num2);
//010-1234-5678

이 경우에는 맨 앞의 0이 표현되지 않기때문에 문자열 형태에 0을 직접 넣어서 표현해준다.

면담내용 중 기억할 것

나는 개발이나 기획을 원하지만, 일반적으로 회사에서 동시에 하기는 힘들다. 그래서 보통 둘 다를 원하는 사람은 인디개발로 간다고 한다.

0개의 댓글