👑 기본 룰 :
1. 제한 시간 안에 떨어지는 사과 먹기
2. 사과는 4개의 타입이 있다.
👉 타입 1, 2, 3: 크기 별로 나뉘며 클 수록 점수가 높다.
😱 타입 4: 병든 사과로 먹을 시 점수가 차감된다.
- 사과
👉 랜덤한 위치에서 랜덤한 종류의 사과 떨어트리기
👉 캐릭터에 부딪히면 점수 업데이트 및 사라지기
👉 땅에 부딪히면 사라지기- 사과를 받아먹을 캐릭터
- 캐릭터가 돌아다닐 땅
- 제한 시간
- 제한 시간 초과 시 종료 화면
- 재시작 기능
- 점수
- 땅을 만들기
- 캐릭터 만들기
- 사과 만들기
- 사과의 충돌 효과 구현하기
- 사과 랜덤한 위치, 랜덤한 크기로 생성하기
- 사과를 계속 생성하기
- 점수 구현하기
- 제한시간 구현하기
- 게임 종료 및 재시작 구현하기
▪️ Window → Layouts → 2 by 3
▪️ Project 메뉴 → One Column Layout
▪️ Game 메뉴 → Free Aspect를 760 * 1280짜리로 변경
▪️ SampleScene → MainScene으로 이름 변경▪️ MainScene 밑에 Square 생성 → 이름 background로 바꾸고 사이즈 6 * 10, 색상 #99CCFF
- MainScene에 Square 생성, 이름 변경하기
- 생성된 Square의 Inspector에서 Sprite Renderer → Sprite에 캐릭터 에셋을 드래그 앤 드롭
- Order in Layer : 1로 설정
- 캐릭터가 지면에 닿아 보이게끔 PosY : -1.5로 조절
캐릭터의 Inspector에 Animator가 적용된 모습 ▼
✨ 생각해보기
캐릭터를 움직인다? 👉 캐릭터의 좌표값을 조절한다.
캐릭터의 좌표값? transform.position!
스크립트 열어서 코드 작성
void Update() { // 캐릭터의 X좌표값을 0.5만큼 계속 더해준다. transform.position += new vector3(0.05f, 0, 0); }
실행 화면 ▼
코드 수정하기
- 위 코드의 0.05f를 변수로 선언하기
float distance = 0.05f; void Update() { transform.position += new Vector3(distance, 0, 0); }
2. 캐릭터의 움직임이 너무 빠르다!
👉 이는 Update()가 프레임 당 호출되기 때문이다. → 게임의 시간을 기준으로 호출하기?
✨ FixedUpdate()를 사용하면 게임 시간을 기준으로 호출되어 속도를 줄일 수 있다.void FixedUpdate() { transform.position += new vector3(distance, 0, 0); }
실행 화면 ▼
현재는 펭귄이 게임의 벽을 뚫고 나가버리는 것을 확인할 수 있다!
👉 벽에 부딪히면 방향 전환을 해 반대 방향으로 가게 하자!
🤔 생각해보기
1. 펭귄의 X좌표가 background의 범위를 벗어나면 distance를 -0.05로 전환하기
👉 대략 -2.2, 2.2정도면 벽에 닿는 듯 하다.if(transform.position.x > 2.2f) distance = -0.05f; if(transform.position.x < -2.2f) distance = 0.05f;
- 펭귄의 스프라이트 뒤집기? transform.localScale()!
penguin.cs로 돌아와 코드 수정!
void Update() { if(transform.position.x > 2.2f) { distance = -0.05f; } if(transform.position.x < -2.2f) { distance = 0.05f; } }
실행 화면 ▼
스프라이트 뒤집기?
penguin의 Inspector의 Transform에서 Scale의 X를 -1로 변경해보면 좌우가 반전된다.
👉 즉, Transform의 Scale의 X값을 -1과 1로 변경하면 좌우 반전을 할 수 있다!
이를 코드로 구현하면 아래와 같다.
'유니티 2D 좌우반전하기' 구글링 :
transform.localScale = new vector3(-1, 1, 1)
void Update() { if(transform.position.x > 2.2f) { distance = -0.05f; transform.localScale = new Vector3(-1, 1, 1); } if(transform.position.x < -2.2f) { distance = 0.05f; transform.localScale = new Vector3(1, 1, 1); } }
실행 화면 ▼
float direction = 1.0f; void Update() { if(transform.position.x > 2.2f) { distance = -0.05f; direction = -1.0f; } if(transform.position.x < -2.2f) { distance = 0.05f; direction = 1.0f; } transform.localScale = new vector3(direction, 1, 1); }
🍎 떨어지는 사과를 먹기 위해 캐릭터의 방향을 임의로 전환할 필요가 있다!
🖱️마우스 클릭? getMouseButtonDown!
❗ 구글링을 통해 GetMouseButtonDown을 통해 값이 true면 방향전환을 시켜주면 됨을 알 수 있었다!
if (Input.GetMouseButtonDown(0)) { distance *= -1; direction *= -1; }
▪️ 코드에 적용 시키기
float distance = 0.05f; float direction = 1.0f; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { // 마우스 입력을 받으면 if (Input.GetMouseButtonDown(0)) { distance *= -1; direction *= -1; } if (transform.position.x > 2.2f) { distance = -0.05f; direction = -1.0f; } if (transform.position.x < -2.2f) { distance = 0.05f; direction = 1.0f; } transform.localScale = new Vector3(direction, 1, 1); } private void FixedUpdate() { transform.position += new Vector3(distance, 0, 0); }
실행 화면 ▼
🤔 생각해보기
👉 사과가 떨어지려면? 중력 효과!
✨ 중력 효과? Rigidbody 2D를 사용하여 구현!
🤔 생각해보기
👉 사과를 충돌시킬려면? 충돌 효과
✨ 충돌 효과? Collider!
Collider를 적용하기 위해선? : Rigidbody, Collider를 가진 물체가 다른 Collider를 가진 물체와 부딪히면 발생한다.
👉 지금은 사과와 펭귄, 땅이 Collider를 가져야 한다!
❗Collider 적용 시 적용 범위와 위치를 확인하여 조정하도록 하자!
🤔 생각해보기
👉 사과가 무언가와 충돌했을 시? onCollisionEnter2D()!
👉 사과가 부딪히는 대상은 어떻게 식별하지? 태그 달기!
- apple.cs를 만들어 apple과 연결.
- apple.cs를 열어 onCollisionEnter2D()를 작성
private void OnCollisionEnter2D(Collision2D collision) { // 부딪히는 게임 오브젝트(collision.gameObject)의 태그가 ground일 경우 제거 if (collision.gameObject.tag == "ground") Destroy(gameObject); }
🤔 생각해보기
랜덤하게 생성? apple.cs의 start()에서 랜덤한 X, Y좌표 및 사이즈를 가지게 만들기!
랜덤하게? Random.Range()
void Start() { float x = Random.Range(-2.5f, 2.5f); float y = Random.Range(2.5f, 4.5f); transform.position = new Vector3(x, y, 0); }
나타야할 사과의 4 종류 :
사과는 3개의 크기로 나뉘어지며, 크기가 클 수록 점수가 높다(4번 사과 제외).1번 사과 : 제일 작은 사과, 사이즈 0.2 0.2, RGB : 186, 186, 186, 점수 : 1점
2번 사과 : 중간 사이즈 사과, 사이즈 0.3 0.3, RGB : 210, 210, 210, 점수 : 2점
3번 사과 : 제일 큰 사과, 사이즈 0.4 0.4, RGB : 255, 255, 255, 점수 : 3점
4번 사과 : 썩은 사과, 사이즈 0.3 0.3, RGB : 0, 0, 0, 점수 : -5점
🤔 생각해보기
👉 랜덤한 크기? Random.Range()를 통해 랜덤한 크기값 생성!
✨ 생성된 크기에 따라 switch문 제어
변수 size, score, type을 선언한다(apple.cs).
Start()에 type을 Random.Range()를 통해 랜덤한 값을 집어넣는다.
int type; int score; float size; void Start() { type = Random.Range(1, 5); // 5는 범위에 포함되지 않는다. switch(type) { case 1: size = 0.2f; score = 1; case 2: size = 0.3f; score = 2; case 3: size = 0.4f; score = 3; case 4: size = 0.3f; score = -5; } transform.localScale = new Vector3(size, size, 0); }
getComponent<SpriteRender>()
를 이용해 색상과 스프라이트를 교체한다.
코드 ▼(getComponent에 관한 함수를 velog에 작성하면 velog의 상태가 이상해져 캡쳐로 대체...)
✨ 사과를 계속 생성하려면? gameManager 오브젝트를 만들고 → 사과를 Prefabs로 틀을 만들고 → Instantiate 복제를 한다!
gameManager? 게임 전체를 조율하는 오브젝트.
Prefab? 오브젝트가 붕어빵이라고 한다면, prefab은 붕어빵틀! prefab을 통해 오브젝트를 복제할 수 있다.
gameManager.cs에 public gameObject apple;
을 선언한다.
유니티로 돌아와 gameManager의 Inspector에 apple을 드래그 앤 드롭한다.
InvokeRepeating()함수를 통해 반복 실행하도록 코드 작성!
✨ InvokeRepeating(string methodName, float time, float repeatRate);
👉 methodName이란 함수를 time부터 repeatRate 마다 반복 실행하는 코드이다!public GameObject apple; void Start() { InvokeRepeating("makeApple", 0, 0.5f); // makeApple()가 05초마다 실행된다. } void makeApple() { Instantiate(apple); // apple을 반복해서 생성한다. }
그 전에 먼저! 폰트 변경하기
1. Assets 밑에 Fonts 폴더 생성
2. Fonts 폴더 밑에 넣고 싶은 폰트 파일 드래그 앤 드롭.
MainScene 밑에 UI → Canvas 생성하기
Canvas 밑에 text 생성
이름 : label_score
사이즈 : 200 * 200, PosX : -275, PosY : -575
폰트 사이즈 : 50, 폰트 스타일 : Bold, 색상 : 255, 255, 255
내용 : 점수
생성된 label_score를 3번 복사(Ctrl + d), 아래와 같이 수정
이름 : score, PosX : -150, 내용 : 0, 폰트 사이즈 60
이름 : label_time, PosX : 25, 내용 : 남은 시간
이름 : time, PosX : 250, 내용 : 30.00, 폰트 사이즈 60
✨ 싱글톤? 프로젝트 전체에 하나만 존재하도록 설정하는 것!
gameManager.cs에 아래의 코드 작성
public static gameManager I; // default는 "public static gameManager instance;" void Awake() { I = this; // 프로젝트 전체에서 gameManager.I를 부르면 이 gameManager가 불려진다. }
int totalScore; public void addScore(int score) { totalScore += score; }
apple.cs에 OnCollisionEnter2D()를 이용해 펭귄과 부딪힐 시 점수를 더하자.
void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.tag == "ground") { Destroy(gameObject); } // 펭귄이라는 태그를 지닌 물체와 부딪힐 시 if (collision.gameObject.tag == "penguin") { gameManager.I.addScore(score); // gameManager의 addScore에게 score를 넘김 Destroy(gameObject); } }
gameManager.cs에 score 텍스트 가져오기
using UnityEngine.UI; public Text scoreText;
유니티도 돌아와서 gameManager에게 score 텍스트 드래그 앤 드롭
public void addScore(int score) { totalScore += score; scoreText.text = totalScore.ToString(); }
public void addScore(int score) { totalScore += score; if (totalScore < 0) totalScore = 0; scoreText.text = totalScore.ToString(); }
gameManager.cs에서 시간이 가도록 만들기!
🤔 생각해보기
👉 시간을 흐르게 한다? 유니티의 Time 클래스!
👉 Time.deltaTime : 마지막 프레임이 완료된 후 경과한 시간을 반환.
✨ if(정해진 시간 - 경과 시간 < 0) 종료!
변수 선언!
정해진 시간을 변수 limit으로 선언하자.
또한 유니티의 time을 텍스트로 받아오기 위해 timeText라는 Text 변수를 생성하자.float limit = 30.0f; public Text timeText;
timeText를 선언했으면 유니티로 돌아와 gameManager에게 timeText를 지정해주자.
gameManager.cs의 Update()에 시간을 흐르게 하는 코드를 넣기.
void Update() { limit -= Time.DeltaTime; timeText.text = limit.ToString("N2"); // N2 : 소수점 둘째 자리까지 출력! }
🤔 게임을 멈추기? Time.timeScale = 0.0f로 변경하면 게임 내 시간이 멈춘다!
선언한 변수 limit이 0보다 작아지면 Time.timeScale을 0으로 변경하자!void Update() { limit -= Time.deltaTime; // 남은 시간이 0보다 작아지면 if (limit < 0) { limit = 0.0f; // 출력되는 timeText의 text값을 0.0으로 변경시켜주기 Time.timeScale = 0.0f; // 게임 시간 멈추기 } timeText.text = limit.ToString("N2"); }
🤔 생각해보기
종료 화면은 게임 내에 존재하나 게임이 끝날 때까지 나타나지 않다가 게임 종료 시 나타난다.
Inactivate를 시켰다가, 게임이 종료되는 시점에 Activate 시키자!
Canvas를 하나 더 만들어 이름을 panel로 변경!
그 밑에 Image, Text를 만든다.
- 이미지 :
크기 : 400 * 250, PosY : 100
색상 : 255, 255, 255- Text : Text 생성 시 반드시! Text의 Inspector에서 Rect Transform을 Reset 시키자! 안그러면 텍스트가 화면에 나타나지 않는다...
크기 400 * 250, PosY : 100
색상 : 0, 0, 0
panel을 Inactivate 시킨다(체크박스 체크 해제).
이제 만들어 놓은 종료 화면을 게임이 종료될 때 띄우자.
이미 gameManager.cs에 게임을 종료시키는 코드가 있었다. 그 코드를 활용해보자!
public GameObject panel;
void Update() { limit -= Time.deltaTime; if (limit < 0) { limit = 0.0f; // panel을 active 시키기 panel.SetActive(true); Time.timeScale = 0.0f; } timeText.text = limit.ToString("N2"); }
우리가 만든 panel을 누르면 게임이 재시작하게 만들기!
게임 재시작? MainScene을 새로 고침하기!
- Scene에 대한 기능을 사용하기 위해서는
using UnityEngine.SceneManagement;
를 Import해야 한다.- 특정 씬을 불러오는 코드 :
SceneManager.LoadScene("Scene_name");
따라서 우리가 gameManager.cs에 추가해야 할 코드는 다음과 같다.
using UnityEngine.SceneManagement; public void retry() { SceneManager.LoadScene("MainScene"); }
❗ panel을 클릭하면 → panel의 retry()함수가 gameManager의 retry()를 호출하는 방식!
❓ 이유? MainScene을 다시 부르는 정도의 중요한 코드는 gameManager가 실행을 하도록 만드는 편이 안전!
panel.cs를 만들어 panel에 연결해준다.
panel.cs에 retry()함수 추가.
public void retry() { gameManager.I.retry() }
😱 게임이 다시 시작하지 않는다!
이유? 아직 Time.timeScale이 0이기 때문이다!
해결 방안? Time.timeScale을 1로 초기화 시켜주는 함수를 만들자!
✨ MainScene을 로드하면 initGame()이란 함수가 실행되도록 만들자.
🤔 초기화 되어야 할 요소? timeScale, 점수, 남은 시간
void Start() { InvokeRepeating("makeApple", 0, 0.5f); // MainScene이 로드되면 실행된다 initGame(); } void initGame() { Time.timeScale = 1.0f; totalScore = 0; limit = 30.0f; }
게임 개발 부트 캠프에서 배운 1주차 내용들을 복습하는 겸사겸사 TIL를 적어보았다. 아니면 WIL일려나? 아무튼...
처음 유니티를 시작하며 모르는 것도 많고 헷갈리는 것도 너무 많아 머리가 어지럽지만, 확실히 복습을 해보니 좋은 것 같다.
다만, 다음부터는 TIL를 하기 위해서는 시행 과정 전부를 적기보단 헷갈리거나 꼭 외워야겠다 싶은 것들 위주로 작성하는 것이 맞는 것 같다. 아직 익혀야 할 것들은 산더미처럼 많은데 이러한 방식은 너무 오래 걸린다. 간결하게 기능별로 적도록 하자!