12/26 본캠프 #3

guno park·2023년 12월 26일
0

본캠프

목록 보기
3/77
post-custom-banner

enum 공부

다른분이 사용한 enum을 공부해보자

코드

Difficulty.cs

public class Difficulty
{
    public static int Diificulty;
}

startBtn.cs

    public void gameStart(int diff)
    {
        Difficulty.Diificulty = diff;
        SceneManager.LoadScene("MainScene");
    }


startBtn에서 On Click시 할당된 diff의 값을 받아서 Difficulty.cs에 넘겨준다. 이 때 그 값은 static으로 Data 영역에 저장되므로 변수를 수정하거나 읽을 때 클래스 객체를 만들 필요가 없다.
GameManager.cs

public enum Diff
{
    Easy, Normal, Hard //순서대로 0,1,2의 값을 가진다.
}

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;   
    [SerializeField] GameObject card;   
      [Header("# Notice")]   
    [SerializeField] Diff diff;  
    GameObject cards;    
    int cardCnt;
    Dictionary<int, card> cardDictionary;
    
    private void Awake()
    {
        Instance = this;
    }
    void Start()
    {
        SetDifficult();
        InitGame();       
    }  

    private void SetDifficult()
    {
        diff = (Diff)Difficulty.Diificulty;        
        switch (diff)
        {
            case Diff.Easy:
                Debug.Log("이지모드");
                cardCnt = 4;
                break;
            case Diff.Normal:
                Debug.Log("노멀모드");
                cardCnt = 16;
                break;
            case Diff.Hard:
                Debug.Log("하드모드");
                cardCnt = 36;
                break;
        }
    }   

    public void InitGame()
    {
        Time.timeScale = 1.0f;
        firstCard = secondCard = null;
        List<int> rtans = new List<int>();

        SpawnCards();     
        cardsLeft = cards.transform.childCount;
    }

    private void SpawnCards()
    {
        List<int> rtans = new List<int>();

        for (int i = 0; i < cardCnt / 2; i++)
        {
            int random = -1;
            while (true)
            {
                random = UnityEngine.Random.Range(0, sprites.Length);
                if (rtans.Any(x => x == random))
                    continue;

                break;
            }            

            rtans.Add(random);
            rtans.Add(random);
        }

        rtans = rtans.OrderBy(item => UnityEngine.Random.Range(-1.0f, 1.0f)).ToList();

        int raw = (int)Mathf.Sqrt(cardCnt);        
        for (int y = 0; y < raw; y++)
        {
            for (int x = 0; x < raw; x++)
            {
                GameObject newtile = Instantiate(tile);
                newtile.transform.parent = cards.transform;                
                card newCard = Instantiate(card).GetComponent<card>();
                newCard.transform.parent = newtile.transform;
                newCard.Setup(rtans[y * raw + x]);

                float offset = 1.5f;
                float startAnchor = offset * (raw / 2) - 0.75f;

                float posX = -startAnchor + offset * x;
                float posY = startAnchor - offset * y;

                newtile.transform.position = new Vector3(posX, posY, 0);
            }            
        }        
    }  
}

넘어온 데이터를 SetDifficult()라는 함수를 통해
diff - (Diff)Difficulty.Difficulty;로 enum diff의 값을 세팅해준 후
스위치문을 통해 카드의 총 개수를 정해준다. 그 개수로 카드를 Spawn하면 끝
enum을 활용한 방법에 대해서는 이해했지만, List와 int[]의 차이가 궁금해졌다.

미니프로젝트

나만의 애니메이션 만들기

혼자하는 프로젝트에는 만들어서 끼워놨는데, Git에 올라온 버전으로도 하나 작성해보았다.

1.세팅

먼저 지난번 포스트에서도 적었지만, 애니메이터가 있는 오브젝트는 절대좌표로 움직인다고 얘기했었다. 내가 애니메이터를 넣어놓은 것은 card프리팹에 있으므로 이 애니메이션을 상대좌표로 이용하기 위해서는 두가지 방법이 있다.
1. 부모 오브젝트를 만들고, 하위에 card프리팹을 복제한다.
2.애니메이터를 자식 오브젝트로 옮긴다.
1번은 이미 해본적 있는 방식이고, 2번은 하위의 back 오브젝트로 옮겨서 해봤는데 카드가 open(),close()될 때마다 entry로 넣어둔 애니메이션 동작하는 문제가 생겼다. 그래서 그냥 1번으로 하기로했다. 그래서 빈 부모 오브젝트를 같이 복제해주기 위해 프리팹화 해주었다.

2. 코드작성

준비된 빈 오브젝트 프리팹을 생성하고 위치를 잡아준 후, 그 자식으로 card 프리팹을 복제하기만하면 된다.

