본문 바로가기

MOBILE/project 회고

UIKit + SpriteKit으로 iOS 모바일 레이블링 게임 만들기

2022/04/20~2022/11/25  [주 ZOO 컴퍼니] 프로젝트

 

목차

1. (주) ZOO 컴퍼니

2. 컨셉 기획과 게임 디자인

3. 개발 과정

4. 각종 크고 작은 이슈들

5. 게임 소개

6. 후기

 

1. (주) ZOO 컴퍼니

 4월 초에 휴학을 하게 되면서 생각만 했던 프로젝트를 직접 해볼 수 있는 시간적 여유가 생기게 되었다. 직접 팀원들을 모아서 기획팀 2명, 디자인팀 2명, 개발은 필자 한명으로 구성된 총 5명의 소규모 프로젝트 팀을 만들었다. 그런데 원래 기획은 모종의 이유로 엎어지게 되었고 이대로면 어렵게 모인 팀원들이 흩어지게 되는 일이 생기게 되었다. 그래서 예전부터 장난식으로 친구와 말을 나누었던 레이블링 게임을 만드는건 어떤지 회의를 했고, 팀원들 모두가 재밌을 것 같다고 하여 함께 하게 되었다.

 

 기본적인 게임 소개는 다음과 같다.

드디어 엄청난 경쟁률을 뚫고 신입으로 구구 테크에 입사하게 된 종이맨!
직접 종이맨이 되어 게임을 플레이하고 신입 부서 배치를 받아보자.
과연 종이맨의 정체는 무엇일까?
(주) ZOO 컴퍼니! 11월 25일 개봉박두!

게임 인트로 화면

 

 

2. 컨셉 기획

 4~5월은 게임 컨셉과 스토리 기획 회의를 집중적으로 진행했다. 주인공 [종이맨]이 엄청난 경쟁률을 뚫고 구구 테크에 신입으로 입사를 하게 되어 보내는 회사 생활이 주 내용이다. 신입으로 회사 생활을 하고 이를 바탕으로 인사 평가를 받게 되는데, 이때 종이맨의 정체와 어떤 부서에 배치되었는지 게임 유저의 선택과 성향에 따라 달라지게 된다. 레이블링 게임의 단점인 지루함을 해결하기 위해 ch1에서 구글의 T-Rex Dinosaur처럼 미니 게임을 추가하여 유저의 흥미를 유지할 수 있도록 하였다.

 

 사실 기획팀분들께서도 게임 스토리와 기획을 하던 분들이 아니셔서 모두가 다같이 스토리를 구성하고 그에 따른 선택지를 만들었다. 이렇게 나온 전체적인 게임 기획을 바탕으로 디자인을 해야하기 때문에 내가 나서서 정리를 했었다. 아무튼 기획이 완료되고 본격적인 게임 디자인을 하고 개발팀과의 협업을 위해 피그마를 사용했다.

ch1과 ch2 게임 디자인

게임 디자인은 가장 오랜 시간이 걸렸던 파트였다. 게임 챕터마다 필요한 디자인 요소가 너무 많아서 작업에 오랜 시간이 걸렸다. 결국 모든 디자인을 끝낼 때까지 기다릴 수가 없어서 개발과 디자인을 병렬로 진행했다. 결국 마지막쯤 가서는 작업 리스트가 너무 많아져서 디자인 팀원을 두분정도 더 모셔왔다.

 

3. 개발 과정

3.1 SpriteKit

 ios에서는 2D 모바일 게임을 위한 SpriteKit이라는 프레임워크를 제공한다. 이를 활용해서 개발을 하기로 했다. 사실 Unity라는 아주 강력한 게임 엔진이 있는데 굳이굳이 SpriteKit을 사용한 이유는 swift와 ios에 최적화 되어있기 때문이었다. 처음에 ios를 타겟으로 정한것은 아니었지만 ios 개발이 재밌다고 생각하게 되어 더 공부하고 싶었기 때문에 이를 사용했다.

 

SpriteKit을 사용한 미니게임

 

SpriteKit은 정말.. 배우기 힘들었다. 초반에 삽질을 굉장히 많이 했다. Scene이 어떻게 로드되는지도 모르겠고 심지어 start controller를 일반 viewController로 한 뒤에는 gamecontroller로 전환이 안되는 이슈도 발생했었다.

