✅ 오늘의 목표
Cat 횡 스크롤 게임을 업그레이드해보자!
1. 게임 시간 화면에 나타내기
2. 게임 Score 기능 구현하기
3. 화면 별로 다른 Bgm 적용하기
4. 게임 오버 기능 구현하기
5. Video 활용
1. Game Play Time
🥕 예행 작업
1. Script 생성 (GameManager)
1. Text UI 생성

2. 코드
using TMPro;
using UnityEngine;
namespace Cat
{
public class GameManager : MonoBehaviour
{
public TextMeshProUGUI playTimeUI;
private float timer;
void Update()
{
timer += Time.deltaTime;
playTimeUI.text = string.Format("플레이 시간 : {0:F1}초", timer);
}
}
}
3. 적용

빈 게임 오브젝트를 생성하고 위와 같이 설정해 준다.

이때 텍스트 UI는 'No Wrap'으로 설정해 주어야 글자가 원하는 대로 나타난다.
4. 결과

2. Score
1. 에셋 다운로드
1.1. 에셋 다운로드
https://assetstore.unity.com/packages/2d/characters/pixel-adventure-1-155360
Pixel Adventure 1 | 2D 캐릭터 | Unity Asset Store
Elevate your workflow with the Pixel Adventure 1 asset from Pixel Frog. Find this & more 캐릭터 on the Unity Asset Store.
assetstore.unity.com
1.2. 이미지 자르기 (해당 이미지는 이미 잘 잘려있으므로 생략 가능)


원하는 과일을 선택하고 (이미지 내 경로 참고) 인스펙터에서 Open Sprite Editor을 클릭하여 이미지를 자르는 도구를 열어준다.
- 이미지를 자르기 위해서는 Texture Type이 'Sprite'으로 설정되어 있어야 하고
- 단일 이미지가 아니기 때문에 Sprite mode도 'Multiple'로 설정해 준 후 열어주어야 한다.
- 캡쳐본에는 나타나있지 않지만 이미지의 크기를 조절하기 위해 Pixels per unit 값을 20으로 설정해 준다.
1.3. 이미지 배치 및 애니메이션 적용

Image UI를 생성해 준 후 위치를 수정해 준 다음 Source 이미지를 원하는 과일로 드래그 앤 드롭한다.

Score 카운트 값을 화면에 나타내기 위해서
Text UI를 과일 이미지 자식 오브젝트로 생성한 후 위치를 조절해 준다.

애니메이션을 생성해 준다.
1.4. 트리거 작업

고양이가 과일을 먹으면 화면에 과일이 나타나지 않아야 하므로 Trigger를 설정해 주자

트리거 충돌 작업은 CompareTag로 처리해 줄 것이기 때문에 Tag를 새로 생성해 준 후 오브젝트에 적용해 준다.
// Cat Controller 스크립트
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Fruit"))
{
// 과일을 먹으면 해당 과일은 화면에서 나타나지 않아야 하므로
other.gameObject.SetActive(false);
}
}
고양이 오브젝트가 과일 오브젝트에 닿으면 이미지가 비활성화되도록 설정해 준다.
2. Loop 스크립트 업그레이드
🥕 예행 작업
1. Script 생성 (ItemEvent)
과일이 게임 플레이 중간중간 다시 생성되도록 설정해 줄 것이다.
기존에 동일하게 작업했던 오브젝트가 있는데, 바로 Pipe다!
한 개의 스크립트로 Pipe, Fruit 두 가지 모두 랜덤한 위치에 나타나도록 설정해 주기 위해
기존 스크립트를 수정해서 적용하기로 한다.

