Unity가 직면한 기술적 문제들을 생각보다 많은 분들이 보시고 피드백을 주셔서 관련하여 제 의견을 한 번 더 정리합니다.
일단 오해의 소지를 줄이기 위해 한 가지 말씀드리면, 이 글은 제가 지난 1년 2개월 Unity를 이용한 모바일 게임 개발에 참여하며 개발자로서 느낀 기술적인 문제점을 정리한 것입니다. 모바일 게임 엔진의 향후 전망 같이 거창한 이야기는 절대 아니고, Unity 사용자가 작성한 피드백 정도로 이해하시면 됩니다. 또한 게임 엔진으로서 Unity 전체에 대한 종합적인 평가가 아니라 .NET 런타임 지원에 국한한 기술적인 문제만 제기한 것이므로 이 부분도 감안해서 읽으시길 권해 드립니다.
글을 올린 후에 많이 받은 피드백 중에 하나가 C#은 유니티 게임 엔진에서 단순히 스크립팅 언어로 사용될 뿐인데, 최신 .NET 런타임을 완벽히 지원하는 것이 중요하느냐라는 질문입니다.
이 피드백에 대해 제 생각을 말씀드리기 위해서는 일단 용어 정의부터 명확하게 해야할 것 같습니다. 프로그래밍 언어는 크게 프로그래밍 언어 자체와 런타임으로 나뉩니다. 런타임은 표준 라이브러리라고도 불리는데, 프로그래밍 언어와 불가분의 관계에 있어서 보통은 언어와 런타임을 통칭하여 프로그래밍 언어로 부릅니다. 예를 들어, C 언어는 C 언어와 C 표준 라이브러리고 구성이 되고, 자바는 자바 언어와 JDK로, C#은 C#과 .NET 런타임으로 구성이 되는 것이죠.
우리가 스크립팅 언어로 흔히 사용하는 언어로 JavaScript와 Lua가 있는데, 이들 언어의 공통점은 런타임이라고 부를 수 있는 요소가 굉장히 작다는 점입니다. 바꿔 말하면, 프로그래밍 단독으로는 거의 할 수 있는 일이 없습니다. 웹브라우저에서 DOM에 접근할 수 있다거나, node.js에서 HTTP 서버를 띄울 수 있는 이유는 JavaScript가 이런 기능을 제공하기 때문이 아니라, 웹브라우저나 node.js가 제공하는 기능을 JavaScript라는 스크립팅 언어를 통해 접근하는 할 수 있기 때문입니다. 이런 언어들은 언어 런타임이 굉장히 작기 때문에 이식성도 높고, 임베딩도 쉽게 할 수 있다는 장점이 있습니다.
이런 제약이 싫다면 Python이나 Ruby를 스크립트 언어로 사용할 수 있습니다. 이들 언어는 JavaScript나 Lua와 달리 상당히 방대한 양의 언어 런타임(표준 라이브러리)을 제공하고 있습니다. 예를 들어, Python은 로컬 파일을 읽는 기능이나, 쓰레드 생성, 네트워크 통신 등을 별도의 통합 작업 없이도 언어 런타임이 기본으로 지원합니다. 이런 기능은 필요에 따라 상당히 편리하게 이용될 수도 있으나, 반대로 임베더 입장에서는 열어줘서는 안 되는 기능을 의도치 않게 열게 될 수도 있습니다. 일례로, 만약 웹브라우저가 파이썬을 스크립팅 언어로 제공했다면 갑자기 웹브라우저의 기능과 상관 없이 Python의 표준 라이브러리를 사용하여 로컬 파일을 읽어가거나 네트워크 통신을 하는 일이 가능해집니다.
따라서 언어 런타임에 많은 기능이 포함된 언어를 스크립팅 언어로 사용하기 위해서는 어떤 API를 열어줄지 엄밀하게 고민해서 API를 부분적으로 잘라내거나, 기능에 접근할 수 없게 막는 장치가 필요하게 됩니다. 사내에서만 사용하는 스크립팅 언어라면 내부적으로 합의만 하면 되지만, Unity처럼 SDK 형태로 나가는 경우에는 상황이 달라집니다. 공개된 모든 API는 사용될 가능성이 있기 때문입니다.
Unity는 .NET을 스크립팅 언어로 선택했습니다. 명시적으로는 C#, UnityScript, Boo 등 구체적인 프로그래밍 언어를 지원한다고 하지만, 이들 언어 모두 .NET 기반이고 F#과 같이 지원이 명시되지 않은 언어도 Unity에서 동작에 문제가 없으므로 Unity의 스크립팅 언어는 사실상 .NET이라고 봐야 합니다. 그리고 .NET을 스크립팅 언어로 선택한 것은 축복이면서 동시에 저주가 되었다고 생각합니다.
Unity가 초기 모바일 게임 시장에 빠르게 진압할 수 있었던 이유에는 게임 엔진의 우수함이나 에디터의 편리함도 있겠지만, .NET 플랫폼을 활용한 크로스플랫폼 개발 환경이라는 요소를 무시할 수 없다고 생각합니다. C#이라는 편리한 개발 언어, .NET이 제공하는 수많은 API는 런타임 없이 간단한 스크립팅 언어만 제공하는 엔진에 비해 높은 생산성을 제공했기 때문입니다. 또한 .NET에 존재하는 수많은 써드파티 라이브러리도 별다른 노력없이 확보할 수 있었습니다.
하지만 Unity의 .NET 기술 지원이 뒤처지면서 초기의 이런 장점들은 점점 빚이 됩니다. .NET 4 이후로는 사실상 별도의 섬처럼 생태계가 고립되기 시작하였고, 더 이상 .NET 라이브러리를 Unity에서 그대로 사용할 수 없어 포팅 작업을 하거나 별도의 라이브러리를 제작해야 하는 상태가 되었습니다. 또한 LINQ, async/await 등 C#과 .NET의 최신 기능을 제공하지 않으면서 그 간극은 점점 더 멀어지고 있는 상태입니다.
일례로, 이전 글에서 이야기한 것처럼 LINQ를 지원하는 것도 아니고 지원하지 않는 것도 아닌 채로 방치하는 것은 심각한 문제입니다. .NET을 스크립팅 언어로 채택했으면, 아에 LINQ를 사용할 수 없게 API를 잘라냈거나 제대로 지원을 했어야 합니다. 이런 상황은 껍데기는 예쁘게 포장했는데 막상 포장을 뜯어보니 정작 내용물은 여기 저기 상해 있는 과일 상자와 다를 바가 없습니다.
쓰레드를 사용하지 말라고 이야기하는 것도 무책임한 말입니다. .NET을 스크립팅 언어로 채택했으면 쓰레드를 지원하거나 쓰레드 지원이 Unity 게임 엔진 사용에 문제가 된다면 아에 쓰레드 API를 제외했어야 합니다. LINQ와 마찬가지로 100% 지원하는 것도 아니고, 그렇다고 안 되는 것도 아닌 상태로 출시를 하면 당연히 .NET에 익숙한 개발자들이 쓰레드를 사용하게 되고 여러 가지 문제점을 겪으며 고생할 수밖에 없습니다.
물론 게임 엔진이라는 게 내부적으로 태스크(task) 혹은 잡(job)을 관리하기 위한 스케쥴러가 있고, 병렬적으로 동작하므로 단순히 게임 로직만 작성하는 스크립팅 언어에서 굳이 쓰레드를 사용하거나 복잡한 일을 해서는 안 된다고 조언할 수는 있습니다. 하지만 어린 아이는 다칠 위험이 없는 안전한 방에서 놀게 해야지, 온갖 위험한 물건들이 즐비한 방에서 놀게 하고선 위험한 물건은 만지지 말라고 조언해서는 안 된다고 생각합니다.
또한 async/await 키워드로 대표되는 C#의 비동기 프로그래밍은 단순히 멀티쓰레드 프로그래밍과는 개념이 다릅니다. 오히려 Unity가 코루틴을 이용해 어설프게 해결하고 있는 동시성(concurrency) 혹은 멀티태스킹(multitasking)을 쉽게 작성하기 위해 고안된 방법에 가깝습니다. 게임이라는 게 로직만 작성하더라도 동시성을 표현하지 않을 수 없는데, 이런 동시성을 표현하는 가장 효과적인 방법이 async를 이용한 비동기 프로그래밍이기 때문입니다. 참고로 대표적인 싱글 쓰레드 모델의 프로그래밍 언어인 JavaScript 또한 ES7에서 C#의 async와 유사한 async 함수 도입을 논의하고 있습니다.
Unity는 어쨌거나 .NET을 채택했으므로 이왕이면 발전하고 있는 .NET 기술의 혜택도 누리는 게 맞는데, 이러지도 저러지도 못하고 구버전 .NET에 갖혀 있는 상태가 안타깝다는 생각이 듭니다. 마이크로소프트나 Xamarin과 협상이 잘 되어서 돌파구를 찾거나 IL2CPP 개발에 더 박차를 가해서 당면한 문제를 풀기를 기대해 봅니다.
Pingback: Unity가 직면한 기술적 문제들 2 | 서광열의 코딩 스쿨
Pingback: Unity가 직면한 기술적 문제들 | Kwang Yul Seo