for (int x = 0; x < raw; x++)
{
  GameObject newtile = Instantiate(tile);
  newtile.transform.parent = cards.transform;     //cards 밑으로 빈 오브젝트 집어넣음           
  card newCard = Instantiate(card).GetComponent<card>();
  newCard.transform.parent = newtile.transform; //빈 오브젝트 밑으로 card 집어넣음
  newCard.Setup(rtans[y * raw + x]);

  float offset = 1.5f;
  float startAnchor = offset * (raw / 2) - 0.75f;

  float posX = -startAnchor + offset * x;
  float posY = startAnchor - offset * y;

  newtile.transform.position = new Vector3(posX, posY, 0); //빈 오브젝트의 위치 잡아줌
}            

이렇게 하면 정상적으로 동작한다.

결과에 점수 표시

1. 텍스트 세팅

남은 시간과 매칭횟수를 바탕으로 점수를 산출할 것이고, 그 점수를 보여줄 Text를 만들어주었다.

2.점수 산출 코드 작성

점수 산출은 게임이 끝났을 때 최종적으로 계산해 띄워주면 된다고 생각해
GameEnd() - 다 못맞췄을때 작동하는 구문, cardsLeft ==2 일 때 두 곳에 작성해주었다.

remain.text = (30.0f - time).ToString("N2");
score.text = ((int)((30.0f - time) * 10 - count)).ToString();

remain.text는 남은 시간을 텍스트로 보여준다.
score.text에는 남은 시간과 카운트 횟수로 점수를 산출한다.
※점수 산출 방식 = (30초-걸린시간)10 - 매칭시도횟수

※Git 작성본 점수 산출 방식 (30초-걸린시간)
10 - (매칭 횟수 - 난이도별 최소매칭횟수)

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

1.세팅

스테이지 선택과 최단기록 띄워주기를 위한 UI 세팅
기존에 되어있던 시작버튼 대신 난이도로 있었던 부분을 되돌려서
시작 > 난이도 선택 > 게임 진행 순으로 바꿔주었다.


그리고 진행 중 스테이지의 최단 기록을 나타내는 text도 추가했다.

2-1.기능하는 코드 작성

startBtn.cs

작성한 코드를 먼저 풀어보면

1. 필요한 오브젝트 가져오기

GameObject resetbtn;  
GameObject canvas;

  private void Start()
  {       
      resetbtn = GameObject.Find("reset");
		canvas = GameObject.Find("Canvas");
      DontDestroyOnLoad(gameObject);       
  }

필요한 오브젝트 두개를 먼저 선언해주고 find를 통해 가져온다.

2. 시작 버튼에 기능 추가하기