- 중복된 Pipe 오브젝트 삭제 = 1개만 남겨둠
- 부모 오브젝트 생성 후 Pipe 위치에 맞게 피봇 위치 수정
- 과일 오브젝트도 해당 부모 오브젝트 하위에 넣어주기
새로운 스크립트를 적용할 것이기 때문에 Pipe에 적용된 스크립트들 (ColliderEvent, Transform LoopMap)은 모두 삭제해 준다 (비활성화하면 충돌 났었음..)
using UnityEngine;
public class ItemEvent : MonoBehaviour
{
public float moveSpeed = 2f;
public float returnPosX = 15f;
public float randomPosY;
void Start()
{
SetRandomPos(transform.position.x);
}
void Update()
{
transform.position += Vector3.left * moveSpeed * Time.fixedDeltaTime;
if (transform.position.x <= -returnPosX)
SetRandomPos(returnPosX);
}
private void SetRandomPos(float posX)
{
randomPosY = Random.Range(-7.2f, -3.5f);
transform.position = new Vector3(posX, randomPosY, 0);
}
}
기존 Transform LoopMap 스크립트에 작성된 내용과 크게 다른 점이 없는데 약간의 중복 내용을 없애 줬음!
이제 화면에 나타나는 방법 3가지를 정의해 줄 거임
- 과일 오브젝트만 나타나기
- 파이프 오브젝트만 나타나기
- 둘 다 나타나기
어젠가? 배운 enum을 사용해서 이 3가지만 값을 가질 수 있는 타입을 만들어보자!
using UnityEngine;
public class ItemEvent : MonoBehaviour
{
public enum ColliderType {Pipe, Fruit, Both}
public ColliderType colliderType;
public GameObject pipe;
public GameObject fruit;
public GameObject particle;
public float moveSpeed = 2f;
public float returnPosX = 15f;
public float randomPosY;
void Start()
{
SetRandomSetting(transform.position.x);
}
void Update()
{
transform.position += Vector3.left * moveSpeed * Time.fixedDeltaTime;
if (transform.position.x <= -returnPosX)
SetRandomSetting(returnPosX);
}
private void SetRandomSetting(float posX)
{
randomPosY = Random.Range(-7.2f, -3.5f);
transform.position = new Vector3(posX, randomPosY, 1f);
// 다 꺼놓고 Switch문에서 켜주기
pipe.SetActive(false);
fruit.SetActive(false);
particle.SetActive(false);
// Pipe, Fruit, Both 3개의 값중 하나
colliderType = (ColliderType)Random.Range(0, 3);
switch (colliderType)
{
case ColliderType.Pipe:
pipe.SetActive(true);
break;
case ColliderType.Fruit:
fruit.SetActive(true);
break;
case ColliderType.Both:
pipe.SetActive(true);
fruit.SetActive(true);
break;
}
}
}
gameObject를 public으로 모두 선언해 주고,
switch문을 사용해서 과일, 파이프, 둘 다 3개 값 중 하나가 랜덤한 위치에 나타나도록 구현해 주었다.
3. 과일 애니메이션 추가
이제 과일을 먹으면 터지는? 그런 애니메이션을 추가해 보겠다.

우선 아까처럼 이미지를 좀 잘라줄 건데
마지막 이미지의 경우 점이 딱 3개만 찍혀있기 때문에 Auto로 자르게 되면 원하지 않는 모양으로 잘린다.
이때에는 행&열의 개수를 지정해서 잘라주면 된다. (이미지가 일정한 간격으로 있을 경우에만!!)
// ItemEvent 스크립트
public GameObject particle;

파티클 애니메이션을 적용시켜 주기 위해서 ItemEvent 스크립트에 Particle 오브젝트를 지정해 준다.
// CatController 스크립트
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Fruit"))
{
// 과일을 먹으면 해당 과일은 화면에서 나타나지 않아야 하므로
other.gameObject.SetActive(false);
// Fruit의 부모 오브젝트로 접근해서 부모의 다른 자식인 particle에 접근 후 활성화
other.transform.parent.GetComponent<ItemEvent>().particle.SetActive(true);
}
}
그런 다음 고양이가 과일을 먹었을 때 파티클이 터져야 하니까, CatController 스크립트에 해당 부분을 구현해 준다.
4. Score 구현

