오늘로 2일차...
벌써 조원들과 어색함이 많이 사라졌다! 굿!
어제는 기능구현을 각자 1개씩 맡아서 했지만, 오늘은 하나를 다같이 해보는 방식으로 했다.
먼저 각자 기능 구현을 하고, 서로의 방식을 화면공유를 통해 자신의 코드를 알려주고
구현을 못 하신 분들은 발표자들의 코드를 보며 배워가는
모르는 부분은 채워주고 아는 부분은 확고히 다져나가며 좀 더 좋은 방향을 제시하는 일석 삼조의 협업!
물론 시간은 많이 걸리긴 한다...ㅎ;;
그래서 오늘의 기능구현 목록
1. 카드 오브젝트 개수 늘리기 (사진 바리에이션 늘리기)
2. 현재 스테이지(또는 난도)에 따라 카드 배열 증가시켜보기
이렇게 두 가지를 한 번에 하는걸로 했다.
카드 사진을 기왕 늘리는겸 난이도 모드도 추가하는것이 맞다고 판단했다.
가보자!
첫 번째로 구현 할 것은 '사진 바리에이션 늘리기'
이 부분은 스프라이트 추가에 따른 고정적이지 않고 랜덤하게 사진이 나오도록 하는것이다.
18가지의 사진으로 마구마구 섞어보자!
물론 메인 기능 중 하나인 사진의 주인을 알아야 한다는 사실은 변함없다.
자 그럼 어떻게 요리해볼까?
일단 단순하게 시작해서 Sprites 배열에 미리 Sprite 들을 할당 해두자
다만 중요한것이 어제의 일지에서 Card 클래스에 들어있던 sprites 배열 변수를 GameManager에 옮겼다는것이다.
이유는 다음과 같다.
1. 대부분의 기능구현이 GameManager에서 이루어질 예정
2. 그 기능 가운데 Sprites 배열의 크기를 알아야 하는 코드가 있다.
3. Card에 Sprites 배열 변수가 남아있으면 크기를 알 수 없다!
다음은 GameManager의 수정된 코드들이다.
public Sprite[] sprites;
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();
}
앞서 말했듯 GameManager에 sprites 배열 변수를 옮겨왔다.
그리고 InitGame 함수에 카드를 생성하는 코드를 SpawnCards 함수에 옮겼고 이제 16장의 카드에
18개의 스프라이트 중 8개가 랜덤하게 할당 되는것을 보자
일단 rtans 배열이 List로 바뀐것은 팀장님의 코드로 병합하였기 때문이므로 자세한 설명은 넘어간다.
먼저 for 문의 조건 중 cardCnt 는 난이도 기능에 필요하기에 먼저 만들어둔 int 전역변수이다.
즉, while문을 잠깐 생략하고 현재 16 / 2 = 8 : 전체카드수 / 절반 = 필요카드(스프라이트) 수 만큼 루프하며 rtans List에 int 값을 할당 할 것이다.
여기서 중요한것은
필드에 생성될 스프라이트는 짝을 제외하여 8가지 이지만,
List 에는 우리가 가진 18가지 중 랜덤하게 골라져 할당돼야 한다는 것이다.
그럼 while문 내부로 가보자 조건이 충족하면 break 를 해줄것이므로 조건은 true로 무한루프를 돌도록 하자.
int random = -1;
while (true)
{
random = UnityEngine.Random.Range(0, sprites.Length);
if (rtans.Any(x => x == random))
continue;
break;
}
코드를 보면
int 값 random 지역변수를 0 ~ 17 중 랜덤한 수를 할당 후
if 문을 통해 rtans List에 random과 같은 값이 있는지 체크한다.
만약 같은 값이 있다면?
다시!
겹치는 수가 안 나올 때 까지 무한반복하고 if 문에서 걸리지 않으면 중복되지 않는 수가 random에 할당 된 것이므로
while문을 종료한다.
그렇게 while문을 빠져나와 rtans에 random을 두번 할당 해 짝을 맞춰주도록 하자.
디버그를 통해 확인 결과, for문을 완전히 빠져나왔을 때
rtans List에 16개의 랜덤한 수가 짝을 지어 잘 할당 된 것을 알 수 있다.
나머지는 rtans List를 섞고 카드를 생성하는 부분이므로 생략한다.
이렇게 18가지의 Sprite 중 랜덤하게 가져올 수 있도록 바꿔주었다.
Card에서도 추가된 case에 따라 사람을 할당 할 수 있게 코드를 수정하자
public void Setup(int index)
{
spriteRenderer = transform.Find("front").GetComponent<SpriteRenderer>();
spriteRenderer.sprite = GameManager.Instance.sprites[index];
switch (index)
{
case 0:
case 1:
case 2:
case 3:
WhosCard = WhosCard.Seungjun;
break;
case 4:
case 5:
case 6:
case 7:
WhosCard = WhosCard.Geon_o;
break;
case 8:
case 9:
case 10:
case 11:
WhosCard = WhosCard.Geonhyeong;
break;
case 12:
case 13:
case 14:
WhosCard = WhosCard.Jiyoon;
break;
case 15:
case 16:
case 17:
WhosCard = WhosCard.Ingyu;
break;
}
}
코드가... 솔직히 영 마음에 안 든다. 뭔가 좀 더 좋은 방법이 있을텐데!!!
하지만 그 방법을 생각 해내다간 모두에게 민폐가 될 것 같으니 이정도로 만족하자.
대신 index에 맞게 Sprites 배열에 카드 주인 순서대로 잘 할당해주자.
자 게임을 실행해보자.
어제의 일지에 등장하지 않았던 뉴페이스 카드들이 잘 나온다!
자 그럼 다음으로 난이도를 구현해주자!
난이도는
쉬움, 보통, 어려움
3가지의 난이도로 기획했다.
그리고 복잡성을 최대한 줄이고자 보드의 크기는
2 x 2 / 4 x 4 / 6 x 6
위와같이 고정한다.
그럼 일단 StartScene 에서 게임시작을 난이도 선택 버튼으로 바꿔주도록 하자
각각의 버튼을 누르면 해당하는 난이도로 씬이 전환된다.
씬은 MainScene 하나 지만 난이도 별 보드의 크기가 다르다.
버튼 컴포넌트를 확인해보자
기존의 StartBtn 스크립트의 코드는 버튼 클릭시 바로 MainScene 으로 넘어가는 코드가 작성되어 있었기 때문에
Inspector 상 Onclick이 저렇게 보인다.
하지만 우린 난이도에 따라 다르게 할 것이니 이렇게 바꿔주자
using UnityEngine;
using UnityEngine.SceneManagement;
public class startBtn : MonoBehaviour
{
public void gameStart(int diff)
{
Difficulty.Difficulty = diff;
SceneManager.LoadScene("MainScene");
}
}
GameStart 함수에 int 매개변수를 받아 Difficulty 클래스의 int 변수인 Difficulty에 할당 해주자!
※ 근데?
Q. Difficulty 클래스는 뭔데?
A. Difficulty 클래스는 아주아주 심플하게 static 으로 선언되어있는 int 변수 하나만을 가지고 있는 클래스이다!
Q. 그래서 int 는 왜 가져오는건데?
A. public static 으로 선언 된 변수는 아무 클래스에서 마구마구 사용이 가능하다! 즉 위 코드와 같이 클래스이름.변수명 을 바로 접근이 가능 하다는 뜻! (그렇다고 진짜 마구마구 사용하면 안 된다.)
또한, 유일한 정적 변수이기 때문에 유니티 씬이 넘어가도 변하지 않는다! 우린 이것으로 난이도를 조절할 것!
Q. ???
A. ... 예를들어보자!
A씬과 B씬이 있고
a클래스 내부에 public int diff
b클래스 내부에 public static int diff
가 있다고 가정하자.
A씬에서 a클래스의 전역변수인 diff를 사용 한다고 가정하자.
일단 생성을 해야 할 것이다. 이 때 두 가지 방법이 있다.
1. a 클래스에 Monobehavior 를 상속해 오브젝트의 컴포넌트로 붙여 사용하는 방법
2. c라는 가상의 Monobehavior를 상속한 클래스에서 new 를 이용해 새로 만들어 사용하는 방법
아무튼 둘 다 A 씬에서만 작동한다는 것이다. B 씬으로 넘어가면 쓰지 못하게 된다.
(물론 DontDestroy 등을 이용하면 되긴 하지만 여기선 쓰지 않겠다.)
하지만 b 클래스를 사용한다면?
이 프로젝트 세상속 유일한 diff를 A 씬에서 변경을 했다면 B 씬으로 넘어가더라도 그대로 남아있다!
그것이 정적 변수이니까!
그렇기에 정적변수 Difficulty 에 매개변수 값을 일단 할당 한 뒤 MainScene으로 넘어가자!
자~ 그럼 MainScene 으로 넘어왔다 그럼이제 어떻게 되는걸까?
바로 코드를 확인해보자
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
public enum Diff
{
Easy, Normal, Hard
}
public class GameManager : MonoBehaviour
{
[SerializeField] Diff diff;
public Sprite[] sprites;
void Start()
{
SetDifficult();
InitGame();
Debug.Log(Difficulty.Difficulty);
}
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;
}
}
}
역시나 바뀐 코드만 확인 해보도록 하자.
GameManager 클래스 상단에 enum 으로 난이도를 조절하도록 한다.
전역변수로 diff를 선언 후 SetDifficult 함수를 통해
diff = (Diff)Difficulty.Difficulty;
위 코드로 Difficulty 에 할당된 정수를 Diff로 캐스팅 하자.
enum에 적어놓은 Easy에 마우스 커서를 가져다 대어보면 0 이 할당되어 있는것을 볼 수 있다.
따로 설정하지 않는 이상 0 부터 차례대로 정수값이 할당된다.
이를 이용해 switch 문에서 diff 에 enum 으로 캐스팅한 int 를 할당하고
switch 문에서 해당하는 타입에 따라 난이도를 설정하자!
int cardCnt 에는 난이도에 따라 2 x 2 = 4 / 4x 4 = 16 / 6 x 6 = 36 개의 카드가 생성되니 이에 맞게 할당하자!
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++)
{
card newCard = Instantiate(card).GetComponent<card>();
newCard.transform.parent = cards.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;
newCard.transform.position = new Vector3(posX, posY, 0);
}
}
}
다시 SpawnCards 함수로 돌아와 첫 for 문 까지는 생략하고 다음을 집중적으로 보도록 하자.
private void SpawnCards()
{
int row = (int)Mathf.Sqrt(cardCnt);
for (int y = 0; y < row; y++)
{
for (int x = 0; x < row; x++)
{
card newCard = Instantiate(card).GetComponent<card>();
newCard.transform.parent = cards.transform;
newCard.Setup(rtans[y * row + x]);
float offset = 1.5f;
float startAnchor = offset * (row / 2) - 0.75f;
float posX = -startAnchor + offset * x;
float posY = startAnchor - offset * y;
newCard.transform.position = new Vector3(posX, posY, 0);
}
}
}
int row 에는 현재 난이도의 행의 개수를 담아두자. Mathf.Sqrt 는 매개변수의 제곱근을 반환해주는 함수이다.
4 x 4 = 16 이니 16 의 제곱근인 4는 한 행에 해당한다.
그리고 이중 for 문을 돌자!
왼쪽부터 오른쪽으로 한 줄 씩 순차적으로 생성할 것이다. 여기서 offset과 startAnchor의 값은 그저 배치하고 싶은 오브젝트의 크기와 간격을 기획에 따라 맞게 설정하면 되는데다 사실상 수학적 영역이기 때문에 이부분에 대한 설명은 생략하자.
그래서 결과물은?!
Good!
'스파르타 내배캠' 카테고리의 다른 글
스파르타 내배캠 Unity 3기 6일차 (1) | 2023.12.29 |
---|---|
스파르타 내배캠 Unity 3기 5일차 (0) | 2023.12.28 |
스파르타 내배캠 Unity 3기 4일차 (1) | 2023.12.27 |
스파르타 내배캠 Unity 3기 3일차 (2) | 2023.12.26 |
스파르타 내배캠 Unity 3기 1일차 (1) | 2023.12.21 |