Profile picture

10cheon00의 Archive

2D 웹 게임 프레임워크 제작기

July 08, 2023

수강신청

강의평에는 학점 퍼주는 강의라고 되어 있어서 기회가 되면 들어야지 했는데 모종의 이유로 수강신청에 성공했다.

수업과에서 여석을 풀어주는 날짜를 착각하여(...) 수강신청에 성공했다. 안그래도 컴공과 학생들이 여석없다고 난리를 치는데 이런 실수를 하면 어쩌나 싶다.

팀 프로젝트?

코로나 시기엔 비대면 강의였기 때문에 팀프로젝트도 없고 시험 잘보면 끝이라고 했었는데 웬걸, 올해부터 대면 강의로 바뀌어 교수님께서 과제 및 팀프로젝트가 평가 항목에 들어간다고 하셨다.

작년에 스타크래프트 클랜을 위해 제작했었던 전적 검색기를 만들었던 경험이 있기 때문에 별 걱정은 안했었다. 단지 조원들이 깃허브를 써본 적이 없어서 협업이 잘 될까 고민이었다.

조원분들과 얘기를 통해 뭘 만들지 생각했는데 게임을 만들자는 의견은 있었지만 마땅한 기획이 없어 쉬운걸 만들자는 결론을 내렸다. 지뢰찾기는 너무 식상하니까 이미 있는 게임을 카피하자고 하여 바운스볼을 제작하기로 했다.

그런데 바운스볼은 물리 엔진이 들어가는 게임이다. 조원분들은 쉽게 생각하는 것 같았지만 나는 이걸 만들려면 기반이 될 프레임워크가 필요하다고 생각했다.

물론 이미 오픈소스나 다른 상용 라이브러리가 있기 때문에 이걸 활용하면 OK다. 이것저것 가져다 쓰면 개발은 빨리 되겠지만 나는 게임 개발을 위해서 직접 개발하기로 했다.

프레임워크 제작

순수 JS

기왕 만들거면 다른 라이브러리를 하나도 쓰지 않고 하나부터 열까지 순수한 JS로 구현하자는(...) 독기를 품었다. 이 때 중간고사 기간이 겹쳐 있어서 조원분들과 협업하지않고 따로 개발했다.

프레임워크를 만져본 경험이 없었다면 하다가 포기하고 상용 프레임워크로 넘어갔을 것이다. 나는 고등학교 때 동아리 활동을 하면서 선배들이 제작한 프레임워크를 만져봤었고 유니티 엔진도 잠깐 공부하면서 얻은 지식이 있었기 때문에 포기하지 않았다.

DOM tree?

게임의 등장할 모든 오브젝트의 최상위 객체는 GameObject다. Sprite, Text, Rect 등 모든 오브젝트는 GameObject를 상속받아 구현된다.

유니티에서는 GameObject간에 계층 시스템이 있는데, 내가 개발할 프레임워크에도 계층 시스템을 구현하려고 했었다. 게다가 페이지에 렌더링이 되어야 하므로, 두 가지 책임을 한 번에 해결할 수 있는 DOM Tree를 활용하기로 했다.

하지만 새로운 오브젝트를 만들려면 실제로 HTML Element를 생성한 후에 document에 추가해야하고, 오브젝트의 색깔이나 크기를 변경하려면 css를 다뤄야 하므로 유지보수에 있어 불편함을 겪었다.

고민에 고민을 한 끝에 이 문제를 한 번에 해결하기 위해서는 싹 갈아 엎어야 함을 깨달았다. 부모 오브젝트를 지울 때 자식 오브젝트들도 모두 지워야하는데, 이 때 HTML Element에 대해 너무 많은 처리를 요구했다.

게다가 부모의 position, rotation, scale값이 자식에게도 전파되어야 하는데, 변수에 저장되어 있는 값이 렌더링을 위해 style에도 저장된다면 중복되어 저장되는 것인데, 이게 마음에 들지 않았다.

Canvas

렌더링 부분을 시스템에서 분리시키기 위해 <canvas>를 활용하여야 했고 이를 위해서는 렌더링 파이프라인도 구성해야했다.