가장 어려운 부분은 충돌 물리를 구현하는 부분이었다. 장애물을 만나면 충돌이 발생해야 하는데 collisionMask를 씌워도 반응이 없어서 도대체 뭐가 문제인가 계속 찾았었다.

 

  let physicsBox = CGSize(width: runArray.first!.size().width * playerScale,
                                height: runArray.first!.size().height * playerScale)
        
        playerSprite.physicsBody = SKPhysicsBody(rectangleOf: physicsBox)
        playerSprite.physicsBody?.isDynamic = true
        playerSprite.physicsBody?.mass = 1.0
        playerSprite.physicsBody?.categoryBitMask = playerCategory
        playerSprite.physicsBody?.contactTestBitMask = obstacleCategory
        playerSprite.physicsBody?.collisionBitMask = groundCategory

 

결국 이런식으로 모든 비트 마스크를 씌우고 질량을 설정한 뒤에야 맞출 수 있었다. 자료도 별로 없고 찾는게 정말 힘들었다.

 

 

3.2 SpriteKit과 UIKit을 섞어서 사용하자... 그런데 화면 전환은?

 개발을 진행하면서 UIKit도 같이 사용하였기 때문에 화면 전환을 여러번 해야하는 일이 생겼다. 스택에 계속 쌓여서 메모리 낭비가 발생하는게 마음에 들지 않아 controller를 dissmiss 후 present를 하는 화면 전환 코드를 UIViewController extension 안에 작성했었다. 그리고 화면 전환은 fade in fade out 효과를 주기 위해 CATransition을 layer.add 하는 방식으로 사용했다. 그런데 이렇게 화면 전환 하는 사이에 root view controller가 계속 보이는 문제가 발생했다. 분명 root view에서 이를 호출하는데도 문제가 발생했다. 그런데 진짜 심각한 것은 버그가 생겼다 안생겼다 하는 것이었다. 내가 생각하는 원인은 dissmiss 후에 present를 하기 때문인 것 같고, 또 다른 하나는 rootview에서 dissmiss하고 present를 하는데 이때 sound이 fade out 효과를 같이 주게 되면서 thread 문제가 좀 섞인 것 같다..

 

sound fade out 코드를 이렇게 작성하였는데 여기서 좀 문제가 발생한 것 같다. 왜냐면 present 자체가 비동기로 처리되기 때문이 아닐까싶다.

 

    func setVolumeFadeOut(_ sender: AVAudioPlayer?, completion: (() -> Void)?) {
        sender?.setVolume(0, fadeDuration: 0.5)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
            sender?.stop()
            completion?()
        })
    }

 

어쨌든 이 문제는 present 하는 방식을 바꿔서 해결했다.

 

 

3.3 클래스 상속 구조 설계

게임 구조를 생각해보면 레이블링 게임이기 때문에 굉장히 같은 구조가 반복된다. 따라서 이를 매번 작성하지 말고 상속해서 쓰자고 생각했다. 이를 위해서 parent view controller와 layout 클래스를 설계했다. 이 과정에서도 진짜 여러번 갈아엎은 적이 많다. 하지만 개발이 나 혼자라서 모든 책임과 감당 또한 나 혼자여서 괜찮았다. 어쨌든 이를 상속해서 쓰게 되면서 layout 코드는 거의 건드리지 않고 상속 그대로 받아서 사용할 수 있었다. 진짜 객체 지향 최고. 재활용은 아름다운 것이다(?) 

 

 마지막에 보이는 게임 결과도 여러 개의 상속으로 이루어져 있다. 일단 보여야 하는 것은 동물 종류와 사용자의 타입에 따른 설명 클래스고 이를 전체적으로 포함하는 데이터 바인딩 용 클래스이다. 따라서

Animal -> AnimalType -> DepartmentData 구조로 설계했다.

클래스 설계

전체적으로 동물이 가져야 하는 정보는 Animal class를 상속받아 만들었다. 그리고 이러한 Animal class 여러개가 필요한 동물 타입 클래스를 상속받을 수 있도록 하였다. 이렇게 만든 AnimalType 클래스는 결과 viewcontroller가 필요한 데이터를 바인딩하는 클래스에 생성되어 들어간다.

 

 

 