public void diffchoice()
  {
      gameObject.SetActive(false);
      resetbtn.SetActive(false);
      canvas.transform.Find("starteasy").gameObject.SetActive(true);
		canvas.transform.Find("startnormal").gameObject.SetActive(true);
		canvas.transform.Find("starthard").gameObject.SetActive(true);       
      if (PlayerPrefs.GetInt("cleareasy") != 1)
      {
          canvas.transform.Find("startnormal").gameObject.GetComponent<Image>().color = Color.gray;
      }
      if (PlayerPrefs.GetInt("clearnormal") != 1)
      {
          canvas.transform.Find("starthard").gameObject.GetComponent<Image>().color = Color.gray;
      }

난이도 선택 기능이 실행되면, 시작 버튼과 초기화버튼이 사라지고, 3가지의 난이도 선택 버튼만 나타난다. 이 때, 로컬에 저장된 이지,노말의 클리어 기록에 따라 노말,하드 난이도의 색상이 선택 불가능하다는 의미로 회색이 된다.

3. 리셋버튼만들기

안에서 편하게 기능 확인을 위해 리셋기능을 만들어주었다

public void reset()
  {
      PlayerPrefs.SetInt("cleareasy", 0);
      PlayerPrefs.SetInt("clearnormal", 0);
      PlayerPrefs.SetInt("clearhard", 0);
      PlayerPrefs.SetInt("besteasytime", 0);
      PlayerPrefs.SetInt("bestnormaltime", 0);
      PlayerPrefs.SetInt("besthardtime", 0);
  }

4. 난이도별 기능 추가

public void gameStartnormal()
  {
      if (PlayerPrefs.GetInt("cleareasy") == 1)
      {            
          diff.GetComponent<difficulty>().SetDifficulty(Difficulty.normal);
          SceneManager.LoadScene("MainScene");
      }
      
  }
  public void gameStarthard()
  {
      if (PlayerPrefs.GetInt("clearnormal") == 1)
      {            
          diff.GetComponent<difficulty>().SetDifficulty(Difficulty.hard);
          SceneManager.LoadScene("MainScene");
      }        
  }

그 전 단계를 클리어하지 않으면 실행되지 않게 하기위하여 조건을 걸어준다.

오류사항

원래는 난이도별 버튼을 참조할 때

easy = GameObject.Find("starteasy"); 
normal = GameObject.Find("startnormal");
hard = GameObject.Find("starthard");

이렇게 참조하려 했으나 null reference exception object reference not set to an instance of an object > 참조하려는 대상이 없을 때 뜨는 오류가 발생하여 이유가 뭘까 곰곰히 생각해봤는데

이 버튼들이 Canvas라는 부모 오브젝트의 밑에 있어서 그런게 아닐까하고 Canvas를 참조한 뒤 그 밑의 자식오브젝트를 찾아 바꿔주는 방식을 채택했더니 오류가 발생하지 않았다.

2-2.GameManager.cs

1. 세팅에서 추가한 오브젝트 참조하기

public Text besttime1;
public Text besttime2;

드래그로 참조해주었다.

2. 난이도별 문자 바꿔주기

void Start()
{
   Difficulty = GameObject.Find("difficulty");
   diff = (int)Difficulty.GetComponent<difficulty>().diff;    
   if (diff == 1)
   {
       choose = new Sprite[4];
       besttime1.text = "이지 최단 시간";
       besttime2.text = PlayerPrefs.GetFloat("besteasytime").ToString("N2");
   }
   else if (diff == 2)
   {
       choose = new Sprite[8];
       besttime1.text = "노말 최단 시간";
       besttime2.text = PlayerPrefs.GetFloat("bestnormaltime").ToString("N2");
   }

   else if (diff == 3)
   {
       choose = new Sprite[18];
       besttime1.text = "하드 최단 시간";
       besttime2.text = PlayerPrefs.GetFloat("besthardtime").ToString("N2");
   }
   InitGame();        
}

난이도별로 "ㅁㅁ 최단 시간"으로 바꿔주며, 현재 최고 기록을 보여준다. 하지만 처음 시작할 때는 최고기록이 0으로 작성되어 있을 것이다.

최고기록 바꿔주기

최고기록은 로컬값으로 저장하도록 했고, 최고기록을 저장할 때는 클리어한 때이기 때문에 이 때 클리어 했다는 기록도 로컬로 저장해주었다.

if (cardsLeft == 2)
{
  Time.timeScale = 0;   
  endTxt.gameObject.SetActive(true);   
  besttime1.gameObject.SetActive(false);
  besttime2.gameObject.SetActive(false);
  if (diff == 1)
  {
      PlayerPrefs.SetInt("cleareasy", 1);
      if (PlayerPrefs.GetFloat("besteasytime") > time || PlayerPrefs.GetFloat("besteasytime") ==0f)
      {
          PlayerPrefs.SetFloat("besteasytime", time);
      }                   
  }
  else if (diff == 2)
  {
      PlayerPrefs.SetInt("clearnormal", 1);
      if (PlayerPrefs.GetFloat("bestnormaltime") > time || PlayerPrefs.GetFloat("bestnormaltime") == 0f)
      {
          PlayerPrefs.SetFloat("bestnormaltime", time);
      }                   
  }
  else if (diff == 3) { 
      PlayerPrefs.SetInt("clearhard", 1);
  if (PlayerPrefs.GetFloat("besthardtime") > time || PlayerPrefs.GetFloat("besthardtime") == 0f)
  {
      PlayerPrefs.SetFloat("besthardtime", time);
  }                
  }
}

게임이 끝날 때 최단기록 ui도 false로 바꿔주고(겹치기때문에), 현재 기록이 저장된 기록보다 작거나 저장된 기록이 없으면(0이면) 기록을 바꿔주는 것으로 작성하였다.

오류사항

처음에 대충 읽고 진행하다가 게임 끝나면 최단기록 보여주는 건줄 알고 게임 끝나고 데이터 저장할 때랑 게임 패배했을 때 띄워줬었는데 다시 읽어보니 게임 진행 중이라고 되있어서 다 바꿨다.
한글은 끝까지 읽자

추가로 배운 것들

animation 프로퍼티 추가



이렇게 애니메이션 만들 때 하위 오브젝트에 접근이 용이하다.

Dictionary<Tkey,TValue>

작성했을 때 한 키값을 입력하면 해당하는 값을 출력하는 사전형 데이터이다.
EX)Dictionary로 선언된 변수에 A를 넣으면 TValue값을 뱉어냄
Dictionary<0,hppotion> = 0번칸에 hp포션 저장한다.

Vector3.Lerp

Vector3간의 선형보간법으로 끝점의 값이 주어졌을 때 그 사이에 위치한 값을 추정하기 위하여 직선 거리에 따라 선형적으로 계산하는 방법 (출처 : Vector3.Lerp)
오브젝트를 부드럽게 이동하거나 회전할 시에 사용한다고 한다.

public GameObject A;
Vector3 pos = new Vector3(5,5,5);