이제 고양이가 과일을 먹으면 Score가 올라가도록 구현해 보겠다.
// CatController 스크립트
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Fruit"))
{
// 과일을 먹으면 해당 과일은 화면에서 나타나지 않아야 하므로
other.gameObject.SetActive(false);
// Fruit의 부모 오브젝트로 접근해서 부모의 다른 자식인 particle에 접근 후 활성화
other.transform.parent.GetComponent<ItemEvent>().particle.SetActive(true);
GameManager.score++;
}
}
이 역시 고양이 동작에 따라 구현되어야 하므로 CatController 스크립트에 적용해 주었고
using TMPro;
using UnityEngine;
namespace Cat
{
public class GameManager : MonoBehaviour
{
public TextMeshProUGUI playTimeUI;
public TextMeshProUGUI scoreUI;
private float timer;
public static int score;
void Update()
{
timer += Time.deltaTime;
playTimeUI.text = string.Format("플레이 시간 : {0:F1}초", timer);
scoreUI.text = $"X {score}";
}
}
}
점수는 GameManager 스크립트에 구현해 주었다.
3. Sound
지금은 게임을 시작하면 한 가지 노래만 나오고 있는데,
인트로 화면에선 인트로 음악이, 충돌 났을 때에는 충돌 사운드가 나도록 설정해 주겠다.
using UnityEngine;
// 네임 스페이스 설정
namespace Cat
{
public class SoundManager : MonoBehaviour
{
public AudioSource audioSource;
public AudioClip introBgmClip;
public AudioClip jumpClip;
public AudioClip bgmClip;
public AudioClip colliderClip;
public void SetBgmSound(string bgmName)
{
if (bgmName == "Intro")
{
audioSource.clip = introBgmClip;
}
else if (bgmName == "Play")
{
audioSource.clip = bgmClip;
}
audioSource.loop = true;
audioSource.volume = 0.2f;
audioSource.Play();
}
public void OnJumpSound()
{
// PlayOneShot = 한번만 실행
audioSource.PlayOneShot(jumpClip);
}
// 충돌 사운드 구현
public void OnColliderSound()
{
audioSource.PlayOneShot(colliderClip);
}
}
}

인트로일 때에는 인트로가, 플레이 중일 때에는 플레이 음악이 나오도록 설정해 주기 위해 조건문을 사용해 주었고
위 코드에서 조건문만 줬지 어떤 bgm을 실행시킬 것인지는 구현되지 않았기 때문에 아무것도 안 들림!
게임 Intro bgm은 게임 시작 시 디폴트 값이니까 이 부분은 GameManager에 구현해 준다
using TMPro;
using UnityEngine;
namespace Cat
{
public class GameManager : MonoBehaviour
{
public SoundManager soundManager;
public TextMeshProUGUI playTimeUI;
public TextMeshProUGUI scoreUI;
private float timer;
public static int score;
public static bool isPlay;
void Start()
{
soundManager.SetBgmSound("Intro");
}
void Update()
{
// 게임 시작 전에는 update문 실행X
if (!isPlay) return;
timer += Time.deltaTime;
playTimeUI.text = string.Format("플레이 시간 : {0:F1}초", timer);
scoreUI.text = $"X {score}";
}
}
}
지금 위 두 개 코드로만 봤을 때에는 절대 Play Bgm이 실행되지 않음
Play Bgm은 게임이 시작되었을 때 = 화면이 전환될 때이니까 UI Manager에 구현해 주자
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Cat
{
public class UIManager : MonoBehaviour
{
public SoundManager SoundManager;
public GameObject playObj;
public GameObject introUI;
public GameObject playUI;
public TMP_InputField inputField;
public TextMeshProUGUI nameTextUI;
public Button startButton;
void Awake()
{
playObj.SetActive(false);
introUI.SetActive(true);
playUI.SetActive(false);
}
void Start()
{
// 유니티 인스펙터 창에서 넣지 않고 코드로 넣어주는 방법
startButton.onClick.AddListener(OnStartButton);
}
public void OnStartButton()
{
bool isNoText = inputField.text == "";
if (isNoText)
{
Debug.Log("입력된 텍스트 없음");
// startButton.interactable = false;
}
else
{
nameTextUI.text = inputField.text;
SoundManager.SetBgmSound("Play");
GameManager.isPlay = true; // true가 되면 타이머 시작
playObj.SetActive(true);
playUI.SetActive(true);
introUI.SetActive(false);
}
}
}
}