그 다음 머신 러닝을 통해 나온 classification을 바탕으로 부서 class를 return 하면 AnimalType에 넣어주는 형태로 설계했다. 정말.. 객체 지향은 최고다. 사실 클래스, 상속, 객체 지향 등을 배울 때는 잘 못느끼는데 직접 프로젝트를 하다보면 정말 아름다운 아키텍쳐같다.

 

 

3.4 라이브러리 분해하기

 추천서 view의 그래프를 표시하기 위해서 charts 라이브러리를 반입해서 사용하였다. 사용한 것은 HorizontalBarChart, RadarChart 두 가지이다. 그런데 문제는 HorizontalBarChart에서 원하는 UI로 그리기 위해서는 rendering 과정을 건드려야 하는 것이었다... 목표는 round인데 사각형만 지원했기 때문이다. 포기할까? 그냥 사각형으로 하자고 할까? 하다가 이대로 멈출 수 없기 때문에 라이브러리를 까보기로 했다.

원하는 곡선 모양을 추가하기 위해서 인턴을 하며 배웠던 UIBezierPath를 사용했다. UIBezierPath(roundedRect:) 를 사용하여 곡선 처리 된 사각형을 context에 추가하는 방향으로 그렸다. 그런데 왜 어느 부분은 잘 되고 어느 부분은 잘 안되는지 아직도 원인은 못찾았다.

 

BarChartRenderer

어쨌든 그렸으니 해피엔딩이라고 할 수 있다.

 

3.5 Machine Learning 모델 도입하기

 사용자의 선택지를 기반으로 어떻게 부서 추천과 동물 추천 알고리즘을 구현할까 고민을 했다. 마침 이번학기에 기계 학습 수업을 듣고 있었기 때문에 classification 문제는 코드로 작성하여 해결할 수 있었다. 그런데 문제는 python에서는 잘 되어있는 ML용 라이브러리를 과연 swift에서 지원해줄까 하는 것이었다. 그렇다고 밑바닥부터 행렬 연산.. Transpose 연산... back propagation... gradient descent.. 이런 코드를 작성하는 것은 정말 시간이 오래 걸리고 낭비인 것이었다. 그래서 Xcode에서 제공해주는 머신 러닝 도구인 coreML를 사용했다.

이걸 바탕으로 유저 선택지를 파라미터로 predict를 하고, classification 결과를 label로 return 한 것을 가지고 Department class에 넣어서 유저의 동물과 부서 클래스를 생성했다.

 

 

자세한 coreML 모델 사용기는 이전에 발행했던 포스트 참고

 

[iOS] coreML 사용하여 multi class classification 모델 생성 및 프로젝트 반입하기

프로젝트를 진행하던 중 multi class classification을 해야할 일이 생겼다. 프로젝트가 아직 진행 중이라 다 말할 수는 없지만 사용자의 선택지에 따라 선택에 맞는 결과를 분류하여 class 중 하나를 보

josushell.tistory.com

 

 

3.6 앱 스토어에 출시하기

 드디어! 최종 버그까지 다 수정하고 앱 스토어에 심사를 제출했다. 이 과정도 진짜 쉽지 않았다. 개인정보처리방침 url을 추가해야해서 급하게 이를 알아보고 깃헙 문서로 추가하는 과정까지 거쳤다.

 

 

 

4. 각종 크고 작은 이슈들

 앞에서 말했다시피 디자인 요소가 너무 많은데 팀원은 2명이라는 문제가 있었다. 게다가 팀원 중 한분께서는 회사를 다니고 계셔서 작업 속도를 내기가 어려운 상황이었다. 결국 개발이 디자인 작업의 속도를 따라잡아서 개발을 더 이상 못하게 되는 문제가 자주 발생하였다. 이를 해결하기 위해서 피그마에 있는 체크리스트를 활용해보기로 했다.

 

먼저 작업 속도를 내기 위해서는 다음의 두 가지 요소가 필요하다고 생각했다.

1) 정확한 마감 기한

2) 동기 부여 가능한 상황

1번을 위해서는 피그마에 있는 타임라인 위젯을 활용하여 정확한 작업 기간을 시각적으로 볼 수 있도록 해두었다. 날짜를 알고 있는 것과 눈으로 확인하는 것은 다르기 때문이다.

