Unity에서 슈팅 기능과 적 AI, 게임 완성도를 높이는 고급 기능들을 구현하는 가이드입니다.
- 슈팅 기능 구현
- 총알 발사 시스템
- 적 AI 구현
- 충돌 감지와 피해 시스템
- 게임 오버 시스템
- 사운드 효과 추가
- 게임 완성
플레이어가 마우스를 클릭하면 총에서 총알이 나가도록 하는 기능을 구현한다.
😄 단, 아직 총알은 움직이지 않아요.
이번 강의의 핵심은 “총알을 인스턴스화(Instantiate)” 하는 것!
총알이 생겨날 위치를 알려줘야 하기 때문이에요.
총알이 플레이어 몸통에서 나오면 이상하겠죠? 총구에서 나와야 하잖아요.
1. Player 프리팹 선택
2. 우클릭 > Create Empty 로 빈 오브젝트 생성 (Player의 자식으로)
3. 이름을 Gun으로 바꿔요
4. Gun의 위치 조정
플레이어의 손 근처, 총이 있을 법한 위치로 이동
예: (0.5, -0.2) 정도 오른쪽 아래로 살짝
Gun 오브젝트는 총알이 생성될 “출발 위치”예요.
총 오브젝트 생성
미리 만들어둔 게임 오브젝트 틀이라고 보면 돼요.
게임 중에 코드로 “복사해서 사용”할 수 있어요.
1. 스프라이트 라이브러리에서 플레이어 스프라이트 하나 선택
예: 19번 스프라이트 (작고 귀여운 모습)
2. 씬으로 끌어다 놓고 이름을 Bullet으로 바꾸기
3. Bullet 오브젝트에 다음 구성 요소 추가:
Sprite Renderer → 플레이어와 같은 Sorting Layer
BoxCollider2D (또는 원형 Collider) 추가
4. 색깔도 바꾸기 (예: 빨간색으로 → 총알처럼 보이게)
5. Bullet을 Prefabs 폴더로 드래그해서 프리팹으로 만들기
프리팹에 Bullet 을 추가
Unity에서 오브젝트 충돌을 설정할 때 사용하는 구분자예요.
예: 총알은 적과 충돌하지만 플레이어와는 충돌 안 하게 하고 싶을 때 사용.
1. Bullet 오브젝트 선택 > Layer → Add Layer… > Bullets 생성
2. Bullet 오브젝트의 Layer를 Bullets로 설정
3. 메뉴 > Edit > Project Settings > Physics 2D
4. 충돌 설정에서:
Bullets가 Enemy, Hazards와 충돌할 수 있도록 체크
Player, Ground 등과는 충돌 해제
프리팹의 Bullet 색 변경 ( 빨간 자기 자신을 발 )
코드로 오브젝트를 실시간으로 새로 만들어내는 것이에요.
예: 총알 프리팹을 복제해서 “씬 위에” 출현시키는 것
[SerializeField] GameObject bullet; // 총알 프리팹 참조
[SerializeField] Transform gun; // 총구 위치 참조
SerializeField는 Unity 에디터에서 프리팹이나 오브젝트를 직접 드래그해서 연결할 수 있게 해줘요.
void OnFire(InputValue value) {
if (!isAlive) return;
Instantiate(bullet, gun.position, transform.rotation);
}
마우스 클릭(Input)을 감지하면
총알을 gun.position 위치에
플레이어의 회전값으로 생성!
Instantiate(bullet, gun.position, transform.rotation);
이 줄은 총알을 생성하는 핵심 코드입니다.
3개의 인자에 대해 설명하면:
bullet 생성할 총알 프리팹입니다. (미리 만들어둔 총알 오브젝트)
gun.position 총알이 생성될 위치입니다. 보통 플레이어 총구 위치로 설정된 빈 오브젝트의 좌표입니다.
transform.rotation 총알이 생성될 때 가질 회전값(Rotation) 입니다. 현재 플레이어가 바라보는 방향과 같게 설정됩니다.
회전값으로 생성된다는 건 무슨 뜻인가요?
transform.rotation은 플레이어의 회전 상태(방향) 를 나타냅니다.
총알은 생성될 때 이 회전값을 그대로 갖기 때문에,
플레이어가 오른쪽을 보면 → 총알도 오른쪽을 향해 생성되고
플레이어가 왼쪽을 보면 → 총알도 왼쪽을 향해 생성됩니다.
이렇게 하면 총알이 플레이어가 바라보는 방향으로 발사되는 것처럼 보이게 됩니다.
Input Actions 에서 Fire 설정 확인
Fire 액션이 있고
마우스 왼쪽 버튼에 연결되어 있어야 해요
InputSystem 의 Fire
1. Player 오브젝트 선택
2. PlayerMovement 컴포넌트에서:
Bullet 필드에 Bullet 프리팹 드래그
Gun 필드에 Gun 오브젝트 드래그
여기서 주의해야할게 왼쪽의 Hierachy 에서 가져와서 넣어주면 화면상에
만들어져 있는 총알을 가져오는거다 ( 그래서 지우면 사라짐 )
그렇게 하지 말고 프리팹의 총알을 가져와서 넣어준다
총알을 화면에 발사해서 날아가게 하고
적과 충돌하면 적을 처치하며
벽에 부딪히면 총알이 사라지게 만드는 기능 구현
Rigidbody2D 컴포넌트 추가
Body Type: Dynamic
Gravity Scale: 0 (중력 영향 없음)
Capsule Collider 2D 추가 (충돌 감지용)
Collider 크기 수정
회전 방지
// Inspector 창에서 총알 속도를 설정할 수 있도록 하는 변수 (기본값: 20f)
[SerializeField] float bulletSpeed = 20f;
// 총알에 적용될 Rigidbody2D 컴포넌트 참조 변수
Rigidbody2D myRigidbody;
// 총알이 움직일 x축 속도
float xSpeed;
void Start() {
// 총알이 부착된 오브젝트에서 Rigidbody2D 컴포넌트를 가져옴
myRigidbody = GetComponent<Rigidbody2D>();
// 현재 씬에 있는 PlayerMovement 스크립트를 가진 오브젝트를 찾음
PlayerMovement player = FindObjectOfType<PlayerMovement>();
// 플레이어가 바라보는 방향에 따라 총알 속도를 정함
// (플레이어가 왼쪽을 보고 있으면 -1, 오른쪽을 보고 있으면 1)
xSpeed = player.transform.localScale.x * bulletSpeed;
}
void Update() {
// 총알을 x축으로 이동시킴 (속도 적용)
myRigidbody.velocity = new Vector2(xSpeed, 0f);
}
플레이어의 localScale.x 값을 사용해 총알의 방향 결정
Rigidbody2D.velocity를 이용해 화면 가로 방향으로 날아가게 함
FindObjectOfType<PlayerMovement>()는 뭐야?
이 함수는 씬 안에 있는 PlayerMovement 스크립트를 가진 첫 번째 오브젝트를 찾아서 가져옵니다.
이걸 통해 PlayerMovement 클래스(=플레이어)를 찾고, 그 Transform 값을 가져올 수 있어요.
예: player.transform.localScale.x는 플레이어가 오른쪽(1) 또는 왼쪽(-1)을 보고 있는지를 판단하는 데 사용됨.
주의: FindObjectOfType는 성능상 비용이 크기 때문에
Update() 안에서는 사용하지 않고, Start() 같은 한 번만 실행되는 함수에서 사용하는 것이 좋습니다.
void OnTriggerEnter2D(Collider2D other) {
if (other.tag == "Enemy") {
Destroy(other.gameObject); // 적 제거
Destroy(gameObject); // 총알 제거
}
}
적 오브젝트는 반드시 태그를 "Enemy"로 설정해야 함.
void OnCollisionEnter2D(Collision2D other) {
Destroy(gameObject); // 그냥 사라지게 처리
}
만든 Bullet 스크립트를 Bullet 프리팹에 넣어줘서 총알이 날아가도록 해준다
충돌 시 효과음, 애니메이션 등을 추가하면 완성도 상승
플레이어 스크립트에서 총알 발사를 위한 Input 처리 필요
Layer, Physics2D.IgnoreLayerCollision 등을 활용해 플레이어와 총알 간 충돌 방지 가능
Object Pooling을 사용하면 성능 개선 가능
총 3개의 레벨(Level)을 만들어보는 것이 이번 강의의 목표입니다.
각 레벨을 하나의 씬(Scene)으로 구성하고, 플레이어가 다음 레벨로 넘어갈 수 있도록 설계합니다.
프리팹(Prefab): 여러 번 사용할 오브젝트를 저장해 두는 Unity의 기능
1. 적(Enemy) 오브젝트를 선택해 프리팹으로 저장
2. **카메라(Camera)**도 프리팹으로 저장
3. 모든 필요한 오브젝트를 프리팹 폴더에 저장해 두세요
👉 프리팹으로 만들어 두면 다른 씬에서도 쉽게 재사용할 수 있습니다.
프리팹에 필요한 에셋들 추가
1. Scenes 폴더에 있는 현재 씬을 복제합니다
Windows: Ctrl + D / Mac: Cmd + D
2. 이름을 바꿔주세요
Level 1, Level 2, Level 3 등
3. Scenes 폴더 → Levels 폴더로 이름 변경 (또는 새 폴더 만들기)
Unity 용어: Scene
게임 개발 용어: Level (강사님은 "Level" 선호)
1번 씬을 복제해서 추가
플레이어가 Exit(출구) 오브젝트를 만나면 일정 시간 후에 다음 레벨로 이동하는 기능을 구현
Hierarchy에서 2D Object > Sprite > Square 생성 후 이름을 Exit으로 변경.
Sprite Renderer에서 본인이 만든 출구 픽셀 아트(Exit Pixelated)로 스프라이트 교체.
새 Sorting Layer를 만들어 이름은 Interactables로 설정.
Layer도 같은 이름으로 설정.
적절한 크기 조절: Scale = (0.5, 0.5) 등.
Box Collider 2D 추가하고 Is Trigger 체크.
Exit 오브젝트를 Prefabs 폴더로 드래그해서 프리팹 생성.
Scripts 폴더에서 LevelExit 스크립트 생성 후 Exit 프리팹에 추가.
더블 클릭해서 코드 편집.
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
StartCoroutine(LoadNextLevel());
}
}
// Unity에서 인스펙터 창에 이 변수를 노출시켜서 값 조정이 가능하게 함
[SerializeField]
float levelLoadDelay = 1f; // 씬을 전환하기 전에 기다릴 시간(초)
// 코루틴 함수: 일정 시간 기다린 후에 다음 씬을 로드함
IEnumerator LoadNextLevel()
{
// 게임 시간과 상관없이 실제 시간 기준으로 'levelLoadDelay' 초만큼 기다림
yield return new WaitForSecondsRealtime(levelLoadDelay);
// 현재 활성화된 씬의 인덱스 번호를 가져옴 (Build Settings에서의 순서)
int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
// 다음 씬의 인덱스를 현재 씬 인덱스 + 1로 계산
int nextSceneIndex = currentSceneIndex + 1;
// 만약 현재 씬이 마지막 씬이라면 (즉, 다음 씬이 없는 경우)
if (nextSceneIndex == SceneManager.sceneCountInBuildSettings)
{
// 다시 첫 번째 씬(인덱스 0번)으로 돌아가도록 설정
nextSceneIndex = 0;
}
// 계산된 인덱스에 해당하는 씬을 로드함
SceneManager.LoadScene(nextSceneIndex);
}
코루틴의 간단한 정의:
"시간이 걸리는 작업을 잠시 멈췄다가 나중에 다시 실행할 수 있게 해주는 Unity의 기능입니다."
Unity에서는 일반적인 함수는 한 번에 모든 코드를 한 줄씩 빠르게 실행합니다.
하지만 어떤 경우에는 기다렸다가 작업을 해야 할 때가 있습니다. 예를 들면:
버튼을 누른 후 2초 뒤에 다음 씬으로 넘어가기
플레이어가 죽은 후 3초 후에 리스폰하기
일정 시간 동안 애니메이션 재생 후 이벤트 실행하기
이런 경우 일반 함수로는 "잠시 멈췄다가 다시 실행"을 할 수 없습니다.
이때 코루틴을 사용하면, 중간에 yield return을 통해 잠시 멈추고, 이후에 다시 실행할 수 있습니다.
IEnumerator ExampleCoroutine()
{
Debug.Log("1초 대기 전");
yield return new WaitForSeconds(1f); // 1초 대기
Debug.Log("1초 후 실행됨");
}
| 용어 |
설명 |
IEnumerator |
코루틴의 반환 타입 (특별한 함수처럼 작동) |
yield return |
중간에 멈춤을 뜻함. 이후 다시 이어서 실행됨 |
WaitForSeconds |
게임 시간 기준으로 기다림 |
WaitForSecondsRealtime |
실제 시간 기준으로 기다림 (게임이 멈춰도 흐름) |
모든 씬을 Scenes In Build에 추가.
**순서를 0, 1, 2, ...**로 정리.
프로젝트 빌드 셋팅을 해준다.
1. 게임 세션(GameSession)을 만들어서
2. 플레이어의 목숨(lives) 을 추적하고
3. 목숨이 모두 없어지면 게임을 리셋하고
4. 그렇지 않으면 장면(Scene)을 다시 시작하는 시스템을 만들기
유니티에서 씬(Scene) 을 다시 로드하면, 모든 게임 오브젝트들이 처음부터 다시 생성됩니다.
그래서 목숨, 점수, 코인 같은 데이터를 계속 유지하기가 어렵죠.
이럴 때 사용하는 것이 바로 게임 세션 오브젝트예요.
이건 우리가 게임을 플레이하는 전체 시간 동안 유지되도록 만들 거예요.
2-1. 빈 오브젝트 생성
Hierarchy 창에서 우클릭 → Create Empty
이름을 Game Session으로 바꾸기
2-2. C# 스크립트 생성
Scripts 폴더에서 우클릭 → Create > C# Script
이름을 GameSession으로 바꾸고
이 스크립트를 Game Session 오브젝트에 붙이기
프로그램에서 오직 하나만 존재해야 하는 클래스를 만들 때 사용하는 패턴이에요.
GameSession은 하나만 있어야 하니까 딱 맞는 방식이죠!
void Awake() {
int numGameSessions = FindObjectsOfType<GameSession>().Length;
if (numGameSessions > 1) {
Destroy(gameObject); // 이미 GameSession이 있으면 이건 파괴
} else {
DontDestroyOnLoad(gameObject); // 아니면 이걸 유지
}
}
FindObjectsOfType → 같은 타입 오브젝트를 모두 찾음
DontDestroyOnLoad → 씬이 바뀌어도 이 오브젝트는 유지됨
void Awake()
이건 유니티에서 오브젝트가 "처음 생성될 때 무조건 한 번" 실행되는 함수예요.
📌 Awake()는 오브젝트가 생기자마자 실행
📌 Start()는 모든 Awake가 끝나고 나서 실행
그래서 싱글턴 체크는 Awake에서 해야
다른 오브젝트보다 먼저 판단할 수 있어요!
FindObjectsOfType<GameSession>().Length
씬에 존재하는 모든 GameSession 오브젝트를 찾아서
그 개수(count) 를 구해요.
예를 들어:
처음에는 1개니까 괜찮아요.
그런데 다음 씬으로 넘어가면서 GameSession이 하나 더 생기면?
→ 총 2개가 되죠 ❌
if (numGameSessions > 1)
만약 2개 이상이라면?
→ 지금 막 생긴 이 GameSession은 가짜, 필요 없는 복사본이에요!
Destroy(gameObject);
그래서 이 오브젝트는 바로 제거해버립니다.
이렇게 해서 오직 하나만 남게 되죠.
else { DontDestroyOnLoad(gameObject); }
만약 이 GameSession이 처음 만든 진짜 하나뿐인 거라면,
씬이 바뀌어도 사라지지 않도록 설정해요.
| 기능 |
설명 |
| 오직 하나만 존재 |
이미 있으면 새로 생긴 거는 파괴 (Destroy) |
| 유지됨 |
씬이 바뀌어도 살아남음 (DontDestroyOnLoad) |
[SerializeField] int playerLives = 3;
SerializeField → 유니티 에디터에서 이 값 보이게 하기
플레이어는 처음에 목숨 3개를 가짐
public void ProcessPlayerDeath() {
if (playerLives > 1) {
TakeLife(); // 목숨 깎고 씬 다시 시작
} else {
ResetGameSession(); // 목숨 없으면 게임 전체 리셋
}
}
void TakeLife() {
playerLives--;
int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
SceneManager.LoadScene(currentSceneIndex);
}
playerLives-- → 목숨 하나 줄이기
SceneManager.LoadScene() → 현재 씬 다시 로드
void ResetGameSession() {
SceneManager.LoadScene(0); // 보통 0번이 메인 메뉴
Destroy(gameObject); // 기존 GameSession 없애기
}
void Die() {
if (myBodyCollider.IsTouchingLayers(LayerMask.GetMask("Enemy" ,"Hazards"))) {
isAlive = false;
myAnimator.SetTrigger("Die");
myRigidbody.velocity = deathKick;
FindObjectOfType<GameSession>().ProcessPlayerDeath();
}
}
적이나 위험 요소에 닿으면 죽음 애니메이션을 실행하고, GameSession에 죽음 처리를 맡겨요.
1. Game Session 오브젝트를 Prefabs 폴더로 끌어다 저장
2. 각 레벨(씬)에 Prefab을 끌어다 놓기
3. 각 레벨 저장 (Ctrl+S)
| 기능 |
설명 |
| 목숨 추적 |
playerLives 변수로 추적 |
| 죽었을 때 처리 |
ProcessPlayerDeath 메서드 |
| 씬 재시작 |
TakeLife 메서드 |
| 게임 전체 리셋 |
ResetGameSession 메서드 |
| 씬 변경 시 유지 |
DontDestroyOnLoad 사용 |
| 중복 제거 |
싱글턴 패턴 적용 |
개념: Sprite란?
Sprite는 2D 게임에서 이미지로 표현되는 캐릭터, 오브젝트 등을 말합니다.
1. Sprites 폴더로 이동하면, 동전 이미지가 여러 장 있는 스프라이트 시트가 있음.
2. Sprite Editor에서 보면 동전이 잘 나눠져 있음 (프레임별로 나눠진 애니메이션 이미지).
3. Hierarchy에서 우클릭 → Create Empty → 이름을 Pickups로.
4. 그 안에 또 우클릭 → Create Empty → 이름은 coin.
1. coin에 Sprite Renderer 컴포넌트 추가.
2. 스프라이트 중 하나 선택 (예: Coin 0번 이미지).
3. Sorting Layer를 Interactables로 설정 (다른 오브젝트 뒤에 가리지 않게 하기 위해).
4. Layer도 Interactables로 설정 (충돌 조건 설정을 위해).
1. Circle Collider 2D 추가 – 동전은 둥글기 때문에 원형이 적절.
2. 크기를 적절히 조절 (Edit Collider 버튼을 눌러서).
3. Is Trigger 체크 ✅
→ 이건 "부딪히지만 실제로 막지는 않는다"는 의미예요. 통과하면서 이벤트만 발생시킴.
💡 애니메이션이란?
스프라이트를 연속으로 재생해서 오브젝트가 움직이는 것처럼 보이게 합니다.
1. 프로젝트 창에서 동전 스프라이트 여러 개 선택 (예: Coin 0 ~ 5).
2. 우클릭 → Create → Animation → 이름은 CoinSpin → Animations 폴더에 저장.
3. 자동으로 .anim 파일이 생성됨 (애니메이션 파일).
4. 애니메이터 컨트롤러도 생성 → 이름은 Coin.
5. Coin 애니메이터에서 CoinSpin 애니메이션을 가운데로 끌어 넣음.
6. Entry → CoinSpin으로 연결됨.
7. CoinSpin 애니메이션 클릭 → Loop Time 체크 ✅ (계속 반복되게 하기 위해).
8. 동전 오브젝트에 Animator 컴포넌트 추가하고 → Controller에 Coin을 연결.
애니메이션 커트롤러를 연결
개념: Trigger 충돌 이벤트
플레이어가 트리거 콜라이더에 닿았을 때 특정 동작을 실행하는 방법입니다.
1. Scripts 폴더에서 우클릭 → Create > C# Script → 이름: CoinPickup.
2. Start와 Update 함수 삭제.
3. 다음 코드 작성:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CoinPickup : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "Player"){
Destroy(gameObject); // 동전 없애기
}
}
}
OnTriggerEnter2D: 트리거에 닿았을 때 실행되는 이벤트 함수.
other.tag == "Player": 닿은 게 플레이어인지 확인.
Destroy(gameObject): 현재 오브젝트(동전)를 없앰.
개념: Prefab이란?
여러 오브젝트를 같은 설정으로 관리하고 재사용할 수 있게 만든 ‘템플릿’입니다.
1. Hierarchy에서 완성된 동전 오브젝트를 Prefabs 폴더로 드래그 → 프리팹 생성.
2. 여러 개를 Scene에 배치해서 테스트 가능.
플레이어가 동전(Coin) 을 먹으면,
띠리링~ 하는 소리가 나게 하자!
1. 폴더 만들기
프로젝트 뷰에서 마우스 오른쪽 클릭 → Create > Folder
새 폴더 이름은 Audio
2. 오디오 클립 추가
강의자가 제공한 동전 소리 오디오 파일 (예: CoinPickup.wav)을
Audio 폴더에 넣기
Unity에서 소리를 재생하려면 AudioClip(소리 파일) 과
그걸 실제로 재생하는 도구(AudioSource) 가 필요해요.
AudioSource는 보통 게임 오브젝트에 붙어서 작동하는데,
여기서는 한 번만 소리 재생하고 파괴되니까
AudioSource.PlayClipAtPoint()라는 정적 함수(static method) 를 씁니다.
이제 동전을 먹으면 소리를 내게 스크립트에 코드를 추가해봅니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CoinPickup : MonoBehaviour
{
// 1. 소리 파일을 인스펙터에서 연결할 수 있도록 준비
[SerializeField] AudioClip coinPickupSFX;
// 2. 플레이어가 동전과 충돌하면 실행되는 메서드
void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "Player"){
// 3. 메인 카메라 위치에서 소리를 재생
AudioSource.PlayClipAtPoint(coinPickupSFX, Camera.main.transform.position);
// 4. 동전을 파괴
Destroy(gameObject);
}
}
}
보통 소리를 재생하려면 게임 오브젝트에 AudioSource가 붙어 있어야 해요.
하지만 동전은 소리를 재생하자마자 사라지니까,
그렇게 하면 소리가 재생되기도 전에 끊겨버려요.
그래서 Unity는 PlayClipAtPoint()라는 즉시 재생 함수를 제공해요.
이 함수는:
잠깐 존재하는 임시 오브젝트를 만들어서
거기서 소리를 재생하고
소리 다 재생되면 자동으로 사라짐
사운드 위치: 왜 `Camera.main` 위치에서 재생하나요?
Unity에서 소리는 3D 공간에서 "어디서 들리는가" 도 중요해요.
소리를 동전 위치에서 재생하면 카메라와 멀면 소리가 작게 들릴 수도 있어요.
그래서 카메라 위치에서 소리를 재생하면 플레이어가 항상 잘 들을 수 있어요. (특히 2D 게임에서는!)
1. Unity 에디터로 돌아가서 Prefabs > Coin 프리팹 선택
2. CoinPickup 스크립트가 붙어있으면 Coin Pickup SFX 칸이 보일 거예요
3. 거기에 Audio 폴더의 오디오 클립(CoinPickup.wav) 을 드래그해서 넣기
지금은 PlayClipAtPoint()로 소리를 간단히 재생했지만,
게임이 커지고 효과음(SFX), 배경음(BGM), UI 사운드 등이 많아지면
오디오 매니저(AudioManager) 를 만들어서 중앙에서 일괄 관리하는 게 훨씬 좋습니다.
게임 전체의 모든 소리를 통합해서
한 곳에서 재생, 정지, 볼륨 조절 등을 처리해주는 스크립트입니다.
게임 전반에 공통적으로 쓰이는 오디오 기능을 제공
BGM과 SFX를 구분해서 관리
씬이 바뀌어도 유지되게 만들 수 있음 (DontDestroyOnLoad)
Assets/
└── Scripts/
└── AudioManager.cs
└── Audio/
├── bgmTheme.wav
├── coinPickup.wav
└── buttonClick.wav
using UnityEngine;
public class AudioManager : MonoBehaviour
{
public static AudioManager instance;
[Header("오디오 소스")]
public AudioSource bgmSource;
public AudioSource sfxSource;
[Header("효과음들")]
public AudioClip coinPickup;
public AudioClip buttonClick;
void Awake()
{
// 싱글톤 패턴
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject); // 씬이 바뀌어도 유지됨
}
else
{
Destroy(gameObject); // 이미 있으면 새로 만든 건 제거
}
}
public void PlaySFX(AudioClip clip)
{
sfxSource.PlayOneShot(clip);
}
public void PlayCoinSound()
{
PlaySFX(coinPickup);
}
public void PlayClickSound()
{
PlaySFX(buttonClick);
}
public void PlayBGM(AudioClip bgmClip)
{
bgmSource.clip = bgmClip;
bgmSource.loop = true;
bgmSource.Play();
}
public void StopBGM()
{
bgmSource.Stop();
}
}
AudioManager.instance.PlayCoinSound();
AudioManager.instance.PlayClickSound();
이 강의는 유니티(Unity)에서 2D 게임을 개발하며 **목숨(Lives)**과 점수(Score) 시스템을 추가하는 과정을 다룹니다.
강의는 초보자를 대상으로 하며, 캔버스 설정, UI 텍스트 생성, 폰트 적용, 스크립트 작성, 싱글턴 패턴,
그리고 동전 줍기 기능까지 상세히 설명합니다.
1.1 캔버스 생성
작업: 유니티에서 UI 요소를 표시하기 위해 **캔버스(Canvas)**를 생성합니다.
방법:
1. Hierarchy 창에서 마우스 우클릭 → UI → Canvas 선택.
2. 캔버스가 자동으로 생성되며, 기본적으로 Canvas라는 이름으로 추가됩니다.
설명: 캔버스는 UI 요소(텍스트, 버튼, 이미지 등)를 화면에 렌더링하기 위한 컨테이너입니다.
모든 UI 요소는 캔버스 안에 있어야 화면에 표시됩니다.
1.2 캔버스 스케일러 설정
작업: 캔버스의 크기가 다양한 화면 해상도에 맞게 조정되도록 설정합니다.
방법:
1. Hierarchy에서 Canvas를 선택.
2. Inspector 창에서 Canvas Scaler 컴포넌트를 찾습니다.
3. UI Scale Mode를 Constant Pixel Size에서 Scale With Screen Size로 변경.
4. Reference Resolution은 강의에서는 1920x1080으로 설정했지만, 사용자의 모니터 해상도에 맞게 설정하면 됩니다.
설명:
Scale With Screen Size는 캔버스의 UI 요소 크기가 화면 해상도에 비례해 조정되도록 합니다.
예를 들어, 1920x1080 해상도에서 UI가 잘 보이도록 설정하면,
다른 해상도(예: 1280x720)에서도 비슷한 비율로 표시됩니다.
Reference Resolution은 기준 해상도로, 이 해상도에 맞춰 UI가 최적화됩니다.
1.3 캔버스 설정 팁
강의에서는 강사의 선호 해상도(1920x1080)를 언급했지만,
이는 필수 사항이 아니며 사용자가 원하는 해상도로 설정해도 됩니다.
캔버스 설정은 게임의 UI가 모든 디바이스에서 일관되게 보이도록 만드는 중요한 단계입니다.
2.1 TextMeshPro 텍스트 생성
작업: 목숨(Lives)을 표시할 텍스트 UI를 생성합니다.
방법:
1. Hierarchy에서 Canvas를 선택하고 우클릭 → UI → Text - TextMeshPro 선택.
2. 새로운 텍스트 오브젝트가 캔버스 하위에 생성되며, 기본 이름은 **Text (TMP)**입니다.
설명:
TextMeshPro는 유니티의 기본 텍스트보다 더 강력한 텍스트 렌더링 시스템입니다.
폰트 스타일, 그림자, 윤곽선 등을 더 쉽게 커스터마이징할 수 있습니다.
기본 텍스트(UI Text)도 사용할 수 있지만, TextMeshPro가 더 현대적이고 유연합니다.
2.2 TextMeshPro 필수 패키지 임포트
작업: TextMeshPro를 사용하기 위해 필수 패키지를 임포트합니다.
방법:
1. TextMeshPro를 처음 사용할 때, 유니티가 TMP Essential Resources를 임포트하라는 메시지를 표시합니다.
2. Import TMP Essential Resources를 클릭하여 필수 리소스를 가져옵니다.
3. 추가 예제(Examples) 임포트는 필요 없으므로 무시합니다.
설명:
TMP Essential Resources는 TextMeshPro가 제대로 작동하기 위해 필요한 기본 폰트,
셰이더, 템플릿 등을 포함합니다.
문제가 생길 경우, 이 리소스를 다시 확인하거나 재임포트해야 할 수 있습니다.
2.3 텍스트 이름 변경 및 위치 조정
작업: 생성한 텍스트의 이름을 Lives Text로 변경하고 위치를 조정합니다.
방법:
1. Hierarchy에서 **Text (TMP)**를 선택하고 이름을 Lives Text로 변경.
2. Rect Transform에서 **Anchor(앵커)**를 왼쪽 상단으로 설정하여 텍스트가 화면 왼쪽 상단에 고정되도록 합니다.
Anchor Presets에서 왼쪽 상단 아이콘을 선택하고, Alt 키를 눌러 위치도 맞춤.
3. 텍스트 크기를 조정:
Rect Transform의 Width와 Height를 약 200으로 설정.
Text Input Box에 99를 입력하여 최대 목숨 수를 표시할 공간을 확보.
설명:
Anchor는 UI 요소가 화면의 어느 위치에 고정될지를 결정합니다.
왼쪽 상단 앵커를 설정하면 해상도가 바뀌어도 텍스트가 항상 왼쪽 상단에 위치합니다.
99는 최대 목숨 수를 상정하여 충분한 공간을 확보하기 위한 값입니다.
실제 게임에서는 9개 이하로 제한될 가능성이 높지만, 여유를 두는 것이 좋습니다.
2.4 폰트 다운로드 및 적용
작업: 게임에 사용할 픽셀 스타일 폰트를 다운로드하고 적용합니다.
방법:
1. 폰트 다운로드:
dafont.com에서 픽셀(pixel) 스타일 폰트를 검색.
강의에서는 Symtext 폰트를 선택하여 다운로드.
다운로드한 폰트 파일(.ttf 또는 .otf)을 압축 해제.
2. 유니티에 폰트 추가:
Project 창에서 Assets 폴더 내에 새 폴더를 만들고 이름을 Fonts로 지정.
다운로드한 폰트 파일을 Fonts 폴더로 드래그하여 추가.
3. Font Asset 생성:
메뉴에서 Window → TextMeshPro → Font Asset Creator 선택.
Font Asset Creator 창에서:
Source Font File에 다운로드한 폰트를 지정.
Generate Font Atlas를 클릭하여 폰트를 변환.
생성된 폰트 에셋을 Fonts 폴더에 저장.
4. 폰트 적용:
Hierarchy에서 Lives Text를 선택.
Inspector의 TextMeshPro - Text (UI) 컴포넌트에서 Font Asset을 방금 생성한 폰트로 변경.
5. 폰트 크기 조정:
텍스트 크기가 너무 크다고 판단되면 Rect Transform의 Width와 Height를 150으로 줄임.
폰트의 상단 여백이 넓어 보일 수 있지만, 화면 상단에 잘 맞도록 조정.
설명:
dafont.com은 무료 폰트를 제공하는 인기 사이트로, 게임에 적합한 픽셀 폰트를 쉽게 찾을 수 있습니다.
Font Asset Creator는 TrueType 또는 OpenType 폰트를 TextMeshPro에서 사용할 수 있는 형태로 변환합니다.
폰트 적용 후 텍스트가 더 픽셀화된 느낌으로 표시되며, 게임의 레트로 스타일에 잘 맞습니다.
앵커를 다시 확인하여 텍스트가 화면 왼쪽 상단에 고정되도록 합니다.
텍스트 다운로드 사이트
https://www.dafont.com/symtext.font
3.1 Lives Text 복제
작업: Lives Text를 복제하여 점수(Score)를 표시할 텍스트를 만듭니다.
방법:
1. Hierarchy에서 Lives Text를 선택하고 Ctrl + D (또는 우클릭 → Duplicate)를 눌러 복제.
2. 복제된 텍스트의 이름을 Score Text로 변경.
설명: 복제를 통해 동일한 설정(폰트, 크기, 앵커 등)을 유지하면서 새로운 텍스트를 빠르게 생성할 수 있습니다.
3.2 Score Text 위치 및 크기 조정
작업: Score Text를 화면 오른쪽 상단으로 이동하고 크기를 조정합니다.
방법:
1. Rect Transform에서 Anchor를 오른쪽 상단으로 설정.
2. 텍스트 박스의 Width를 늘려 더 많은 숫자를 표시할 수 있도록 조정.
3. Text Input Box에 999999 (6자리)를 입력하여 최대 점수 표시 공간을 확인.
4. 텍스트가 화면에 잘 맞는지 확인하고, 필요하면 크기를 미세 조정.
설명:
점수는 최대 6자리(999999)까지 표시할 수 있도록 설정하여 높은 점수도 화면에 잘 나타나도록 합니다.
오른쪽 상단 앵커를 설정하면 Lives Text와 대칭을 이루며 깔끔한 UI 레이아웃이 완성됩니다.
3.3 테스트
작업: 플레이 버튼을 눌러 UI가 제대로 표시되는지 확인합니다.
방법:
1. 유니티 상단의 Play 버튼을 클릭.
2. 게임 화면에서 Lives Text와 Score Text가 의도한 위치(왼쪽 상단, 오른쪽 상단)에 표시되는지 확인.
결과:
텍스트가 올바르게 표시되며, Lives Text에는 99, Score Text에는 999999가 나타남.
UI가 예상대로 작동하는지 확인하고 만족스러운 상태임을 확인.
텍스트 앵커 설정
복제한 TEXT
4.1 문제 발견
문제: TextMeshPro를 사용하면서 EventSystem과 새로운 입력 시스템 간 충돌이 발생.
증상: 콘솔에 경고나 오류 메시지가 나타남.
설명: 유니티의 새로운 입력 시스템(Input System)과 TextMeshPro가 기존
EventSystem과 제대로 호환되지 않아 발생하는 문제입니다.
4.2 해결 방법
작업: EventSystem을 새로운 입력 시스템 모듈로 대체합니다.
방법:
1. Hierarchy에서 EventSystem 오브젝트를 선택.
2. Inspector에서 Replace with InputSystemUIInputModule 버튼을 클릭.
3. 콘솔 창에서 Clear 버튼을 눌러 오류 메시지를 정리.
설명:
InputSystemUIInputModule은 유니티의 새로운 입력 시스템을 지원하는 모듈로, TextMeshPro와 더 잘 호환됩니다.
이 작업으로 충돌 문제가 해결되며, 콘솔에 오류가 사라짐.
4.3 테스트
작업: 다시 플레이 버튼을 눌러 오류가 해결되었는지 확인.
결과: 오류가 사라지고 UI가 정상적으로 작동함.
이벤트 시스템의 오류 해결
5.1 GameSession 스크립트 개요
목표: GameSession 스크립트를 수정하여 목숨과 점수를 관리하고, 화면에 표시하도록 합니다.
설명:
GameSession은 게임의 전반적인 상태(목숨, 점수 등)를 관리하는 스크립트입니다.
싱글턴 패턴을 사용하여 게임 세션이 하나만 존재하도록 보장합니다.
5.2 변수 추가
작업: Lives Text와 Score Text를 참조할 변수를 추가합니다.
코드:
[SerializeField] TextMeshProUGUI livesText;
[SerializeField] TextMeshProUGUI scoreText;
설명:
TextMeshProUGUI는 TextMeshPro의 UI 텍스트를 참조하는 타입입니다.
**[SerializeField]**를 사용하여 유니티 에디터에서 이 변수를 인스펙터에 노출시킵니다.
livesText는 Lives Text UI를, scoreText는 Score Text UI를 참조합니다.
5.3 네임스페이스 추가
문제: TextMeshProUGUI를 사용하려면 TMPro 네임스페이스가 필요합니다.
작업: 스크립트 상단에 네임스페이스를 추가.
코드:
using TMPro;
설명: TMPro 네임스페이스를 추가하면 TextMeshProUGUI 타입을 사용할 수 있습니다.
5.4 점수 변수 추가
작업: 점수를 저장할 변수를 추가.
코드:
[SerializeField] int score = 0;
설명:
score는 현재 점수를 저장하며, 초기값은 0으로 설정.
**[SerializeField]**를 사용하여 인스펙터에서 초기값을 수정할 수 있도록 함.
5.5 Start 메서드 수정
작업: 게임 시작 시 Lives Text와 Score Text를 초기화합니다.
코드:
void Start()
{
livesText.text = playerLives.ToString();
scoreText.text = score.ToString();
}
설명:
livesText.text와 scoreText.text는 각각 Lives Text와 Score Text의 표시 내용을 설정합니다.
playerLives와 score는 정수형 변수이므로, ToString() 메서드를 사용하여 문자열로 변환하여 텍스트에 할당합니다.
이로써 게임 시작 시 목숨(3)과 점수(0)가 화면에 표시됩니다.
5.6 TakeLife 메서드 수정
작업: 플레이어가 목숨을 잃을 때 화면에 반영되도록 수정.
코드:
void TakeLife()
{
playerLives--;
int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
livesText.text = playerLives.ToString();
SceneManager.LoadScene(currentSceneIndex);
}
설명:
**playerLives--**로 목숨을 1 감소.
**livesText.text = playerLives.ToString()**로 감소된 목숨 수를 화면에 업데이트.
현재 장면을 다시 로드하여 플레이어를 시작 위치로 되돌립니다.
5.7 AddToScore 메서드 추가
작업: 점수를 추가하는 메서드를 작성.
코드:
public void AddToScore(int pointsToAdd)
{
score += pointsToAdd;
scoreText.text = score.ToString();
}
설명:
pointsToAdd는 추가할 점수 값(예: 동전 하나당 100점).
score += pointsToAdd로 점수를 증가.
**scoreText.text = score.ToString()**로 증가된 점수를 화면에 업데이트.
5.8 유니티 에디터에서 변수 연결
작업: GameSession 오브젝트에 Lives Text와 Score Text를 연결.
방법:
1. Hierarchy에서 GameSession 오브젝트를 선택.
2. Inspector에서 GameSession 컴포넌트의 Lives Text 필드에 Lives Text 오브젝트를 드래그하여 할당.
3. Score Text 필드에 Score Text 오브젝트를 드래그하여 할당.
설명: 이로써 스크립트가 UI 텍스트를 참조할 수 있게 됩니다.
5.9 테스트
작업: 플레이 버튼을 눌러 목숨과 점수가 제대로 표시되는지 확인.
결과:
Lives Text에 3이 표시됨.
Score Text에 0이 표시됨.
하지만 플레이어가 죽을 때 목숨이 줄어들지 않고 99로 유지되는 문제가 발생.
6.1 문제 원인
문제: GameSession은 싱글턴 패턴으로 설정되어 장면 간 데이터를 유지하지만,
Canvas와 그 하위 UI(Lives Text, Score Text)는 싱글턴이 아니므로 장면이 재로드될 때 초기화됨.
결과: 목숨이 감소하지 않고 초기값(99)으로 표시됨.
6.2 해결 방법
작업: Canvas를 GameSession의 자식으로 만들어 싱글턴의 DontDestroyOnLoad 효과를 상속받도록 합니다.
방법:
1. Hierarchy에서 Canvas를 GameSession 오브젝트 아래로 드래그하여 자식으로 설정.
2. GameSession에 DontDestroyOnLoad가 적용되어 있으므로, Canvas와 그 하위 UI도 장면 전환 시 유지됨.
3. Prefab에 변경 사항을 적용:
4. GameSession을 선택하고 Overrides → Apply All을 클릭하여 프리팹에 변경 사항을 저장.
설명:
DontDestroyOnLoad는 오브젝트가 장면 전환 시 파괴되지 않도록 유지합니다.
Canvas를 GameSession의 자식으로 설정하면,
Canvas와 Lives Text, Score Text도 함께 유지되어 UI가 초기화되지 않습니다.
6.3 테스트
작업: 플레이 버튼을 눌러 목숨 감소가 제대로 작동하는지 확인.
결과:
플레이어가 죽을 때 목숨이 3 → 2 → 1로 감소하고 화면에 올바르게 표시됨.
목숨이 0이 되면 게임이 재시작되며, 목숨이 다시 3으로 초기화됨.
Canvas 를 GameSession 의 자식으로 넣어주기
7.1 CoinPickup 스크립트 수정
목표: 플레이어가 동전을 주울 때 점수가 증가하도록 구현.
작업: CoinPickup 스크립트에 점수 추가 로직을 추가.
코드:
[SerializeField] int pointsForCoinPickup = 100;
bool wasCollected = false;
void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "Player" && !wasCollected) {
wasCollected = true;
FindObjectOfType<GameSession>().AddToScore(pointsForCoinPickup);
AudioSource.PlayClipAtPoint(coinPickupSFX, Camera.main.transform.position);
Destroy(gameObject);
}
}
설명:
pointsForCoinPickup: 동전 하나당 추가할 점수(기본값 100).
wasCollected: 동전이 이미 주워졌는지 확인하는 플래그. 초기값은 false.
OnTriggerEnter2D:
플레이어(태그가 "Player")가 동전에 닿고, 동전이 아직 주워지지 않았다면(!wasCollected):
wasCollected = true로 설정하여 중복 주움을 방지.
**FindObjectOfType<gamesession>().AddToScore(pointsForCoinPickup)**로
GameSession의 AddToScore 메서드를 호출하여 점수를 증가.</gamesession>
동전 줍기 효과음(coinPickupSFX)을 재생.
동전 오브젝트를 Destroy로 제거.
7.2 동전 오브젝트 추가
작업: 테스트를 위해 레벨에 동전을 추가.
방법:
1. Hierarchy에서 동전 프리팹을 추가(예: Coin 오브젝트).
2. 동전을 플레이어가 쉽게 주울 수 있는 위치에 배치(예: 플레이어 경로 상에 1~2개).
설명: 동전은 CoinPickup 스크립트가 붙어 있어야 하며, 플레이어가 닿으면 점수가 증가하고 동전이 사라집니다.
7.3 테스트
작업: 플레이 버튼을 눌러 동전 줍기 기능이 작동하는지 확인.
결과:
게임 시작 시 점수가 0으로 표시.
동전을 주울 때마다 점수가 100씩 증가(예: 0 → 100 → 200).
동전 줍기 효과음이 재생되고, 동전이 화면에서 사라짐.
이 강의에서는 Unity에서 ScenePersist(장면 영속) 시스템을 구현하는 과정을 통해,
게임 오브젝트의 상태를 씬 전환이나 플레이어의 죽음 후에도 유지하는 방법을 배웁니다.
적군이나 동전을 한 번 제거(획득)하면 다시 나타나지 않도록 한다.
단, 플레이어가 죽고 레벨이 리셋되면 다시 등장하도록 한다.
다음 레벨로 넘어가면 기존의 ScenePersist 오브젝트는 파괴되어야 한다.
1-1. Hierarchy에서 빈 오브젝트 생성
Hierarchy 우클릭 → Create Empty
이름: ScenePersist
Transform 초기화: Reset
1-2. ScenePersist 스크립트 만들기
Scripts 폴더에서 우클릭 → Create > C# Script
이름: ScenePersist
빈 오브젝트에 이 스크립트를 붙여준다.
GameSession처럼 ScenePersist도 하나만 유지되고 파괴되지 않도록 만드는 싱글턴(Singleton) 패턴을 쓴다.
이렇게 하면:
플레이어가 죽더라도 동전과 적군 상태가 유지됨
하지만 레벨이 바뀌면 새 ScenePersist로 대체됨
public class ScenePersist : MonoBehaviour
{
// 게임이 시작되거나 오브젝트가 생성될 때 가장 먼저 실행되는 함수입니다.
void Awake()
{
// 현재 씬에 존재하는 ScenePersist 오브젝트의 수를 셉니다.
int numScenePersist = FindObjectsOfType<ScenePersist>().Length;
// 만약 ScenePersist가 2개 이상이라면(즉, 중복된 경우)
if (numScenePersist > 1)
{
// 이 오브젝트는 삭제합니다. (중복을 방지하기 위해)
Destroy(gameObject);
}
else
{
// 이 오브젝트는 씬이 바뀌어도 파괴되지 않도록 설정합니다.
DontDestroyOnLoad(gameObject);
}
}
// 다른 스크립트에서 이 오브젝트를 제거하고 싶을 때 호출할 수 있는 함수입니다.
public void ResetScenePersist()
{
// 이 ScenePersist 오브젝트를 삭제합니다.
Destroy(gameObject);
}
}
4-1. Pickups 그룹 만들기
빈 오브젝트 생성 → 이름: Pickups
Transform 초기화
동전들(Coin)을 모두 Pickups 아래로 넣기
4-2. Enemies 그룹 만들기
빈 오브젝트 생성 → 이름: Enemies
Transform 초기화
적군(예: Goober)들을 모두 Enemies 아래로 넣기
4-3. ScenePersist 아래로 이동
Pickups, Enemies를 모두 ScenePersist 아래로 넣는다
Project > Prefabs 폴더로 ScenePersist 오브젝트를 끌어다 놓기
각 레벨마다 이 프리팹을 사용해서 ScenePersist 배치
6-1. Level 2, 3에도 ScenePersist 배치
각 씬에 ScenePersist 프리팹을 배치하고
그 안에 Pickups, Enemies를 구성해서 넣기( 에너미와 픽업은 프리팹 오버라이드 하지말자 )
// 이 메서드는 외부에서 ScenePersist 오브젝트를 삭제하고 싶을 때 사용됩니다.
public void ResetScenePersist()
{
// 이 스크립트가 붙어있는 오브젝트 자체를 삭제합니다.
Destroy(gameObject);
}
ResetScenePersist()는 다른 스크립트에서 ScenePersist를 제거할 수 있게 해주는 **공개 함수(public method)**입니다.
void ResetGameSession()
{
// 현재 씬에서 ScenePersist 타입을 가진 오브젝트를 찾아서 삭제하는 메서드를 호출합니다.
FindObjectOfType<ScenePersist>().ResetScenePersist();
// 게임을 처음 상태로 되돌리기 위해 첫 번째 씬(index 0)을 다시 로드합니다.
SceneManager.LoadScene(0);
// GameSession 오브젝트 자체도 삭제합니다. (새 씬에서 새로 생성되도록)
Destroy(gameObject);
}
이 함수는 게임을 초기화(리셋) 할 때 사용됩니다.
ScenePersist와 GameSession 오브젝트 둘 다 삭제한 뒤, 첫 번째 씬을 다시 로드합니다.
따라서 완전한 게임 재시작을 구현합니다.
IEnumerator LoadNextLevel()
{
// levelLoadDelay(몇 초) 만큼 기다렸다가 실행됩니다.
yield return new WaitForSeconds(levelLoadDelay);
// 현재 씬의 인덱스를 가져옵니다.
int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
// 다음 씬의 인덱스를 계산합니다.
int nextSceneIndex = currentSceneIndex + 1;
// 만약 현재가 마지막 씬이면, 다시 첫 씬(0번)으로 돌아가도록 처리합니다.
if (nextSceneIndex == SceneManager.sceneCountInBuildSettings)
{
nextSceneIndex = 0;
}
// 씬이 바뀌기 전에 ScenePersist 오브젝트를 삭제합니다.
FindObjectOfType<ScenePersist>().ResetScenePersist();
// 계산한 씬 인덱스를 기반으로 다음 씬을 로드합니다.
SceneManager.LoadScene(nextSceneIndex);
}
이 코루틴은 플레이어가 레벨 출구에 도달했을 때 다음 씬을 로드하는 기능입니다.
다음 씬으로 넘어가기 전에 ScenePersist 오브젝트를 삭제해서,
다음 씬에서 새로운 ScenePersist가 생성될 수 있게 합니다.
만약 현재 씬이 마지막이라면, 다시 첫 씬으로 돌아갑니다. (무한 루프 구조)
**프리팹(Prefab)**은 Unity에서 자주 사용하는 게임 오브젝트(예: 동전, 적, 아이템 등)를 템플릿으로 만들어,
여러 장면이나 게임 요소에서 재사용할 수 있도록 하는 기능입니다.
한 번 만든 프리팹을 복사해서 여러 곳에 배치하고,
원본 프리팹을 수정하면 **모든 복사본(인스턴스)**도 같이 수정됩니다.
✅ 예: 동전 프리팹을 만들고 장면(Scene)에 10개를 배치했다면, 원본을 수정하면 10개 모두 반영됨.
프리팹 변형이란, **기존 프리팹을 기반으로 새로운 버전(변형)**을 만드는 것입니다.
이 변형은 원본과 연결된 상태로 유지되며, 원본의 변경 사항을 상속하지만,
변형에서 수정한 부분은 개별적으로 다르게 유지됩니다.
✅ 예: 원본 동전은 금색이고 크기 1인데, 변형에서는 크기를 2로 키운 “Big Coin”을 만들 수 있어요.
1. 원본 동전 프리팹이 있다고 가정.
2. 장면(Scene)에 하나를 끌어다 놓고, Scale 값을 2, 2, 2로 변경해 크기를 키움.
3. 변경한 동전을 다시 Prefabs 폴더에 드래그해서 놓음.
4. 메뉴가 뜨면 "Prefab Variant 만들기"를 선택.
5. 새 프리팹에 이름을 줌 → 예: Big Coin Variant.
예시: 원본 Coin 프리팹의 사운드 볼륨을 1 → 0.5로 낮추면,
Big Coin Variant도 같이 볼륨이 낮아집니다.
왜냐하면 사운드는 변형에서 수정하지 않았기 때문이에요.
✅ 즉, 원본에서 수정한 값은 변형에도 반영됩니다.
❌ 단, 변형 쪽에서 이미 해당 값을 수정한 경우는 제외됩니다. (그 값은 고유한 override 값이 됨)