김산나
[멋쟁이사자처럼부트캠프 유니티 게임 개발 7기] 2026년 1월 26일 회고록 - 1945 몬스터 스포너, 스킬, 시네머신 본문
[멋쟁이사자처럼부트캠프 유니티 게임 개발 7기] 2026년 1월 26일 회고록 - 1945 몬스터 스포너, 스킬, 시네머신
김산나 2026. 1. 26. 19:222026_01_26 강의 요약본
오늘의 구현 목표
- 몬스터 스포너
- 폭탄(스킬)
- 한글 폰트
- 색상 변경
- 시네머신 카메라 (feat. Package Signature invalid 오류)
1. 몬스터 스포너(SpawnManager)
선생님께서 가르쳐주신 기본 코드는 다음과 같다
- Coroutine (StartCoroutine, StopCoroutine) IEnumerator 속성
패턴 기획
- 몬스터는 기본, 강화, 보스 세 종류가 나온다고 가정.
- 기본은 게임 시작 0.5초부터 1초마다 임의의 위치에 스폰
- 강화는 게임 시작 10초부터 4초마다 임의의 위치에 스폰
- 보스는 게임 시작 30초부터 한 번 중앙에서 등장.
void Start()
{
StartCoroutine("몬스터 1 스폰 코드");
Invoke("몬스터 1 스폰 정지 코드", 몬스터 1 스폰 정지 시간);
}
void 몬스터 1 스폰 정지 코드()
{
몬스터 1 반복 = false;
StopCoroutine("몬스터 1 스폰 코드");
//두번째 몬스터 코루틴
StartCoroutine("몬스터 2 스폰 코드");
Invoke("몬스터 2 스폰 정지 코드", 몬스터 2 스폰 정지 시간);
}
이런 식으로 들어간다
Start함수에서 시작을 끊어주고, 스폰 정지 코드에서 다음 함수를 실행해 주는 것이다.
using System.Collections;
using UnityEngine;
public class Spawn : MonoBehaviour
{
[Header("생성 범위")]
public float ss = -2f; //몬스터 생성 x값 좌표 처음
public float es = 2; //몬스터 생성 x값 끝
[Header("생성 타이머")]
public float m1Spawn = 1f;
public float m1stop = 6f;
public float m2Spawn = 4f;
public float m2stop = 17f;
public float mbSpawn = 3f;
public float mbstop = 4f;
[Header("몬스터")]
public GameObject monster1;
public GameObject monster2;
public GameObject monsterBoss;
// 몬스터 스폰 제어
bool m1 = true;
bool m2 = true;
bool mb = true;
[Header("경고문")]
[SerializeField]
GameObject textBossWarining;
private void Awake()
{
textBossWarining.SetActive(false);
}
void Start()
{
StartCoroutine("SpwanM1");
Invoke("StopSpwanM1", m1stop);
}
//코루틴으로 랜덤하게 생성하기
IEnumerator SpwanM1()
{
while(m1)
{
//1초 마다
yield return new WaitForSeconds(m1Spawn);
//x값 랜덤
float x = Random.Range(ss, es);
//x값은 랜덤 y값은 자기자긴값
Vector2 r = new Vector2(x, transform.position.y);
//몬스터 생성
Instantiate(monster1, r, Quaternion.identity);
}
}
void StopSpwanM1()
{
m1 = false;
StopCoroutine("RandomSpawn");
//두번째 몬스터 코루틴
StartCoroutine("SpwanM2");
Invoke("StopSpwanM2", m2stop);
}
//코루틴으로 랜덤하게 생성하기
IEnumerator SpwanM2()
{
while (m2)
{
//3초 마다
yield return new WaitForSeconds(m2Spawn);
//x값 랜덤
float x = Random.Range(ss, es);
//x값은 랜덤 y값은 자기자긴값
Vector2 r = new Vector2(x, transform.position.y);
//몬스터 생성
Instantiate(monster2, r, Quaternion.identity);
}
}
void StopSpwanM2()
{
m2 = false;
StopCoroutine("RandomSpawn2");
//보스
StartCoroutine("SpwanMB");
Invoke("StopSpwanMB", mbstop);
textBossWarining.SetActive(true);
}
IEnumerator SpwanMB()
{
while (mb)
{
// 4초 후에 생성
yield return new WaitForSeconds(mbSpawn);
//몬스터 생성
Instantiate(monsterBoss, transform.position, Quaternion.identity);
}
}
void StopSpwanMB()
{
mb = false;
StopCoroutine("SpwanMB");
textBossWarining.SetActive(false);
}
}
시작 -> 1초에 한 번씩 M1생성 -> 10초 지속 -> M1스폰 종료, 4초 에 한 번M2 생성 -> 17초 후 M2생성 종료, 3초 후 보스 생성 및 경고 이펙on -> 4초 후 보스 생성 종료 및 경고 이펙트 off
2. 폭탄 (스킬)
- 충돌 범위 내 오브젝트 리스트
- 중복 확인
1) 충돌 범위 내 오브젝트 리스트화
Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, explosionRadius, hitLayers);
collider 2D속성의 배열을 생성한다.
Physics2D에서 (OverlapCircleAll)원 범위 안에 있는 녀석들을 다 잡아넣겠다.
(중심점, 반지름, 레이어)
이런 식으로 구성되어있다.
2) 중복 확인 - HashSet
List인데 중복이면 안 넣는 List이다.
이거로 중복을 걸러낼 수 있다.
<Moster>클래스의 오브젝트의 HashSet을 생성한다.
그리고 그 HashSet리스트를 foreach문을 돌려서 모든 파일을 검사하여 hits(원 범위 안에 있는 객체)에 해당하면
그 객체를 가져온다.
조건은 실제로 그 객체가 존재하는가? and 그 객체가 damaged에 포함되지 않는가?(데미지 적용 완)
조건에 맞는 오브젝트에 데미지를 적용하고, 완료된 오브젝트를 damaged HashSet에 넣어 기록한다.
using System.Collections.Generic;
using UnityEngine;
public class Bomb : MonoBehaviour
{
[SerializeField] private float explosionRadius = 1.5f;
[SerializeField] private int damage = 50;
[SerializeField] private LayerMask hitLayers = Physics2D.DefaultRaycastLayers;
private void Start()
{
// 트리거에 진입하면 폭발 반경 내 모든 콜라이더를 검사하여 몬스터에게 데미지를 줍니다.
// OverlapCircleAll을 사용하여 폭발 반경 내의 모든 콜라이더를 가져옵니다.
Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, explosionRadius, hitLayers);
// 중복된 몬스터에 대한 데미지 적용을 방지하기 위해 HashSet 사용
var damaged = new HashSet<Monster>();
// 충돌한 모든 콜라이더를 순회
foreach (var hit in hits)
{
// null 체크
if (hit == null) continue;
// 몬스터 컴포넌트를 가져와서 데미지 적용
Monster monster = hit.GetComponent<Monster>();
// 몬스터가 존재하고 아직 데미지를 받지 않은 경우에만 데미지 적용
if (monster != null && !damaged.Contains(monster))
{
// 몬스터에게 데미지 적용
monster.Damage(damage);
// 데미지를 받은 몬스터를 HashSet에 추가
damaged.Add(monster);
}
}
// 폭탄 오브젝트 제거 (원하면 폭발 이펙트 재생 후 제거)
Destroy(gameObject, 2);
}
// 폭발 반경을 시각화하기 위한 Gizmos 그리기
private void OnDrawGizmosSelected()
{
// 폭발 반경을 반투명 오렌지색 구체로 표시
Gizmos.color = new Color(1f, 0.5f, 0f, 0.5f);
//기즈모 구형태로 그리기 포지션, 반지름
Gizmos.DrawSphere(transform.position, explosionRadius);
}
}
* 참고
- Gizmos.color = new Color(1f, 0.5f, 0f, 0.5f); - 색상 설정
- Gizmos.DrawSphere(transform.position, explosionRadius); - 구 그리기
구체로 범위를 표시하는 것이다. Scene에서만 보임.
3. 한글 폰트 설정 (Text Mesh Pro 2편)
https://www.notion.so/1b929e4eaef280c980b5e5be02d5b27f
텍스트메쉬프로설정 | Notion
예전부터 사용해왔던 UI의 Text는 이제 Legacy로 빠진 상태입니다.
www.notion.so
자료 참조.
Text Mesh Pro의 한글 폰트 제작 방법이다.
그냥 작성하면 한글은 깨진다.
4. 폰트 색상 변경 (코드로)
using System.Collections;
using TMPro;
using UnityEngine;
public class TMPColor : MonoBehaviour
{
//색상 전환에 걸리는 시간
[SerializeField]
float lerpTime = 0.1f;
//텍스트 컴포넌트
TextMeshProUGUI textBossWarning;
//Awake 메서드 : 컴포넌트 초기화
private void Awake()
{
textBossWarning = GetComponent<TextMeshProUGUI>();
}
//OnEnable메서드 : 오브젝트가 활성화될때 호출
private void OnEnable()
{
StartCoroutine("ColorLerpLoop");
}
//색상 전환 루프 코루틴
IEnumerator ColorLerpLoop()
{
while(true)
{
yield return StartCoroutine(ColorLerp(Color.white, Color.red));
yield return StartCoroutine(ColorLerp(Color.red, Color.white));
}
}
//색상 전환 코루틴
IEnumerator ColorLerp(Color startColor, Color endColor)
{
float currentTime = 0.0f;
float percent = 0.0f;
while(percent < 1)
{
currentTime += Time.deltaTime;
percent = currentTime / lerpTime;
textBossWarning.color = Color.Lerp(startColor, endColor, percent);
//textBossWarning.faceColor = Color.Lerp(startColor, endColor, percent);
yield return null;
}
}
}
코드로 폰트 색상을 변경하는 방법이다.
"textBossWarning.color"이 폰트 색상이다. 이 색상을 Lerp함수를 사용하면 startColor / endColor 로 변경할 수 있다.
저 루프를 빠르게 돌리면 부드럽게 색이 흐르게 된다.
TextMeshProUGUI textBossWarning;
private void Awake()
{
textBossWarning = GetComponent<TextMeshProUGUI>();
}
IEnumerator ColorLerp(Color startColor, Color endColor)
{
float currentTime = 0.0f;
float percent = 0.0f;
while(percent < 1)
{
currentTime += Time.deltaTime;
percent = currentTime / lerpTime;
textBossWarning.color = Color.Lerp(startColor, endColor, percent);
//textBossWarning.faceColor = Color.Lerp(startColor, endColor, percent);
yield return null;
}
}
5. Cinemachine
Cinemachine은 카메라 무빙을 좀 더 쉽게?사용할 수 있는 패키지이다.