void Awake(){
A.transform.position = Vector3.zero;
}
void Update(){
A.transform.position = Vector3.Lerp(A.transform.position,pos,Time.deltatime)
}

이런식으로 작성할 수 있다. 여기서 위치1(A의 위치)에서 위치2(pos)로 둘 사이의 거리*시간으로 이동한다고 함.

FadeCurve

쓰는방법 : 스크립트에 AnimationCurve 선언주면 인스펙터창에서 쓸수 있음
값을 선형적으로 바꿔주는듯함
Curve.Evaluate()를 통해 사용하는 듯 하며, 안의 값은 0~1사이의 숫자가 들어간다. 이 fadecurve라는 기능 자체가

이런 커브를 만들어주는 기능이라서 사용할 때 들어가는 숫자가 저 그래프의 해당하는 값을 의미하는듯하다. Vector3.Lerp와 함께 사용하는 예시를 팀원분이 들어주셔서 작성해보았음.
(출처 : 영어유튜브임)

List<> & 자료형[]

출처 : Array vs List
이해한 바로 간단히 정리하면 list는 크기를 늘리고 줄이는게 자유롭지만
array는 처음 지정된 크기를 변경하는게 불가능하다는게 제일 큰 특징인듯하다.
list에 데이터를 추가,삭제하기위해서는 list.Add / list.Remove를 사용한다.

Any

맨 위의 코드에서 rtans.Any(x => x== random))이라는 구문이 있다.
Linq로 rtans라는 List의 전체 값중에 하나를 x라 하고, 그 x가 random이랑 같을 때 반복문을 다시 돌린다.

삼항연산자

코드를 작성할 때 단순 else if 조건을 처리할 때 줄을 더 써야하나 고민할 때 사용할 수 있는 연산자로 다양한 사용 방법이 있지만 지금은 하나만 짚고 넘어가자.

삼항연산자 기본 처리방법

object tmp = {조건} ? {참(true)일 경우 return값} : {거짓(false)일 경우 return값}

기존에 작성한다면

if (tmp == true)
return a;
else if (tmp ==false)
return b;

이런 느낌으로 4줄짜리 코드가 되겠지만 삼항연산자를 사용하면 간단하게 한줄짜리 코드로 만들 수 있다.
하지만 조건이 복잡한 깊은 코드에 사용하는 것은 지양하도록 하자.
출처 : 삼항연산자 자세항 내용

$ 문자열 보간 & String.format

$문자열 보간 (문자열 안에서 변수 사용하기)

$를 사용하면 문자열 안에 직접 변수 이름을 집어 넣어 사용할 수 있다.
"문자열"의 " 앞에 $표기만 해주면 중괄호{} 안에 변수를 입력할 수 있다.

int a = 10;  
int b = 20;  Console.WriteLine($"string example1 : {a} + {b} = {a+b}");
//string example1 : 10 + 20 = 30 출력

주의점으로 Tostring()처럼 소숫점 자릿수를 설정하지는 못하는 듯하다.
출처 : 개발자 지망생:티스토리

String.format(숫자 > 문자열 자릿수 설정하기)

이름처럼 정해진 형식의 문자열로 변형시켜주는데 일반적으로 사용하는 모습을 보면 int나 float값을 string으로 변형시키는데 사용하는 것 같다.
예시로 나온 것들이 자리수 설정, 반올림 설정, 지수표현하기 등등 정해진 규칙은 있지만 자유롭게 원하는 형식으로 표현이 가능하다고 한다.
오늘도 리뷰

TIL 특강

TIL을 작성하는 이유

  1. 본인이 기억하기 위해 + 나중에 참고하기 위해
  2. 현실 - 면접관의 입장에서 기대-괜찮을지도? 를 위해 TIL 작성
    TIL = 신입이 가장 빠르게 경험과 실력을 증명하는 방법 (+성실성)

작성법

작성법에는 완벽X 정답X

초기 - 강의 내용 정리하기

  1. 오늘배운것 / 2. 개념정리 / 3. 해당개념이 필요한이유 / 4. 사용예시

개발단계정리

1.설치 / 2.초기세팅 / 3. OO만들기(코드작성)

일기

1.있었던 일을 정리한다.
2,발생했던 일에 대한 느낀점을 작성한다.
3.문제가 있었다면 개선방안을 정리한다.

문제해결과정(작성 권장)

문제해결과정(권장)
1.발생한 문제(에러 및 버그)가 무엇인지 작성
2.오류가 발생했던 코드를 작성한다
3.위 코드에서 문제점을 분석하고 가설을 세운다.
4.문제의 원인이 무엇이고 어떻게 해결했는지 작성
5.문제해결 과정 중 느낀 점, 개선방안이 있으면 작성한다.

post-custom-banner

0개의 댓글