스파게티 코드라는 지옥
아, 지금 이 글을 읽고 있는 당신… 혹시 나와 같은 길을 걷고 있는 건 아닌가?
유니티 입문서 하나 달랑 읽고서 “아, 이거 쉽네!” 하면서 의기양양하게 게임 개발에 뛰어들었던 그때의 내가 떠오른다. GameObject 만들고, 스크립트 붙이고, Update()에다가 온갖 로직을 우겨넣고… 처음엔 정말 재미있었다. “와, 내가 게임을 만들고 있어!” 하면서 말이다.
하지만 게임이 조금씩 복잡해지기 시작하면서, 지옥문이 열렸다.
“어… 이 코드가 뭘 하는 거였지?” “이 변수를 왜 여기서 참조하고 있지?” “버그가 생겼는데 어디서 고쳐야 하지?”
몇 주 전에 내가 짠 코드인데도 알아볼 수가 없었다. 새로운 기능을 하나 추가하려고 하면 기존의 10개 스크립트를 다 건드려야 했고, 하나를 고치면 다른 곳에서 3개가 터졌다.
말 그대로 스파게티 코드의 완성이었다.
매니저 스크립트에는 게임의 모든 로직이 들어있었고, UI 스크립트는 게임 로직과 직접 연결되어 있었으며, 플레이어 스크립트는 적, UI, 사운드, 이펙트까지 모든 걸 직접 참조하고 있었다. 하나의 스크립트가 수백 줄이 넘어가는 건 기본이고, 함수 하나가 100줄 넘는 것도 일상이었다.
디버깅은 악몽이었다.
“분명히 여기서 체력을 깎는데 왜 UI가 업데이트 안 되지?” 하면서 추적해보니, 체력 시스템, UI 시스템, 적 시스템이 서로 꼬여있어서 어디서 뭐가 잘못된 건지 찾을 수가 없었다.
확장성은 꿈도 꾸지 못했다.
새로운 적을 하나 추가하려고 해도, 기존 코드 구조가 너무 경직되어 있어서 모든 걸 다시 뜯어고쳐야 했다. “아, 이럴 거면 처음부터 다시 만드는 게 낫겠다”는 생각이 하루에 몇 번씩 들었다.
그렇게 몇 달을 삽질하고 나니, 완전히 지쳐버렸다. “나는 프로그래밍에 재능이 없나? 다들 어떻게 이런 복잡한 게임을 만드는 거지?” 하면서 자괴감에 빠져있었다.
희망의 등장 – 체계적인 프로그래밍의 발견
그러던 중, 우연히 발견한 것이 미사일 커맨더로 배우는, 유니티 C# 프로그래밍 연습 전자책이었다.
처음엔 반신반의했다. “또 다른 입문서 아닌가?” 하는 생각이었으니까. 하지만 강의 소개를 읽어보니 뭔가 달랐다.
“단순한 기능 구현이 아니라 확장성과 유지 보수성을 염두에 둔 유니티 C# 프로그래밍의 원리를 배우실 수 있습니다.”
확장성과 유지 보수성… 내가 그토록 갈망했던 바로 그것이었다.
미사일 커맨더라는 고전 게임을 만들면서 배우는 방식이었는데, 단순히 “이렇게 하면 됩니다” 하고 끝나는 게 아니라 왜 이렇게 해야 하는지, 이런 구조를 선택하는 이유가 무엇인지를 자세히 설명해주었다.
변화의 시작 – 코드가 살아나다
책을 읽기 시작하면서 내가 그동안 얼마나 잘못된 방식으로 코딩하고 있었는지 깨달았다.
Before: 모든 것이 연결된 지옥
public class PlayerController : MonoBehaviour
{
public GameManager gameManager;
public UIManager uiManager;
public EnemySpawner enemySpawner;
public SoundManager soundManager;
void Update()
{
// 이동, 공격, UI 업데이트, 사운드 재생
// 모든 로직이 한 곳에...
}
void TakeDamage()
{
health -= damage;
uiManager.UpdateHealthBar(health);
if(health <= 0)
{
gameManager.GameOver();
soundManager.PlayDeathSound();
// 죽음 처리도 여기서...
}
}
}
After: 역할이 분리된 아름다운 구조
public class PlayerController : MonoBehaviour
{
...
void Start()
{
health.OnHealthChanged += HandleHealthChanged;
health.OnDeath += HandleDeath;
}
private void HandleHealthChanged(int newHealth)
{
// 단순히 이벤트만 발생, 나머지는 각각의 시스템이 처리
}
}
깨달음의 순간들
1. 느슨한 커플링(Loose Coupling)의 마법
기존에는 모든 스크립트가 서로를 직접 참조했다면, 이제는 이벤트를 통해 소통한다. 플레이어가 죽었을 때 UI를 업데이트하고 사운드를 재생해야 한다면, 플레이어는 그냥 “죽었다”는 이벤트만 발생시키고, UI와 사운드 시스템이 각각 그 이벤트를 듣고 알아서 처리한다.
결과: 플레이어 코드를 수정해도 UI나 사운드 시스템에는 영향이 없다!
2. 의존성 주입(Dependency Injection)의 힘
하드코딩된 참조 대신, 필요한 의존성을 외부에서 주입받는다. 이렇게 하면 테스트도 쉬워지고, 다른 구현체로 교체하는 것도 간단해진다.
3. 오브젝트 풀링의 우아함
총알이나 이펙트 같은 자주 생성되고 파괴되는 객체들을 매번 Instantiate/Destroy 하는 대신, 미리 만들어둔 풀에서 가져다 쓰고 반납하는 방식. 성능도 좋아지고 코드도 깔끔해진다.
4. 컴포지션 루트의 명확함
게임이 시작될 때 모든 의존성을 한 곳에서 설정하고 연결하는 방식. 전체 시스템의 구조를 한눈에 파악할 수 있게 되었다.
변화의 결과 – 코딩이 즐거워지다
이런 원리들을 적용하고 나니, 게임 개발이 완전히 다른 경험이 되었다.
디버깅이 쉬워졌다: 버그가 생기면 어디서 찾아야 할지 명확하다. 각 시스템이 독립적이라서 문제의 범위를 쉽게 좁힐 수 있다.
확장이 자유로워졌다: 새로운 적을 추가하거나 새로운 기능을 넣는 것이 기존 코드를 거의 건드리지 않고 가능하다.
코드가 읽기 쉬워졌다: 몇 달 전에 짠 코드도 금방 이해할 수 있다. 각 클래스와 함수가 명확한 역할을 가지고 있기 때문이다.
테스트가 가능해졌다: 각 부분을 독립적으로 테스트할 수 있어서, 버그를 미리 찾아낼 수 있다.
자신감이 생겼다: “이 코드를 수정하면 뭐가 깨질까?” 하는 불안감 대신, “이 부분만 수정하면 되겠네” 하는 확신을 가질 수 있게 되었다.
같은 실수를 반복하지 마라
지금 이 글을 읽는 당신이 나와 같은 상황이라면, 제발 더 늦기 전에 제대로 된 구조를 배우기 바란다.
“일단 돌아가게 만들고 나중에 리팩토링하자”는 생각은 환상이다. 나중은 오지 않는다. 기술부채는 복리로 쌓인다.
입문서 하나 읽고 만족하지 마라. 기초적인 문법을 안다고 해서 설계를 할 수 있는 건 아니다. 마치 알파벳을 안다고 해서 소설을 쓸 수 있는 게 아닌 것처럼.
“일단 돌아가게 만드는 것”과 “제대로 만드는 것”은 다르다. 전자는 단거리 달리기고, 후자는 마라톤이다. 게임 개발은 마라톤이다.
구조와 설계 원리를 먼저 배워라. 그래야 나중에 몇 달, 몇 년 뒤에도 자신의 코드를 이해할 수 있고, 확장할 수 있다.
마무리 – 절망에서 희망으로
몇 달 전의 나는 완전히 절망하고 있었다. 내가 만든 코드를 내가 이해할 수 없다는 게 얼마나 좌절스러운 일인지…
하지만 지금은 다르다. 코딩이 다시 즐거워졌고, 새로운 기능을 추가하는 것이 두렵지 않다. 버그가 생겨도 당황하지 않는다. 어디서 찾아야 할지 알기 때문이다.
구조의 중요성을 깨달은 지금, 나는 더 이상 스파게티 코드의 노예가 아니다.
당신도 같은 절망을 겪고 있다면, 포기하지 마라. 올바른 길이 있다. 다만 그 길을 찾기 위해서는 기본기와 원리부터 제대로 배워야 한다.
코딩은 예술이다. 그리고 모든 예술에는 기본기가 있다.
이 글이 스파게티 코드의 지옥에서 고생하고 있는 누군가에게 작은 희망이 되었으면 좋겠다. 당신도 분명 해낼 수 있다. 나도 했으니까.
미사일 커맨더로 배우는, 유니티 C# 프로그래밍 연습 (바로 가기)