2번을 위해서 정말 고민을 많이 했는데, 사실 동기 부여는 남이 해주는 것보다 스스로 해야 지속가능하기 때문이다. 그래서 내가 무언가를 하기 보다 디자인팀 분들께서 어떤 액션을 통해 능동적으로 성과를 이루어낼 수 있는 상황이 필요하다고 생각했다. 따라서 체크리스트 위젯을 사용했다.

 

 일단 필요한 디자인 작업 목록을 중요도 순으로 나열해둔 다음 각 항목마다 체크리스트를 부여하여 작업이 완성되면 체크리스트를 클릭하여 성과를 눈으로 볼 수 있도록 하였다. 이렇게 한다면 어떤 작업이 덜 되었고, 무엇이 중요한지 한눈에 파악할 수 있을 뿐만 아니라 작업 후 체크를 표시하면서 뿌듯함과 성취감을 쉽게 얻을 수 있기 때문이다.

동기 부여를 위해 생각해낸 해결방안

실제로 이 방법을 도입하고 나서 작업 속도가 많이 올랐었다. 게다가 디자인팀의 한분도 굉장히 좋은 생각이라면서 체크를 표시하는 쏠쏠한 재미가 있다고 말씀해주셨다. 아주 뿌듯했다! 무언가 개선이 안된다면 역시 환경이나 주변 상황에 살짝 변화를 주는 것만으로도 많은 것이 바뀔 수 있다고 생각한다.

 

 

5. 게임 소개

이제 게임 소개를 해보자

드디어 엄청난 경쟁률을 뚫고 신입으로 구구 테크에 입사하게 된 종이맨!
직접 종이맨이 되어 게임을 플레이하고 신입 부서 배치를 받아보자.
과연 종이맨의 정체는 무엇일까?
(주) ZOO 컴퍼니! 11월 25일 개봉박두!

 

 게임을 플레이하고 회사에서 과연 나는 어떤 부서가 어울릴지 알아보자!

 

 

다양한 상황 속에서 만날 수 있는 선택지와 미니 게임까지!

 

 

게임을 플레이하고 머신 러닝을 바탕으로 추천된 부서를 알아보고 구구 테크의 사원증까지 받아보자!

 

QR코드를 스캔하면 앱 스토어로 연결됩니다

 

 

6. 후기

 4월 부터 진행된 약 7개월 간의 정말 정말 긴 프로젝트였다. 게다가 중간에 회사 인턴을 하는 기간이 겹쳤었는데, 이때가 정말 힘들었다. 퇴근하고 공부 + 개발까지 함께 병행하느라 거의 개인시간이 없었지만 진짜 많은 걸 배우고 활용할 수 있었던 프로젝트였다. 개발도 혼자서 진행하느라 클래스 구조와 로직을 다 직접 설계하는 과정에서 이슈가 정말 많았지만 하나씩 슈팅해나가는 과정을 통해서 디자인 패턴과 UIKit, Ios에 대해서도 많은 것을 배울 수 있었다. 게다가 팀원 분들이 모두 동문이라 더 재밌게 진행할 수 있었다. 프로젝트를 진행하며 여태까지는 직접 그림판에 디자인했었는데, 이번에는 디자인팀과의 협업을 거의 처음으로 해봤다. 소통과 회의라는 것은 정말 쉽지 않지만 그만큼 책임감을 가지고 임할 수 있었다. 또한 최종에는 디자인팀 분들을 더 모셔오면서 7명의 팀이 되었는데, 이 팀의 리더로서 무조건! 반드시! 게임을 성공적으로 출시해야 한다는 책임에 더 열심히 임할 수 있었다.

 이 경험을 바탕으로 더 성장하는 내가 되었으면 좋겠다. 나중에 게임을 업데이트 할때는 RxSwift와 같은 비동기 처리 툴도 사용해보고 싶다. 항상 배움을 잃지않는 내가 되자. 항상 1. 메모리 2. 속도 3. 코드 길이(재활용) 이 세가지 본질을 잃지말고 추구하자 파이팅이다!

 

 

GitHub - josushell/ZOO_company: (주) ZOO 컴퍼니에서 살아남기

(주) ZOO 컴퍼니에서 살아남기. Contribute to josushell/ZOO_company development by creating an account on GitHub.

github.com