이거 말고도 포함된 컴포넌트가 다수 있다.
그 중에서 Impulse Source라는 컴포넌트가 있는데, 말 그대로 충격을 주는 컴포넌트이다. 설정을 이것저것 만져서
코드로 실행시키면 맞게 움직인다.

그냥 쌩 코드로 움직일 수도 있다.
다음 코드를 보자.
(CinemachineCamera 오브젝트에 부착)
using Unity.Cinemachine;
using UnityEngine;
public class CameraImpulse : MonoBehaviour
{
public static CameraImpulse Instance;
[SerializeField]
CinemachineImpulseSource impulse;
// 싱글톤
void Awake()
{
if(Instance != null)
{
Destroy(gameObject);
return;
}
Instance = this;
}
// 카메라 흔들기
public void CameraShakeShow()
{
impulse.GenerateImpulse();
}
}
impulse.GenerateImpulse(); <- 이게 설정대로 움직여라임
(보스 or 스포너에 부착)
IEnumerator Shake()
{
int shakeCnt = 30;
while(shakeCnt > 0)
{
CameraImpulse.Instance.CameraShakeShow();
yield return new WaitForSeconds(0.1f);
shakeCnt--;
}
}
이건 30번 흔들어재끼라는 코드.
최근 6.3버전부터 패키지에 인증 서명을 받는 시스템을 운영하기 시작했는데, 덕분에 이곳저곳에서 패키지가 먹통이 되고 있다.
마음 편하게 먹고 그냥 다른 버전을 쓰거나 패치를 기다리자.
RIP
===========================================================
스포너를 제작할 수 있게 되었다 !
범위 내 오브젝트를 잡고 중복 확인하여 체크하는 방법을 알게 되었다 !
시네머신 사용법을 알게 되었다 !