'Get Ready' 버튼을 클릭했을 때 동작되도록 OnStartButton() 메서드 안에 음악 플레이를 구현해 주었다.
잊지 말고 꼭 각 Manager에 SoundManager 추가해 주기
4. Game Over
기존에는 게임이 종료되었을 때 Fade Panel만 구현해 주었었는데 (화면이 점점 어두워지는 거)
이번에는 Game Over 문구가 나타나면서 Fade Panel이 적용되도록 업그레이드해주자
1. 오브젝트

Outro 오브젝트 생성 후 그 아래에 Fade Panel과 Game Over 오브젝트들을 추가해 준다.
Game Over 이미지를 넣을 때에는 처음에는 크기가 좀 이상한데 'Set Native Size'를 클릭해 주면 알아서 조정된다.
2. 애니메이션

애니메이션은 취향에 맞게 설정해 주면 되는데
나는 그냥 작은 글씨에서 커졌다가 다시 작아지도록 스케일 값만 조절해 주었다.
그리고 게임 오버 애니메이션은 반복될 필요 없으니 Loop 값도 꺼주었다.
3. 코드
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class FadePanel : MonoBehaviour
{
public Image fadePanel; // 페이드 이미지
public void OnFade(float fadeTime, Color color)
{
StartCoroutine(FadeRoutine(fadeTime, color));
}
// 이렇게 모듈화 하였기 때문에 다른 씬/프로젝트에서도 사용 가능
IEnumerator FadeRoutine(float fadeTime, Color color)
{
float timer = 0f;
float percent = 0f;
while (percent < 1f)
{
timer += Time.deltaTime;
percent = timer / fadeTime; // Fade 퍼센트
fadePanel.color = new Color(color.r, color.g, color.b, percent);
yield return null;
}
}
}
기존에 작성한 Fade Panel 스크립트를 조금 수정해 주었다.
코루틴이 시작될 시간과 패널의 색깔을 지정해 줄 수 있도록 파라미터 값을 받아오고, 이 값들은 CatController 에서 삽입해 준다.(고양이가 파이프에 닿았을 때 게임이 종료되니까)
// CatController 스크립트
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Fruit"))
{
// 과일을 먹으면 해당 과일은 화면에서 나타나지 않아야 하므로
other.gameObject.SetActive(false);
// Fruit의 부모 오브젝트로 접근해서 부모의 다른 자식인 particle에 접근 후 활성화
other.transform.parent.GetComponent<ItemEvent>().particle.SetActive(true);
GameManager.score++;
}
}
private void OnCollisionEnter2D(Collision2D other)
{
// 게임 오버 Game Over (Outro)
if (other.gameObject.CompareTag("Pipe"))
{
soundManager.OnColliderSound();
gameOverUI.SetActive(true);
fadeUI.SetActive(true);
fadeUI.GetComponent<FadePanel>().OnFade(3f, Color.black);
}
if (other.gameObject.CompareTag("Ground"))
{
// 점프 중일 때 계속 점프 애니메이션이 작동되도록
catAnim.SetBool("IsGround", true);
jumpCount = 0;
}
}
이렇게 코드를 구현해 준 후 유니티 에디터에서 Cat 인스펙터(스크립트 부분)에 알맞은 오브젝트들을 연결해주어야 함

4. 게임 난이도 하향..

