어제자 글에서 이미 다룬 내용이지만, Git용으로 만들어 내용이 달라진 부분이 있어 재포스팅함.
스테이지 선택 + 스테이지 구분 가능한 시작화면 만들기
기존의 난이도 선택지는 그대로 두고, 한 버튼을 복사해서 새로운 시작버튼을 만든다.
플레이 중 스테이지의 최단 기록도 띄워주어야 하기때문에 메인 씬에서도 새로운 텍스트를 만든다.
어제와는 다르게 난이도별 클리어 기록은 Difficulty.cs에, 최단 기록은 로컬 데이터에 저장했다. 그 이유는
1. 배운 거 써보고 싶어서
2. 초기화 버튼 따로 만들고싶지 않아서
작동 순서에 따른 스크립트 순서대로 분석해보도록 하자.
public class Difficulty { public static int Diificulty; //원래 있던 부분 public static bool easyclear = false; public static bool normalclear = false; }
난이도는 이지, 노말, 하드의 세 단계로 구분되며 이전 단계를 클리어해야 다음 단계로 넘어갈 수 있도록 설정해두었다. 그럼 클리어 기록은 이지, 노말 두개만 있으면 된다.
어제 포스팅에서 static은 Data 영역에 저장되므로 변수를 수정하거나 읽을 때 클래스 객체를 만들 필요가 없다고 얘기했다. 그렇기 때문에 씬이 이리저리 넘어가더라도 호출할 수만 있다면 그 값을 저장하고 있다.
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. 이지 ,노말 클리어 기록에 따라서 노말, 하드 난이도 버튼의 이미지 색 변경 + 버튼 컴포넌트 비활성화 (클릭이 되지 않게하기위해)
로 작성되었다.
코드가 길어 작성한 코드만 올림
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(transform 시작지점,transform 도착지점, 0~1사이의 숫자)
인데 저 숫자 부분에는 시작지점과 도착지점 사이의 거리를 1이라 했을 때, 그 퍼센테이지만큼을 얘기한다.
보통 update()처럼 프레임이 흘러야 위치가 바뀌도록 하는 것이 매끄럽게 이동할 수 있기때문에 가능하다면 코루틴을 사용하는 것이 좋을 듯 하다.
그리고 FadeCurve를 사용하는 방법을 다시 배워왔는데
1. FadeCurve를 사용할 스크립트에 AnimationCurve fadeCurve를 선언
2. 인스펙터에서 FaedCurve 설정
3. Vector3.Lerp(시작지점,끝지점,fadeCurve.Evaluate(percent));
percent에 따른 fadeCurve의 값으로 속도의 변화를 선형이 아닌 다른 모양으로 넣어줄 수 있다.
오늘 작성한 코드 중 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로 만들 수 있는 것도 참고하자.
형변환이라고도 한다. 일반적으로 형변환을 할 때에 조건은 "말이 되야된다는 것"
예를 들어 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.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(); // 모두 삭제하기
어제 작성한 easy.gameObject.Find("easy");가 null 오류가 났던 기억이 있다. 어제는 canvas의 자식오브젝트로 잡혀있어서 그런줄 알았지만, 알고보니 비활성화된 Object를 못 찾는 거였다. 그래서 여러가지 Find함수의 활용법을 정리하고자 한다. (출처 : 강정이좋아)
먼저 Find를 사용해 오브젝트를 찾을 때 2가지 클래스로 나뉜다. 클래스별로 정리해보자.
1.Find 이름으로 자식 오브젝트를 검색 > 가장 처음에 나오는 오브젝트를 반환
2. GetComponentInChildren 이 컴포넌트를 가진 처음 나타난 자식 오브젝트를 반환 //s 넣으면 배열로도 가능
3.GetComponentInParent 이 컴포넌트를 가진 처음 나타난 부모 오브젝트를 반환 //s 넣으면 배열로도 가능
어제 $를 사용할 때 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을 직접 넣어서 표현해준다.
나는 개발이나 기획을 원하지만, 일반적으로 회사에서 동시에 하기는 힘들다. 그래서 보통 둘 다를 원하는 사람은 인디개발로 간다고 한다.