왜 만들었나
고등학교 지구과학 시간 발표 자료로 쓰려고 만들었다. 기상 현상(태풍, 뇌우, 우박)이 어떻게 생기는지를 슬라이드로 설명하는 대신, 실제로 돌아가는 3D 화면을 띄워 놓고 발표하고 싶었다.
| 항목 | 내용 |
|---|---|
| 엔진 | Unity 2021.3.45f2 |
| 기여도 | 100% (1인 개발) |
| 용도 | 고등학교 지구과학 시간 발표 |
| 다루는 현상 | 태풍 · 뇌우 · 우박 |
| 리소스 | 파티클 효과와 텍스처 에셋 전부 직접 제작 |
PPT를 넘기는 것보다 직관적인 3D 자료가 있으면 듣는 쪽이 이해하기 쉬울 거라고 봤다. 발표의 질을 높이는 게 목적이었지 게임을 만들 생각은 아니었다.
뇌우와 우박 — 파티클

번개, 비, 우박 입자는 파티클 시스템으로 만들었다. 이쪽은 기획한 대로 나왔다. 구름 아래로 떨어지는 것들의 밀도와 속도만 조절하면 우박과 비의 차이가 화면에서 바로 구분된다.
태풍 — 셰이더
태풍은 파티클로 풀 수 없었다. 소용돌이 형태 자체가 모양을 가져야 했기 때문이다. 나선형 메시를 만들고 거기에 셰이더를 입혀 회전하는 구름 덩어리로 보이게 했다.


발표 흐름에 맞춰 단계를 넘기기
기상 현상은 순서가 있다. 수증기가 증발하고, 상승 기류가 생기고, 구름이 만들어지고, 비나 우박이 내린다. 이 순서를 발표 타이밍에 맞춰 넘길 수 있어야 했다.
count 변수 하나를 올리면서 각 단계에 해당하는 파티클 오브젝트를 켜고 끄는 구조로 만들었다. 상태 머신처럼 동작한다.
count += 1;
if (count == 5)
{
// 모든 이펙트 비활성화 및 초기화
count = -1;
rainPar.SetActive(false);
rainPar2.SetActive(false);
tT1.SetActive(false);
// ...
}
else if (count == 1)
{
// 상승 기류 및 초기 구름 형성
tT1.SetActive(true);
upAir1.SetActive(true);
upAir2.SetActive(true);
}
else if (count == 2)
{
// 본격적인 뇌우/우박 파티클 재생
tT2.SetActive(true);
tT3.SetActive(true);
rainPar.SetActive(true);
upAir1.SetActive(false);
tTen1.SetActive(true);
}
// ... (이후 단계 생략)
지금 이 코드를 읽으면
- 초기화에서
count를 0이 아니라 −1로 되돌린다. 함수 맨 위에서 이미count += 1을 했기 때문에, −1로 돌려놔야 다음 호출에서 0이 된다. 증가를 먼저 하고 판정을 나중에 하는 순서 때문에 생긴 보정이다. - 단계 판정과 연출이 한 덩어리다.
else if마다SetActive가 줄줄이 붙어 있어서 단계를 하나 추가하려면 분기 전체를 다시 읽어야 한다. 단계별로 켤 오브젝트 배열을 데이터로 두고 순회하면 분기가 사라진다. tT1,tTen1,upAir1같은 이름은 지금 보면 뭘 가리키는지 알 수 없다. 발표 하루 전에 붙인 이름의 대가다.5라는 종료 단계 수가 코드에 그대로 박혀 있다. 단계를 늘리면 이 숫자도 같이 고쳐야 한다.
발표 중에 끊기지 않게
씬을 넘길 때 화면이 멈추면 발표 흐름이 끊긴다. 비동기 로딩(LoadSceneAsync)에 진행률 바를 붙였다.
IEnumerator LoadScene(string name)
{
lodingImg.SetActive(true);
yield return null;
AsyncOperation op = SceneManager.LoadSceneAsync(name);
op.allowSceneActivation = false;
float timer = 0.0f;
while (!op.isDone)
{
yield return null;
timer += Time.deltaTime;
if (op.progress < 0.9f)
{
progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, op.progress, timer);
if (progressBar.fillAmount >= op.progress) timer = 0f;
}
else
{
progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, 1f, timer);
if (progressBar.fillAmount == 1.0f)
{
op.allowSceneActivation = true;
yield break;
}
}
}
}
이 코드는 자동차 오픈월드 의 메인 메뉴에 쓴 것과 같다. 오타(lodingImg)까지 그대로다. 한 번 만들어 둔 것을 다음 프로젝트에 그대로 옮겨 쓴 흔적이다.
정리
게임이 아니라 교육용 시뮬레이션을 만들면서, 기상 현상을 유니티의 입자 제어와 셰이더로 풀어내는 경험을 했다. 파티클 시스템을 이때 처음 제대로 다뤘다.
형태를 가진 것(태풍)과 흩뿌려지는 것(비·우박·번개)은 접근이 다르다는 것도 여기서 배웠다. 전자는 메시와 셰이더, 후자는 파티클이다. 둘을 바꿔서 시도했다면 훨씬 오래 걸렸을 것이다.