생각보다 게임 난이도가 어려워서 파이프의 윗부분은 닿아도 게임이 종료되지 않도록 콜라이더를 조절해 주었다.
콜라이더를 조절하면서 생긴 여백은 Square로 채워주었다.
5. Video
비디오 UI를 한번 사용해 볼 건데 게임 업그레이드보단 그냥 UI 사용해 보기에 가깝다!
게임이 성공했을 때, 실패했을 때 비디오 영상이 실행되도록 해보자


1. 게임 성공 VS 게임 종료
// CatController 스크립트
public GameObject happyVideo;
public GameObject sadVideo;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Fruit"))
{
// 과일을 먹으면 해당 과일은 화면에서 나타나지 않아야 하므로
other.gameObject.SetActive(false);
// Fruit의 부모 오브젝트로 접근해서 부모의 다른 자식인 particle에 접근 후 활성화
other.transform.parent.GetComponent<ItemEvent>().particle.SetActive(true);
GameManager.score++;
// 게임 성공
if (GameManager.score == 1)
{
fadeUI.SetActive(true);
fadeUI.GetComponent<FadePanel>().OnFade(3f, Color.white);
this.GetComponent<CircleCollider2D>().enabled = false;
Invoke("HappyVideo", 5f);
}
}
}
private void OnCollisionEnter2D(Collision2D other)
{
// 게임 오버 Game Over (Outro)
if (other.gameObject.CompareTag("Pipe"))
{
soundManager.OnColliderSound();
gameOverUI.SetActive(true);
fadeUI.SetActive(true);
fadeUI.GetComponent<FadePanel>().OnFade(3f, Color.black);
this.GetComponent<CircleCollider2D>().enabled = false;
Invoke("SadVideo", 5f);
}
if (other.gameObject.CompareTag("Ground"))
{
// 점프 중일 때 계속 점프 애니메이션이 작동되도록
catAnim.SetBool("IsGround", true);
jumpCount = 0;
}
}
private void HappyVideo()
{
happyVideo.SetActive(true);
fadeUI.SetActive(false);
gameOverUI.SetActive(false);
playUI.SetActive(false);
soundManager.audioSource.mute = true;
}
private void SadVideo()
{
sadVideo.SetActive(true);
fadeUI.SetActive(false);
gameOverUI.SetActive(false);
playUI.SetActive(false);
soundManager.audioSource.mute = true;
}
- 과일 1개 먹으면 게임 성공되는 조건문 구현 -> 하얀 Fade 나타나도록 설정해 줌
- 게임이 성공/실패된 후 Video가 나타나도록 Invoke 메서드 구현
2. 결과

오늘은 멘토님께서 스터디 운영을 장려하셨다...
하루에 9 to 6 공부해도 턱없이 부족한걸 알지만.. 왜 자꾸만 9 to 6 공부하는 것도 대단하다고 생각하게 되는지.. 호호
물론 지금도 이렇게 공부하는게 대단한 거구.. 정말 쉽지 않은 거긴 하지만..~~;; 역시 세상은 넘 각박하다~!!
일단 노트북 오면.. 그때 좀 해보기로 하고.. 그 전까지는 좀 무슨 공부를 해봐야할지 고민해봐야지...
알고리즘 공부도 필요하다 생각하고, 게임 개발도 따로 해봐야할 거 같은데 흠흠~~ 조언을 좀 구해봐야하나 하고싶은게 너무많다!!
'Unity > 멋쟁이사자처럼 부트캠프' 카테고리의 다른 글
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(20일차) - Cat 횡 스크롤 게임 마무리, Camera 세팅 및 Random 활용하기 (2) | 2025.06.12 |
|---|---|
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(19일차) - Video 심층 학습 및 TV 리모컨 구현부터 게임 빌드까지! (0) | 2025.06.11 |
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(17일차) - C# 기초(9) (0) | 2025.06.09 |
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(16일차) - UI 활용 및 C# 기초 학습(8) (3) | 2025.06.05 |
| [멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(15일차) - 사운드 및 UI 활용 (1) | 2025.06.04 |