렌더링 파이프라인이라고 했지만 셰이더나 다른 그래픽에 관한 처리를 하는건 내가 잘 알지 못해 하지 않았다. 단순히 <canvas>를 사용할 때 필요한 메소드의 순서를 정하는 것이 전부다.

이렇게 DOM Tree에서 <canvas>로 변경하여 렌더링과 계층 시스템을 분리하니 확장성이 더 좋아졌다.

물리 엔진

바운스볼을 제작하기 위해서는 물리 엔진을 무조건 만들어야 했다. 공이 중력을 받아 떨어져야 하고 벽에 부딪치면 튀어 올라와야 하기 때문이다.

하지만 물리 엔진을 만들어본 경험은 없어서 이 부분은 순전히 다른 오픈소스를 참고해야했다.

역시나 누가 이미 🔗문서로 정리했었다.

이 블로그도 좋지만 몇몇 부분은 오타가 있어 🔗이 블로그를 참고하여 해결했다.

구글링을 통해 교차 검증하여 물리 엔진을 완전히 만드는데 2주나 걸렸다. 수학적인 계산을 필요로 하는데, 그 부분을 이해하는데 시간이 많이 걸렸다.

테스트를 위해 원 오브젝트를 100개 생성했을 때에도 60프레임을 유지하여 꽤 잘 만들었다고 생각했었다.

협업

얼추 프레임워크를 완성하고나니 중간고사 기간이 좀 지나가 있어서 약간 늦게 협업을 시작했다. 물리 엔진을 개발하느라 늦은 면도 있지만 🔗튜토리얼🔗문서를 제작하느라 늦었다. 나 혼자 만들었기 때문에 나만 이해하고 있었다. 그래서 조원분들이 프레임워크에 대해 이해를 할 수 있도록 튜토리얼과 문서 페이지를 제작했다.

다행히도 그 덕분에 조원분들이 빠르게 이해하고 작업을 시작할 수 있었다.

하지만 조원분들이 깃허브 사용법을 잘 몰라서 어쩔 수 없이 데스크톱 프로그램을 사용했다. 모든 절차에 대해서 설명을 해야했지만 오히려 각 기능에 대해 다시 이해하는 시간이 되어 좋았다. 그리고 기능별로 브랜치를 나누어 작업한 후 병합하는 작업도 해보니 꽤 재밌었던 것 같다.

최적화

스테이지를 구성하기 위해 씬에 오브젝트를 많이 배치하게 되었는데 물리 엔진을 돌리다보니 15프레임까지 떨어지는 문제가 발생했다.

브라우저의 성능 측정 도구로 확인해보니 SetInterval로 실행된 함수가 끝나지 않고 다음 프레임에도 실행되고 있어서 프레임이 현저하게 낮아졌던 것이었다.

profiling

사진에서 상단의 노란색 막대기가 밀린 프레임을 의미한다.

불필요한 코드가 실행되고 있던 것도 아니어서 알고리즘에 의한 최적화가 필요했다. 이 문제 또한 구글링을 통해 Sweep And Prune 알고리즘을 알아내어 구현했다.

Worker

싱글 스레드에서 모든 코드들이 돌아가고 있어서 이런 문제가 발생했던 것 같다. 이 때까지만 해도 별도의 스레드를 만들 수 있는줄 몰랐다.

강의 마지막 주차 쯤 되어서 Worker를 알게 되었는데, 별도의 스레드에 올려서 물리 엔진을 돌리면 되겠다고 생각했다.

프로젝트가 무거웠으면 이렇게 해야했지만 바운스볼은 가벼운 게임이어서 그렇게 하지 않아도 되었다...

마무리

프레임워크를 직접 제작했기 때문인지 마감 날짜보다 이틀이나 빠르게 끝내게 되었다. github.io를 이용해 배포하려고 했지만 정적 페이지를 제대로 인식하지 못해 배포를 해도 제대로 보여지지 않았다. 어쩔 수 없이 Netlify에 직접 리포지토리를 연결했다. Github Action에 대한 이해가 부족해서 이랬던 것 같다.

완성된 2d 웹프레임워크

기말고사를 약간 못 본 것 같아서 A+이 나오지 않을까봐 불안했었지만 다행히 강의평에 올라와있던 내용대로 학점을 잘 주시는 교수님이셨다. grade


Loading script...