[스파르타코딩클럽] 게임개발 종합반 - 4주차
[수업 목표]
- 보드 게임을 만들어보기
- 카드 뒤집기 게임을 만들면서, 총복습하기
- 게임에 필요한 "로직"을 경험하기
[목차]
01. 오늘 배울 것
1) 4주차 수업의 목표와 범위
여태까지 배웠던 것들로 만들거예요!
카드 뒤집기 게임을 만들면서 보드게임 만들기의 기초를 배워봅니다.
→ 보드게임은 우리가 만들었던 게임과 살~짝 다르게, "로직"이 중요하답니다.
→ 지난 주차에 했던 것들이 새록새록 기억 날 거예요!
2) 오늘 만들 것 :
르탄이 카드 뒤집기 게임
같은 모양의 르탄이를 뒤집는 게임! 해본 적 있죠? 😎
3) 만들 순서
- 기본 씬 구성하기 : 배경, 타이머, 리소스 받아두기
- 시간 보내기
- 카드 깔기
- 카드 뒤집기 애니메이션 만들기
- 같은 카드을 뒤집었을 때 없애기
[기본 씬 구성하기]
1) 기본 세팅하기
windows → 2x3 layout 클릭! free aspect → phone 클릭!
→
rgb ⇒ 90, 90, 225
로 맞출게요! (MainCamera)
2) 타이머 만들어두기
UI → Text 클릭 → timeTxt 로 만들어두기
→
font size: 70 , y: 400, width: 200, height: 200, Color: (255, 255, 255, 255)
02. 카드 만들기 - 한 장
1) 르탄이 이미지 받아두기
[코드스니펫] 르탄이 이미지 모음
https://s3.ap-northeast-2.amazonaws.com/materials.spartacodingclub.kr/game_new/week04/rtan.zip
Images 폴더 아래에 rtan 이미지 풀어두기
2) 카드 한 장만 만들어두기
Cards (Create Empty!) 아래에 → card (Create Empty) 한 장만 만들어둘게요
→ 앞면(front)은 르탄이 이미지가 들어가고, 뒷면(back)은
?
물음표가 들어갑니다.→ card 아래에 front/back 로 sprite를 만들어둘게요. 아래처럼!
front
스프라이트에 rtan0 이미지를 끌어다 놓습니다.→ 앗, 너무 크네요! 이미지를 클릭해서
pixels per unit
을 450으로 맞춥니다.→ 다른 르탄이들도 모두 450으로 맞춰주세요! (shift 눌러서 한번에 잡아 바꾸기)
우선
front
는 꺼두겠습니다.back 아래에 Canvas를 만들어 Text로
?
표시를 넣어주세요Canvas UI를 만듭니다.
Render-Mode를
World Space
로 변경 해줍니다.Transform Reset 버튼을 통해 Rect Transform을 초기화합니다.
Order in layer를
1
로 바꾸어 줍니다.Text에서 Font Size는 50으로 설정 합니다.
Scale 부분을 (0.01 , 0.01)로 맞추어 줍니다.
마지막으로 Card의 Scale을 1.3으로 바꾸기
03. 시간 가게 하기
1) gameManager 세팅하기 (쉬운 것부터!)
gameManager 만들기
시간이 가게 하기
public Text timeTxt; float time = 0.0f; void Update() { time += Time.deltaTime; timeTxt.text = time.ToString("N2"); }
Play 해서 확인해보기!
04. 카드(1)_배치하기
1) 배치 전략
카드를 16장 만들어서 직접 배치하는 방법은 → 100장이면 너무 힘들잖아요!
지금 카드 사이즈가
x:1.3, y:1.3
이니까,1.4
만큼씩 띄워서 붙여주려고요!
2) 자동으로 카드 생성하기
card를 프리팹으로 만들기
→ 기존 것은 과감히 삭제하기!
카드 생성하기 전에
반복문
을 구경해보기 (튜터만 할게요!)void Start() { for (int i = 0; i < 16; i++) { Debug.Log(i); } }
card를 새로 만들어서 cards 아래에 붙이기
→ 실행해서 확인해볼까요?
public GameObject card; void Start() { for (int i = 0; i < 16; i++) { GameObject newCard = Instantiate(card); newCard.transform.parent = GameObject.Find("cards").transform; } }
3) 카드 위치 잡아주기
전략을 생각하기
[예를 들어
1번째
라고 하면]→ x : 1을 4로 나눈 몫 = 0
→ y : 1을 4로 나눈 나머지 = 1
⇒ (0,1) 위치 ⇒ (1.4씩 곱해주면) ⇒
(0, 1.4)
위치[예를 들어 7번째 라고 하면]
→ x : 7을 4로 나눈 몫 = 1
→ y : 7을 4로 나눈 나머지 = 3
⇒ (1,3) 위치 ⇒ (1.4씩 곱해주면) ⇒
(1.4, 4.2)
위치카드 위치 잡아주기
void Start() { for (int i = 0; i < 16; i++) { GameObject newCard = Instantiate(card); newCard.transform.parent = GameObject.Find("cards").transform; float x = (i / 4) * 1.4f; float y = (i % 4) * 1.4f; newCard.transform.position = new Vector3(x, y, 0); } }
카드를 전체적으로 옮겨주기
→ x, y를 적당히 빼줘서 전체를 가운데에 위치하게 하기
→ 여기서는
x: -2.1f, y: -3.0f
void Start() { for (int i = 0; i < 16; i++) { GameObject newCard = Instantiate(card); newCard.transform.parent = GameObject.Find("cards").transform; float x = (i / 4) * 1.4f - 2.1f; float y = (i % 4) * 1.4f - 3.0f; newCard.transform.position = new Vector3(x, y, 0); } }
완성된 배치 구경하기
05. 카드(2)_르탄이 넣기, 애니메이션
[르탄이 넣기]
1) 랜덤으로 섞기 전략
- 일단 [0, 0, 1, 1, 2, 2, ..., 7, 7]까지 쓰인 리스트를 만들고
- 이걸 섞어서 [2, 3, 4, 1, 2, 0, 1, ..., 7]로 만들고
- 카드가 만들어 질 때 하나씩 꺼내서 르탄이 이미지를 붙여주기!
2) 리스트를 랜덤으로 섞기
우선 리스트 만들고 출력하기
→ 카드가 만들어지면서
Debug.Log
해보면 되겠죠?void Start() { int[] rtans = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7 }; for (int i = 0; i < 16; i++) { GameObject newCard = Instantiate(card); newCard.transform.parent = GameObject.Find("cards").transform; float x = (i / 4) * 1.4f - 2.1f; float y = (i % 4) * 1.4f - 3.0f; newCard.transform.position = new Vector3(x, y, 0); Debug.Log(rtans[i]); } }
리스트를 섞기
→ 아래를 맨 위에 추가
using System.Linq;
[코드스니펫] 랜덤하게 섞기
rtans = rtans.OrderBy(item => Random.Range(-1.0f, 1.0f)).ToArray();
```csharp
void Start()
{
int[] rtans = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7 };
rtans = rtans.OrderBy(item => Random.Range(-1.0f, 1.0f)).ToArray();
for (int i = 0; i < 16; i++)
{
GameObject newCard = Instantiate(card);
newCard.transform.parent = GameObject.Find("cards").transform;
float x = (i / 4) * 1.4f - 2.1f;
float y = (i % 4) * 1.4f - 3.0f;
newCard.transform.position = new Vector3(x, y, 0);
Debug.Log(rtans[i]);
}
}
```
3. `Play` 해서 확인해보기
![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/7c6a4f13-1954-4a0f-8d85-7026bee3c74c/Untitled.png)
3) 르탄이 붙여주기
이미지를 꺼내오려면? → Resources 폴더에 옮겨두기
르탄이 붙이기
string rtanName = "rtan" + rtans[i].ToString();
→ 르탄이 이름(이미지 이름)을 만들어두기
newCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite =
→ 새 카드 아래에
front
를 찾아서, sprite를 변경Resources.Load<Sprite>(rtanName);
→
Resources
폴더에 있는 rtanName 이미지를 가져오자void Start() { int[] rtans = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7 }; rtans = rtans.OrderBy(item => Random.Range(-1.0f, 1.0f)).ToArray(); for (int i = 0; i < 16; i++) { GameObject newCard = Instantiate(card); newCard.transform.parent = GameObject.Find("cards").transform; float x = (i / 4) * 1.4f - 2.1f; float y = (i % 4) * 1.4f - 3.0f; newCard.transform.position = new Vector3(x, y, 0); string rtanName = "rtan" + rtans[i].ToString(); newCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite = Resources.Load<Sprite>(rtanName); } }
확인해보기
→ 프리팹에서 front를 켜고, back을 끈 다음 확인합니다.
→ 잘 나오네요! 😎
[카드 애니메이션]
1) 기본 애니메이션 만들기
card_idle
Animations 폴더 생성 후 card_idle 애니매이션 만들기
→ 프리팹 card를 열어서 붙여놓기, Loop Time 체크하는 것 잊지 말기!
카드를 꺼내놓기
→ 안보이면 만들기 어려우니까!
애니메이션 레코딩하기
→
0:20
에만 rotationz:3
을 만들어놓기
2) 뒤집기 애니메이션 만들기
card_flip
Animations 폴더 생성 후 card_flip 애니메이션 만들기
→ 프리팹을 열어 card에 붙여놓기
→ 뒤집기는
한번
이므로 loop time 체크할 필요 없음!애니메이션 만들기
→
0:10
부분만 Scale 을x:1.2, y:1.2
로 만들어두기→ 살짝 눌린 것처럼!
3) 애니메이션 조건 만들기
Animator 를 열고, transition 만들어 줍니다.
카드의 오른쪽 클릭하고 make transition 을 클릭합니다.
오는 것과 가는 것 두 개의 transition을 생성합니다.
각 transition에 대해서, has exit time 에 체크 해제하고, transition duration을 0으로 변경합니다.
파라미터 만들기
→ bool 형식(true / false)의
isOpen
transition에 파라미터 조건 붙이기
→
idle ⇒ flip
: bool이 true 가 되면 발동→
flip ⇒ idle
: bool이 false 가 되면 발동
06. 카드(3)_뒤집기, 매칭하기
[뒤집기]
1) 카드 클릭하면 뒤집어지기 (=쉬어가기 🙂)
card c# 스크립트에서 button 속성 붙이기
card.cs
만들어서, 클릭하면 front가 보이게 해줍니다.public void openCard() { transform.Find("front").gameObject.SetActive(true); transform.Find("back").gameObject.SetActive(false); }
b.온클릭 함수에 card를 붙여 넣은 후, card에, openCard button 함수를 붙여 줍니다.
![opencard.jpg](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/22262d1e-0413-4224-898c-55db44be50fc/opencard.jpg)
1. 카드에 animtion 적용하기
```csharp
public Animator anim;
public void openCard()
{
anim.SetBool("isOpen", true);
transform.Find("front").gameObject.SetActive(true);
transform.Find("back").gameObject.SetActive(false);
}
```
2. `Play` 해서 잘 뒤집어지는지 실행해보기
→ 잘 되네요!
![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e93f57c8-9ad7-4f2d-af7d-cbf43ef78a90/Untitled.png)
[카드 매칭하기]
1) 카드 매칭하기 - 전략
- 일치하면? ⇒ (1초 후에) 카드를 둘 다 없애주기
- 안 일치하면? ⇒ (1초 후에) 두 카드를 다시 뒤집어주기
2) 카드 매칭하기
우선, gameManager 싱글톤화
→ 이제 다른 곳에서 막 부를 것이니까!
public static gameManager I; void Awake() { I = this; }
gameManager에서 카드 이름을 저장해둘 수 있게 하기 + 매칭 로직 함수 만들어두기
public GameObject firstCard; public GameObject secondCard; public void isMatched() { Debug.Log("판단하자"); }
card.cs
에서 openCard하면 firstCard 또는 secondCard에 나를 넣기public void openCard() { anim.SetBool("isOpen", true); transform.Find("front").gameObject.SetActive(true); transform.Find("back").gameObject.SetActive(false); if (gameManager.I.firstCard == null) { gameManager.I.firstCard = gameObject; } else { gameManager.I.secondCard = gameObject; gameManager.I.isMatched(); } }
gameManager.cs
에서isMatched()
만들기→
front
를 찾아서 카드 이미지 이름을 받아오기→ 다 끝났으면 다시 비워주기
public void isMatched() { string firstCardImage = firstCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; string secondCardImage = secondCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; if (firstCardImage == secondCardImage) { Debug.Log("같다!"); } else { Debug.Log("같지 않다!"); } firstCard = null; secondCard = null; }
card 행동 준비하기
→
card.cs
안에 없애기, 뒤집어주기 함수 만들어두기→ 같으면 → 1초 후에 둘 다 없애기 / 다르면 → 1초 후에 둘 다 뒤집어주기
→ 1초 후에 실행해야하니까, invoke를 따로 만들어줘야 하겠죠?
public void destroyCard() { Invoke("destroyCardInvoke", 1.0f); } void destroyCardInvoke() { Destroy(gameObject); } public void closeCard() { Invoke("closeCardInvoke", 1.0f); } void closeCardInvoke() { anim.SetBool("isOpen", false); transform.Find("back").gameObject.SetActive(true); transform.Find("front").gameObject.SetActive(false); }
gameManager.cs
에서 같을 때 / 같지 않을 때 적절한 함수 불러주기firstCard.GetComponent<card>().destroyCard()
→ firstCard에 붙어있는 card.cs 에서 destoryCard 를 불러라!
public void checkMatched() { string firstCardImage = firstCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; string secondCardImage = secondCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; if (firstCardImage == secondCardImage) { firstCard.GetComponent<card>().destroyCard(); secondCard.GetComponent<card>().destroyCard(); } else { firstCard.GetComponent<card>().closeCard(); secondCard.GetComponent<card>().closeCard(); } firstCard = null; secondCard = null; }
확인해보기 ⇒ 잘 되네요! 😎
07. 게임 끝내기
1) 카드가 모두 없어지면 게임 끝내기
끝! 텍스트를 미리 만들어두기
fontsize 20, timeTxt 를 [윈도우]
ctrl+d
[맥] command + D 해서 만들면 편하겠죠!width: 300, height: 300, posY: 0
으로 설정 해주세요.color : (220, 255, 0, 255)
도 설정해줍니다.안보이게 세팅 하여 감춰두세요!
gameManager.cs
에서, 매칭 됐을 때 남은 카드를 체크하기→ cards를 찾아서, 아래에 몇 개의 자식이 있는지를 판단하면 끝!
→ 실행해보기!
public void checkMatched() { string firstCardImage = firstCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; string secondCardImage = secondCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; if (firstCardImage == secondCardImage) { firstCard.GetComponent<card>().destroyCard(); secondCard.GetComponent<card>().destroyCard(); int cardsLeft = GameObject.Find("cards").transform.childCount; Debug.Log(cardsLeft); } else { firstCard.GetComponent<card>().closeCard(); secondCard.GetComponent<card>().closeCard(); } firstCard = null; secondCard = null; }
실행해보면, 지금 맞춘 두 장을 포함해서 나오는 것을 알 수 있어요
→ 즉,
2
가 나오면 마지막이라는 뜻!게임 끝내기!
public GameObject endTxt; public void isMatched() { string firstCardImage = firstCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; string secondCardImage = secondCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; if (firstCardImage == secondCardImage) { firstCard.GetComponent<card>().destroyCard(); secondCard.GetComponent<card>().destroyCard(); int cardsLeft = GameObject.Find("cards").transform.childCount; if (cardsLeft == 2) { endTxt.SetActive(true); Time.timeScale = 0.0f; } } else { firstCard.GetComponent<card>().closeCard(); secondCard.GetComponent<card>().closeCard(); } firstCard = null; secondCard = null; }
끝
버튼에 다시시작하기 버튼 붙이기!→ (1) 버튼 컴포넌트 추가하기
→ (2)
endTxt.cs
만들고, endTxt에 붙이기using UnityEngine.SceneManagement; public void retryGame() { SceneManager.LoadScene("MainScene"); }
→ (3)
gameManager.cs
start에서 TimeScale을 다시 되돌려놓는 것도 잊지말기Time.timeScale = 1.0f;
확인하기 ⇒ 잘 되네요!
2) 미세 조정하기
이렇게, 다 만들고 변수들을 조정해서 마지막으로 게임을 손 본답니다!
card.cs
에서 없어지거나 / 다시 뒤집히는 시간을0.5f
로 바꿔둘게요!public void destroyCard() { Invoke("destroyCardInvoke", 0.5f); } void destroyCardInvoke() { Destroy(gameObject); } public void closeCard() { Invoke("closeCardInvoke", 0.5f); } void closeCardInvoke() { anim.SetBool("isOpen", false); transform.Find("back").gameObject.SetActive(true); transform.Find("front").gameObject.SetActive(false); }
08. 숙제 - 30초가 지나면 게임 끝내기
이렇게 되면 완성!
힌트요정 - 👻
[수정해야할 부분]
gameManager.cs
의Update()
부분만 수정하면 된답니다!→ 어떤 조건이 되면 아래 코드가 실행되면 되겠죠!
endTxt.SetActive(true); Time.timeScale = 0.0f;
HW. 4주차 숙제 해설
gameManager.cs
부분using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System.Linq; public class gameManager : MonoBehaviour { public Text timeTxt; float time = 0.0f; public GameObject endTxt; public GameObject card; public static gameManager I; public GameObject firstCard; public GameObject secondCard; void Awake() { I = this; } // Start is called before the first frame update void Start() { Time.timeScale = 1.0f; int[] rtans = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7 }; rtans = rtans.OrderBy(item => Random.Range(-1.0f, 1.0f)).ToArray(); for (int i = 0; i < 16; i++) { GameObject newCard = Instantiate(card); newCard.transform.parent = GameObject.Find("cards").transform; float x = (i / 4) * 1.4f - 2.1f; float y = (i % 4) * 1.4f - 3.0f; newCard.transform.position = new Vector3(x, y, 0); string rtanName = "rtan" + rtans[i].ToString(); newCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite = Resources.Load<Sprite>(rtanName); } } // Update is called once per frame void Update() { time += Time.deltaTime; timeTxt.text = time.ToString("N2"); if (time > 30.0f) { endTxt.SetActive(true); Time.timeScale = 0.0f; } } public void isMatched() { string firstCardImage = firstCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; string secondCardImage = secondCard.transform.Find("front").GetComponent<SpriteRenderer>().sprite.name; if (firstCardImage == secondCardImage) { firstCard.GetComponent<card>().destroyCard(); secondCard.GetComponent<card>().destroyCard(); int cardsLeft = GameObject.Find("cards").transform.childCount; if (cardsLeft == 2) { endTxt.SetActive(true); Time.timeScale = 0.0f; } } else { firstCard.GetComponent<card>().closeCard(); secondCard.GetComponent<card>().closeCard(); } firstCard = null; secondCard = null; } }
Copyright ⓒ TeamSparta All rights reserved.