비주얼 타이머를 개발해서 2019년 6월에 출시했고 1년여 시간이 지나 1만 건 다운로드를 넘겼다. 현재는 목표로 했던 기능은 대부분 구현했고 성능 문제 개선, 사용자 피드백 적용을 고민하고 있다. 개발을 시작하면서 어렴풋이 정한 마일스톤인데 달성해서 기분 좋다.

비주얼 타이머는 남은 시간을 직관적으로 확인할 수 있도록 진행 상황을 표시해주는 시각화 타이머로, 소리, 색상, 문구 등 다양한 개인화 기능을 제공한다. 앱은 React native를 사용했고 공지 등 서버 자원은 Azure Function App, 이슈와 리퍼지터리는 Github에서 관리했다.

1만 달성 🙌

개발 동기

  • 생산성 도구를 만들어보고 싶다
  • 프로젝트로 실무 감각을 유지하고 싶다
  • 사이드 프로젝트로 작은 앱은 만들어 봤으니 조금 큰 앱을 만들고 싶다
  • 애플 개발자 등록 비용이라도 벌고 싶다

개발 전

이전부터 생산성 도구에 관심이 많아 만들어보고 싶었다. 타이머는 나도 자주 사용하는 앱 중 하나였고 앱스토어에서 새로운 앱은 없나 종종 검색하기도 했었다. 생산성 앱을 만들겠다는 결정을 하고 나서 다음 과정을 거쳤다.

이전에 시험 삼아 만들어본 팁 계산기 앱에서 얻은 경험을 바탕으로 무슨 앱을 만들지 결정했다.

  • 앱 이름에서 제공하는 기능을 명확하게 알 수 있도록: 별도로 마케팅할 가능성이 작으니까 나는 앱스토어 검색에 최적화하는 쪽으로 기울었다. 앱스토어에서 다양한 키워드로 검색해보니 검색 키워드가 앱 이름이면 유명한 앱보다 먼저 검색 결과에 노출되는 경우가 많았다.
  • 앱스토어에서 검색했을 때 경쟁 앱이 적당히 있어야: 큰 카테고리에서는 검색 순위에 밀릴 수 있어도 조금이라도 카테고리가 좁아지면 상위 순위에 나와야 한다. 검색했을 때 경쟁 앱이 전혀 없다면 사람들이 검색하지 않는 키워드일 가능성도 크다.
  • 기존 앱에서 개선할 만한 부분 확인하기: 다른 앱도 사용해보고 그 앱 리뷰도 보고 업데이트 로그를 보니 이런 앱을 만들며 겪을 피드백과 개선 과정을 이해하는 데 도움이 되었다. (물론 로그를 대충 적는 앱, 이해하기 힘든 리뷰도 많지만)

타이머 자체 기능도 당연히 중요하지만 사소한 개인화 기능을 요청하는 리뷰가 많았다. 그래서 다양한 설정을 할 수 있는 타이머를 만들어보기로 했고 가능한 한 대부분의 설정을 변경할 수 있도록 계획했다. 그 중에도 아동을 대상으로 한 개인화 기능은 유료로 제공해서 수익을 만들기로 했다.

개발 과정

프로젝트는 React native를 사용하기로 했다. 웹앱은 생각보다 느린 반응이 눈에 보여서 Flutter와 RN 사이에서 고민했는데 지금은 많이 개선되었지만 당시에 Flutter로 만들었다는 앱을 써보니 웹앱보다는 빠르지만 약간 느린 인상을 받아서 RN으로 결정하게 되었다.

Expo도 편의 라이브러리가 많아서 좋지만, 결정 당시에는 한국어 입력 문제가 있어서 바로 react native를 사용하기로 했다.

초기에 고려하지 않은 탓에 큰 작업이 되었던 분할 화면

처음에는 공통으로 사용하는 컴포넌트를 만들어서 다른 프로젝트를 하게 되더라도 활용할 수 있도록 계획을 세웠다. 설정 저장/불러오기나 국제화 지원과 같이 전역에서 사용되는 라이브러리는 큰 문제 없이 계획한 방식대로 만들 수 있었다. 다만 생각처럼 되지 않은 부분은 표현 컴포넌트였다. RN에서 기본적인 컴포넌트를 많이 지원하긴 하지만 iOS 스타일의 크고 작은 표현 컴포넌트는 다른 라이브러리를 사용하거나 직접 만들어야 했다. 공개된 라이브러리가 많이 있지만 필요한 기능은 극히 일부인 데다 스타일 일관성을 맞추는 노력이 생각보다 많이 필요로 해서 직접 만들게 되었다. 이렇게 부분적으로 기능하는 컴포넌트는 당장에는 잘 동작하지만, 특정 상황만 고려해서 만든 탓에 다른 프로젝트에서 쓰기엔 조금 부족했다.

디자인은 iOS 기본 컴포넌트를 거의 따라가서 큰 막힘은 없었다. 아직도 약간씩 어색한 부분이 있지만 대부분 컴포넌트가 나름 비슷하게 동작한다. 그 외는... 좀 대충 만든 부분도 있지만 그래도 동작은 하니까 일단 뒀다. (타이머 순서 바꾸는 부분이라든지 매우 안 이쁘다.)

기능 중 완료 이미지 모음 리소스를 만드는 데 가장 많은 시간이 걸렸다. 완료 이미지 모음은 동물 등 이미지와 함께 이미지에 맞는 효과음이 타이머 완료 시에 표시되는 기능이다. 적합한 저작권을 가진 리소스를 찾고 정리해서 넣는 과정에 많은 시간을 썼다. 직접 일러스트라든지 그릴 수 있었다면 시간을 좀 더 줄일 수 있었을까.

서버는 Azure Function App을 사용하고 있는데 공지를 그냥 JSON 형식으로 내려주는 수준이라 별도의 데이터베이스 없이 작성했다. 그런 덕분에 응답도 빠르고 비용도 안 들어서 지금까지 그대로 유지되고 있다.

개발은 출시까지 4개월 정도 걸렸는데 개발은 1달 반에서 2달 정도 걸렸고 나머지는 앱 리소스(소리 효과, 이미지)와 앱스토어 메타 정보(앱 소개, 정책 등)을 준비하는 데 시간을 썼다.

사소하지만 품이 많이 드는 작업. 아이폰, 아이패드, 언어별로.

개발이 완료된 후에는 이상한모임 분들에게 테스트를 부탁해서 진행했다. 이 과정에서 앱이 어떤 맥락과 흐름에서 동작해야 하는지 피드백을 많이 주셔서 큰 도움이 되었다.

마케팅

홍보는 페이스북 그룹, 이상한모임 채널, 클리앙, 내 소셜 계정에서 진행했다. 소개 글과 함께 프로모션 코드도 나눴다. 다운로드 수는 확실히 이런 홍보를 할 때마다 오르긴 했지만 액티브 유저 수 추세는 큰 변동이 없이 항상 일정하게 증가했다. 돈을 쓰지 않아서 그랬을까, 프로모션 코드도 돈이라고 생각한다면 업데이트마다 꽤 지출했다고 볼 수 있겠다.

가장 큰 도움이 되었던 Back to the Mac 그룹

페이스북 타깃 광고는 제공하는 도구로 확인해보니 적어도 광고 본 사람 중 30% 이상은 받고 결제해야 손익 분기인 비용이라 집행하지 않았다...

그래도 적은 수지만 앱스토어에서 검색해 받는 사람도 꾸준히 있다. 메타 정보를 작성하는 일도 생각보다 쉽지 않았다. 업데이트마다 조금씩 변경하고 있지만, 검색 지표가 명확하게 공개돼 있지 않아서 솔직히 잘 모르겠다. (어떤 키워드로 검색했는지 등)

검색 비중이 높다

개선과 피드백

앱 출시 이후 성능 문제를 계속 개선하고 있고 사용자 피드백도 그 과정에서 반영하고 있다. 초기에는 불안정하게 동작하는 부분이 있어서 못쓰겠다는 리뷰 받고 참담했는데 부지런히 안정화했다. 이 과정에서 Xcode의 프로파일링 도구가 큰 도움이 되었다.

피드백은 앱 내 이메일 보내기로도 오지만 대부분은 앱스토어 리뷰로 남긴다. 만족도를 물어보고 이메일로 보낼지 앱스토어 리뷰로 보낼지 구분하겠다고 백로그에 적어두고는 그동안 잊고 지냈... 그동안 작은 피드백도 많이 받았는데 이미 개인화에 중점을 두고 만든 덕분에 대부분 피드백은 간단하게 추가할 수 있었다.

좋은 리뷰와 탄탄한 피드백은 늘 힘이 된다

다만 구조적인 변화가 필요한 피드백은 백로그로 쌓고 있다. 가령 여러 타이머를 동시에 사용하거나 순서대로 동작하도록 하는 방식은 현재 구조에서는 처음에 염두에 두지 않았던 부분이라 구현이 어렵다. 좀 더 정리해서 차기 버전에서는 구현할 수 있는 구조로 가고 싶다.

아쉬운 점

사용자 추적

앱스토어커넥트에서 제공하는 분석 도구는 피상적인 수준이라 실제 데이터를 볼 수 없다는 점이 늘 불편하다. 실제 사용은 앱을 실행했을 때 버전 확인과 공지 데이터를 받기 때문에 애저 펑션앱 사용량으로 확인할 수 있지만, 여전히 부족하다. GA 등 추적 도구를 사용해서 어떤 기능을 주로 사용하는지 등 데이터를 모으면 개발 방향을 잡는 데 더 도움이 될 텐데 그러지 않았다. 처음부터 이 부분이 중요하다는 이야기를 들었는데도 지금까지 적용하지 않은 것은 정말 반성해야 한다.

약한 수익 모델

현재는 인앱 결제로 수익이 발생하고 있다.

  • 완료 시 나오는 이미지 모음
  • 개발 지원하기

처음 앱을 기획할 때는 아동용 타이머로 방향을 잡고 있어서 완료 이미지 모음을 인앱으로 제공했다. 앱을 공개한 이후에는 아동용 타이머 외에 일반 용도로 많이 사용하길래 커피 지원하기 같은 개발 지원 항목을 추가했다. 한국에서는 개발 지원이, 미국 스토어에서는 이미지 모음 결제가 많다. 내가 쓰는 시간을 생각하면 아직도 많이 부족하지만 그래도 앱개발자 등록 비용은 해결할 수 있게 되었다.

다음 버전에서는 인앱을 정리하고 유료로 전환하는 것을 고민하고 있는데 이런 결정은 어떻게 해야 하는 걸까, 사용자에게 무슨 가치를 주고 있는지, 그 비용은 어떻게 결정해야 할지 배우고 싶다.

안드로이드 지원

처음에는 RN이면 안드로이드 지원은 저절로 되겠지 생각했는데 실제로 돌려본 결과는 처참했다. iOS는 그래도 작게나마 해본 경험이 있어서 트러블슈팅도 큰 문제가 없었는데 안드로이드는 구동 자체도 쉽지 않았다. 차기 버전에서는 안드로이드도 같이 지원하고 싶다.

자동화와 테스트 부족

지금 앱은 일부 로직에 유닛테스트가 있지만, 아직 많이 부족하고 자동화도 없다. 앞으로 개선해야 하는 부분이다.

잘한 점

React Native

React로 많은 프로젝트를 해보지 않았는데 이 앱을 만들고 관리하면서 많이 배웠다. 정말 매달 새로운 기능과 라이브러리에 눈과 머리가 바쁘다. 출시 이후에도 react에 hook도 들어오고 많은 새 기능이 들어오고 있는데 업데이트 과정에서 조금씩 적용하고 개선하며 많이 배우고 있다.

확실히 js나 react에 경험이 있다면 큰 문제 없이 RN 프로젝트도 꾸릴 수 있다. 물론 타깃 플랫폼도 잘 알면 더 편하다. 특히 기능이나 용어는 각 타깃 플랫폼에서 제공하는 용어로 많이 설명되고 있어서 안드로이드와 iOS의 용어 차이를 알아두는 것도 도움이 됐다.

다국어 지원

처음부터 고려하지 않았다면 꽤 번거로운 작업이 되었을 텐데 지금 와서는 잘 한 결정 중 하나다. 현재 영어랑 한국어만 지원하지만 지금까지 지역 통계를 보면 확실히 도움이 되었다. 마케팅도, 프로모션도 대부분 한국에 했는데 다운로드 비율이나 수익 비중은 미국이 더 크다.

프로모션 코드는 한국에서 많이 뿌렸는데 조금 억울

우선순위와 시간 관리

혼자 하는 프로젝트는 내 마음대로 할 수 있다는 장점이 있지만, 한편으로는 정말로 제한적인 시간 자원을 어떻게 관리하는가 하는 점이 정말 큰 어려움이었다. 특히 내가 안 하면 아무것도 진행이 되지 않는다는 점이 당연하지만 피곤했다.

다행히 처음에 계획한 최소 기능은 대부분 만들어냈고 학업 하면서도 크고 작은 개선 작업을 진행했다. 출시 이후에는 적은 분량이라도 한 달에 한 번은 업데이트 할 수 있도록 노력했다. 지금까지 받은 피드백 중 적용된 것은 일부지만 개선 과정에서 좋은 리뷰도 많이 받을 수 있었다.


이 이전에도 앱을 만들어본 경험은 있지만, 하루 정도면 만들었던 수준의 앱과는 경험의 깊이가 달랐다. 특히 앱은 웹과는 확실히 다른 맥락과 인터페이스를 갖고 있었다. 앱을 출시하는 경험은 평소라면 별 생각 없이 쓰던 수많은 앱을 교과서처럼 보이게 했다. 아름답고 멋지고 편리한 기능을 모두 앱에 적용하고 싶었지만 작아도 정교한 기능은 보는 것과 다르게 훨씬 많은 시간을 필요로 했다. 쉽진 않겠지만 다음엔 그런 부분도 꼼꼼하게 구현해보고픈 욕심도 생겼다.

지금 이렇게 되돌아보면 잘한 점보다 부족한 점도 많고 배워야 할 부분도 산더미다. 그래도 부족한 부분이 많지만 유용하게 사용하고 있다는 리뷰 하나하나가 큰 힘이 됐다. 앞으로 개선될 타이머와 만들어갈 많은 프로젝트에서도 이런 경험이 거름이 되었으면 좋겠다.

애플 앱스토어에서 보기

App Screenshot

iOS 앱 Tiny Tip Calculator를 만들었다.

계기

매번 식사를 밖에서 할 때마다 팁을 계산하는 모습을 보고 간편한 팁 계산기가 있으면 좋겠다고 생각했다. 그래서 앱스토어에서 받으려고 검색했는데 수많은 팁 계산기가 다음 부류였다.

  • 광고가 지나치게 많아서 사용성을 크게 해침
  • 결과를 보기까지 인터렉션이 너무 많이 필요
  • 그냥 안 이쁨

어떤 앱은 세 가지 모두에 해당했다. 그래서 간단한 앱을 하나 만들기로 했다.

도구 선정

집에 있는 모든 사람이 아이폰을 사용하고 있어서 iOS 앱을 만들기로 했고 무엇으로 개발할지 고민했다.

  • Swift는 일단 네이티브니 성능도 좋고 원하는 만큼 뜯어고칠 수 있겠지만 익숙하지 않았다. 물론 만들면서 배우는 것만큼 학습에 좋은 방식은 없지만 원하는 결과물을 만들어 내는 데 집중하고 싶었다.
  • React Native도 고려했다. React Native의 툴링도 좋고, 성능도 마음에 들고 예전보다 확실히 리소스가 많았다. expo.io도 멋지다.
  • Ionic은 Cordova와 Angular를 잘 섞은 프레임워크였는데 아무래도 웹앱이라서 성능에 대한 걱정이 들었다. 그래도 문서도 정리가 잘 된 편이었고 네이티브 기능을 쉽게 사용할 수 있도록 플러그인도 많이 제공했다.

React도 계속 공부하고 써보려고 하고 있지만, 여전히 Angular가 익숙한 데다 생각보다 Ionic의 성능이 좋아서 Ionic으로 선택했다. React에 좀 더 익숙하다면 React Native를 고민 없이 선택했을 것이다. 결과물을 빠르게 보겠다는 생각 탓에 편향적인 결정을 내렸다.

개발 목표

개발하기 전에 다음 목표를 정했다.

  • 내장 키보드 말고 키패드 직접 만들 것, 계산기처럼 모든 기능이 보이도록.
  • 입력 횟수를 최소로 하기.
  • 팁 비율을 목록으로 보여줌. 비율은 프리셋 설정할 수 있도록.
  • 이쁘고 깔끔하게, 슬라이드 같은 것도 넣지 말고. 테마도 지원하면 좋겠음.
  • 앱처럼 보이도록!

개발 계획

프로젝트를 생성할 때 정도만 문서를 봤고 Angular는 이미 익숙해서 평소 작업하듯 만들었다. 로직은 별문제 없이 만들었지만, 스타일에 수고가 많이 들었다.

전체적으로 페이지 수는 얼마 되지 않았다.

  • 계산 페이지
    • (계산 모드)
    • (결과 모드)
  • 설정 목록
    • 통화 및 팁 비율 설정
    • 테마 설정
    • 소개 페이지

Angular는 각 컴포넌트나 디렉티브, 모듈의 선언적 관리가 전체적인 코드 구성에 정말 편리했다. TypeScript와 함께 궁합도 너무 좋았다. 프레임워크 답게 전체적인 만듦새나 구조는 Angular가 확실히 더 이해하기 좋게 느껴진다.

앱에서 필요한 부분은 모두 ionic에서 제공하는 플러그인으로 충분했다. Storage 등도 이미 다 고수준으로 제공하고 있는 데다 angular에서 손쉽게 의존성 주입으로 활용할 수 있었다.

Good & Bad

웹과 다른 흐름의 도구를 제대로 만들어서 결과를 낸 것은 처음이었다. 이 과정에서 좋았던 점은 다음 같았다.

  • 익숙한 도구를 선택해서 빠른 결과를 냈다. 첫 PoC를 만드는 데 하루 걸렸고 전체적으로 코드를 정리하고 작성하는데 3일 정도 사용했다. 테마 구현을 가장 고민했었는데 의외로 손쉽게 해결했다.
  • 다른 앱의 리뷰를 찾아보고 새로운 유즈케이스를 찾아 기능을 추가했다. 리뷰 중에 세금 불포함 계산 방식이 필요하다는 이야기가 반복되었지만 지원하는 앱이 거의 없었다. 그래서 해당 기능을 첫 릴리즈 이후 추가해서 배포했다.
  • 파워 유저(이자 아내)에게 빠른 피드백을 받을 수 있었다. 실제로 앱을 사용하는 과정도 옆에서 볼 수 있었던 점도 새로운 경험이었다. 버튼의 위치나 세부적인 기능에 대한 조언은 앱에 반영되었다.

아쉬운 점도 있었다.

  • 익숙한 도구를 사용해서 기술 배경에서 크게 도전적인 프로젝트는 아니었다. 게다가 네이티브 환경이 아니라서 이쁘지 않은 부분이 계속 눈에 걸렸다. 예를 들면 스크롤바가 화면 밖에 걸쳐 있다든지 하는 부분은 하이브리드앱에서 흔히 겪는 문제다. 이런 부분이 크게 사용성을 해치는 상황은 아니지만 안 이쁜 건 계속 거슬렸다.
  • 테스트가 미흡했다. Ionic과 Angular 모두 테스트에 유리한 환경을 기본적으로 제공하는데도 테스트를 부지런히 작성하지 않았다. 빠르게 작성하는 데는 성공했지만 좋은 품질을 유지하고 작성하는 일에는 소홀했다.
  • 수익성을 고려하지 않았고 프로모션도 하지 않았다. 다른 앱 리뷰에서 광고 얘기를 보면서 아예 광고를 생각하지 않았다. 지금이야 크게 나쁜 결정이라는 생각은 들지 않지만, 애플 개발자 계정을 연장할 때면 왜 이런 결정을 했을까 생각 들 것 같다.

결과

이미 많은 앱이 존재해서 검색 목록 위로 올라가는 일조차 쉽지 않지만 어떤 방식으로든 결과물을 빠르게 냈다는 점에 즐거웠다. 그리고 작은 앱이더라도 옆에서 부지런히 쓰는 사용자가 있어서 재미있었다. 다음 또 앱을 만든다면 어떤 점을 미리 고려해야 하는지도 배웠다.


트러블 슈팅

iOS에서 overflow로 생성한 스크롤 부드럽게(?) 하기

스타일에서 overflow 프로퍼티를 적용하면 웹 페이지 내에 스크롤을 넣을 수 있다. 그런데 iOS에서는 그 스크롤을 써보면 일반적으로 경험할 수 있는 스크롤처럼 부드럽지 않고 뻑뻑하게 움직인다. 이 동작을 iOS의 기본 동작처럼 바꾸려면 다음 스타일을 추가로 넣어야 한다.

--webkit-overflow-scrolling: touch;

최근 모델의 notch 해결하기

이 스타일은 Safari만 지원한다.

먼저 웹뷰의 페이지에서는 기본적으로 상단의 스테이터스바를 안전 영역으로 처리한다. 그래서 고정 영역을 예로 들어 top: 0로 설정해도 스테이터스바 바로 아래에 자리를 잡는다. 화면 전체를 웹 영역에 넣고 처리하고 싶은 경우에는 viewport-fit을 viewport에 추가해야 한다.

viewport-fit은 다음 3가지 값을 지원한다. csswg 문서에 그림을 잘 그려놨다.

  • auto: 기본 값으로 아무 동작하지 않는다.
  • contain: 웹뷰 영역이 잘리지 않도록 디바이스 영역에 맞춘다.
  • cover: 디바이스 영역에 빈틈이 없도록 웹뷰 영역을 맞춘다.
<meta name="viewport" content="viewport-fit=cover, ..." />

이제 화면 전체 영역을 사용할 수 있게 되었다. 이제 스테이터스바 영역만큼 밀어야 할 필요가 생긴다.

header.global {
    padding-top: 20px;
}

하지만 이렇게 고정값을 넣으면 노치가 없는 기기에서 높이가 이상해진다. 이런 상황에서는 safe-area-inset-* 상수를 사용하면 된다.

heaader.global {
    padding-top: constant(safe-area-inset-top); /* iOS 11.0 */
    padding-top: env(safe-area-inset-top); /* iOS 11+ */
}

constraint()는 없어질 예정이며 이후로는 env()를 사용하면 되겠다.

CSS Grid

고정된 화면을 기준으로 각 컴포넌트를 배치하는데 css grid가 정말 편했다. vw, vhcalc()를 함께 쓰면 어떤 컴포넌트든 원하는 위치에 놓을 수 있었다.

position: sticky

stickyposition 프로퍼티에 사용하면 필요한 요소를 고정으로 띄울 수 있다. 예전엔 위치 계산해서 fixed를 직접 설정해줬어야 했는데 손쉽게 구현할 수 있다.

다만 iOS에서 sticky에 배경을 지정해도 위에 1px 정도 아래 엘리먼트가 보이는 문제가 있었다. transform: translateY(-1px)를 추가해서 약간 비틀어서 해결되었고 GPU 가속이 동작해선지 약간 버벅이던 동작도 없어졌다.

:host-context()

하위 컴포넌트가 상위 컴포넌트의 스타일에 따라 스타일을 제어해야 할 때가 있는데 여기에 :host-context()를 사용할 수 있다. [theme]과 같은 디렉티브를 사용해서 부모 엘리먼트에 클래스를 추가하도록 했다면 :host-context()로 해당 클래스를 추적하는 것이 가능하다.

// hello.theme.scss
:host-context(.theme--hello) {
    * {
        font-family: 'Arial Rounded MT Bold', sans-serif;
    }

    header {
        color: #ff00c3;
    }
    // ...
}

현재는 이렇게 만든 각 테마 scss를 한 곳에서 모두 불러오는 구조로 되어 있다. 이 구현을 다시 한다면 중간에 테마를 제어하는 컴포넌트를 ViewEncapsulation.None로 놓고 테마 스타일을 동적으로 불러오도록 처리하고 싶다.

오버 스크롤 끄기

하이브리드앱을 가장 하이브리드앱처럼 보이게 하는 동작 중 하나가 오버 스크롤이다. 이 동작으로 전체 레이아웃이 고정된 부분 없이 움직이면 앱처럼 느껴지지 않는다. Cordova에서는 다음 속성을 config.xml에 추가하면 이 문제를 해결할 수 있다.

<!--config.xml in the project-->
<preference name="DisallowOverscroll" value="true" />

Version API 만들기

최신 버전을 확인하고 새 버전이 있다면 업데이트를 하도록 작은 버튼을 띄워주고 싶었다. 예전엔 블로그용 서버가 있어서 코드를 작성해서 올리면 되었겠지만 이제는 정적 블로그를 사용하고 있어서 아무래도 제한되는 부분이 있었다. 요구 사항은 이랬다.

  • 적어도 response header를 제어할 수 있어야 함
  • 반환값은 하드 코딩이어도 큰 상관 없음, 디비 연동도 필요 없음
  • 비용으로 가장 저렴할 것 (연 5불 이하)
  • 인증 필요 없음
  • https 지원
  • 관리는 최소로

그래서 Azure Functions를 선택하게 되었다.

module.exports = async function (context) {
    context.res = {
        headers: {
            'Content-Type': 'application/json',
        },
        body: {
            version: '1.1.4',
            tagline: 'New Theme: Mono',
            link: 'https://itunes.apple.com/app/tiny-tip-calculator/id1448227957?mt=8'
        }
    };
};

아쉽게도 현재 netlify에 물려 있는 도메인을 사용해서는 API 주소를 https로 사용할 수 없었다. Azure의 앱서비스 내로 도메인을 가져와야만 A 레코드로 사용할 수 있었고 CNAME은 https의 인증서에 문제가 있다고 접근이 되질 않았다. 큰 기능의 API도 아닌 탓에 그냥 기본으로 제공하는 azurewebsites.net의 서브 도메인을 사용했다.

Azure Functions의 비용은 앱 서비스 플랜과 종량제 플랜 중 하나를 고를 수 있는데 앱 서비스 플랜의 경우는 앱 서비스를 사용해서 function app을 실행하는 방식으로 앱 서비스만큼 비용을 내야 하고 종량제는 쓰는 만큼 비용을 지불하는 방식이다. 내 경우에는 크게 사용량이 많지 않을 것이라는 판단에서 종량제 플랜을 선택했다.

하지만 종량제 플랜에서는 Cold start가 너무 느렸다. 앱서비스 플랜은 인스턴스를 계속 띄우고 있기 때문에 항상 빠른 응답을 받을 수 있다. 하지만 종량제 플랜에서는 Function app 호출이 특정 시간 동안 없을 땐 해당 코드가 구동되는 인스턴스를 없엤다가 호출이 있을 때 인스턴스를 다시 생성해서 코드를 올려 구동한다. 그래서 인스턴스가 올라와 있는 상황에서 호출하면 warm start로 응답이 100ms 미만으로 매우 빠르지만, cold start는 인스턴스를 올리는 것부터 시작하기 때문에 더 오랜 시간이 걸렸다. 내 경우에는 코드가 의존성도 없고 매우 단순한데도 8초에서 22초까지 걸렸다. 비용은 코드를 실행해서 결과를 반환하기까지 시간에 대해서만 청구되기 때문에 비용적으로 문제는 없었다.

현재 사용량이 상당히 적은 상태인데 이 문제 때문에 앱 서비스 플랜을 사용하고 싶지는 않았다. 그래서 해결 방법을 찾아보니 그냥 계속 살아 있도록 function을 호출하는 방법이 있었다. 그래서 5분 간격으로 function app을 실행하도록 Timer 트리거를 추가했다.

{
  "bindings": [
    {
      "name": "myTimer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 */5 * * * *"
    }
  ]
}
module.exports = async function () {
    console.log('health checked');
};

만약 사용량이 많아지면 Azure Functions는 알아서 스케일링을 수행한다. 스케일링을 수행하면 새 인스턴스가 생성되면서 cold start가 다시 발생할 수 있지만, 현재는 두 API 모두 큰 자원을 소모하지 않고 있고 사용량도 적기 때문에 계속 warm start를 유지할 수 있었다. 만약 다른 인스턴스가 올라가서 다시 cold start가 감지된다면 그때는 앱 서비스 플랜으로 변경할 생각이다.

현재까지 비용 예측은 월간 $0.25 정도고 이조차도 Functions 호출 비용보다 코드가 저장된 공간인 스토리지의 비용이 대부분을 차지하고 있다.

빌드와 배포

빌드와 배포 과정에서 문제가 되었던 부분이 몇 있었는데 검색으로 쉽게 해소했다.

  • Xcode의 모던 빌드 시스템을 사용하면 문제가 생긴다. 빌드를 생성할 때 다음 플래그를 추가한다.
    $ ionic cordova build ios -- --buildFlag="-UseModernBuildSystem=0"
    
  • Xcode에서 **File > Project Settings...**에 들어가서 Build System을 Legacy Build System으로 변경한다.
  • Signing에 문제가 있다고 나오면 Automatically manage signing의 체크 박스를 해제하고 Xcode를 다시 실행해서 다시 체크한다.
  • 일반적인 암호화 외의 기능을 사용하면 앱 배포에 추가적인 절차가 필요하다. 하지만 단순히 API 호출에 HTTPS를 사용하거나 인증 절차에 사용하는 경우에는 예외에 해당한다. info.plistITSAppUsesNonExemptEncryptionNO로 설정한다.

예전부터 cocos2D나 unity를 배워보고 싶었는데 몇 번 글을 보고 따라해봐도 감이 안와서 미뤄왔다. 우연히 SpriteKit 튜토리얼을 보고 따라하다보니 생각보다 쉽게 결과물이 나오길래 게임 만들어보자 마음 먹고 매일 문서 찾아보며 조금씩 만들어가고 있다. 아직 Objective-C도 익숙하지 않지만 CS 193P iPhone Application Development에서 야금야금 들었던 내용 가지고 하나씩 배우고 있다.

다음 링크는 진행하면서 도움이 된 글들을 간단하게 정리해봤다. 애플 공식 문서도 깔끔하게 정리되어 있는데 좀 예제가 적은 편이라서 기본적인 내용은 공식 문서에서 확인하고서, 실제 사용하는 방법들은 구글링, Stackoverflow에서 찾을 수 있었다.1

기초

다음 튜토리얼에서 게임에서 기초적으로 필요한 이미지 불러오기, 터치 이벤트 처리, 간단한 충돌 처리, 노드 animate 방법 등을 배울 수 있다. 리소스도 제공하고 단계별로 잘 설명하고 있어 쉽게 시작하는데 도움이 된다.

디자인/그래픽 관련

여러 장의 이미지로 에니메이션을 처리하거나 한 장의 이미지로 여러 node를 만들려고 할 때 다음 글이 도움이 된다. 이미지 하나로 처리하는 방법은 미리 SKTexture textureWithRect:inTexture:로 텍스쳐를 만들어두고 사용하면 된다.

점수나 안내 문구를 SKLabelNode로 사용하려고 했었기에 임베드 폰트 넣는 방법을 찾아봤다. SKShapeNode로 벡터 처리도 가능하다.

버튼 관련

시작, 다시하기, 처음으로 등 버튼을 만들기 위해 찾아본 버튼 관련 내용이다. 꼭 버튼 뿐만 아니라 SKNode에 다 적용되는 내용이라 이해하는데 도움이 되었다.

데이터 저장하기/불러오기

High Score를 저장하기 위해 찾아봤는데 여러 저장 방법이 있지만 NSUserDefaults를 사용하기로 했다.

Footnotes

  1. 아직 나온지 얼마 안되서 그런지 검색이 잘 안되는 경향이 있다.

한국 다녀오기 전후로 계속 바뻐서 한동안 새로운 일을 벌리지 않기도 했지만 사실 잡다한 생각이 너무 많아 게임을 그닥 하질 않았다. 이 게임도 커뮤니티서 얘기가 나오길래 이전에 받아두고 공항에서 Boarding 대기하다가 처음으로 실행해봤는데 왠걸 너무 재미있었다. 게다가 최근 UI가 대폭 변경됨으로 아이템 구입 등 다양한 메뉴가 더욱 직관적이고 쉽게 변화했다. 이전 상점의 Depth와 UI로는 분명 수익도 별로 없었을 것 같단 생각이 들 만큼 조잡하고 복잡했었다.

이전까지는 무얼 눌러야 시작할지 모를 정도로 복잡했는데 훨씬 깔끔해졌다

다양한 게임 모드를 지원하는데 처음 시작하면 단계별로 몇번씩 해야하는 횟수를 채워야 다른 모드를 진행할 수 있다. Timed나 Skull 같은 어려운 모드도 존재하는데 게임 하면서 스트레스 받기 싫으므로(?) Cupcake 모드를 주로 하는 편이다. 이전까지만 해도 위 화면에 Shop이니 뭐니 우겨넣어진 상태여서 참 북적북적한 화면이었는데 참 바람직하게 개선되었다.

여러가지 게임 모드가 있는데 Auto로 표시된 것은 과일을 내리자마자 자동으로 터진다.

위에서 과일을 던지면 색이 맞는 Blob에 놓아 터뜨려 진행하는 방식이다. 테트리스와 같이 칸을 가득 채워 버리면 게임이 끝나는 형태로 기둥에 눈금이 있어 반대방향이 높게 쌓이더라도 쉽게 알 수 있다. 몇차례 하면 Wave가 증가하며 속도가 빨라지는 전형적인 블럭 퍼즐게임인데 속도가 빨라지는 게임에 특히 약한 내 경우에도 두어개 놓칠 뿐 할만한 속도까지만 빨라진다.

만약 과일과 Blob의 색이 다른 경우 처치 곤란이 될 때가 있는데 같은 과일을 3개 쌓으면 그것도 터진다. 횡으로는 안터지므로 옆으로 쌓는 일은 하지 말자. 그리고 이게 위로만 쌓는 형태라서 생각보다 콤보를 만들어내기 힘든데 (사실 콤보에 혜택은 전혀 없다. 하지만 콤보로 터뜨리면 뭔가 뿌듯해져…) 한칸 떨어진 Blob 사이에 같은 색 과일을 넣어 연결하는 식으로도 플레이 가능하다. 다양한 아이템도 있으니 아이템을 활용하면 더 화려한 플레이가 가능해진다.

모아서 한방에 터질 때 그 느낌은 스냅샷으로 담아지질 않는다!

게임 중에 사용할 수 있는 여러가지 아이템들이 있지만 내 경우에는 과일 내리는데 정신 없어서 아이템을 제대로 써본 적이 없다. 매일 지급하는 아이템도 있고 코인으로 구입할 수도 있다. 요즘은 흔해졌지만 일종의 퀘스트로 미션도 있어서 미션 수행하면 혜택을 준다.

SNS 등록해서 친구와 등록하고 이런 부분도 다 있긴 있다. Social Notification 노이로제에 걸린 사람들이 과연 이런 기능들을 활성화 하고 사용하고 있을지 모르겠지만… 근래 페이스북이나 트위터에서 이런 게임 알림을 본지 정말 오래된듯 싶다. 여튼 주변 사람들과의 경쟁도 좋지만 과거 오락실에 High Score처럼 명예의 전당도 좀 남겨줬으면 좋겠다.

플레이 시간에 비해 보상이 적다…

속도 제한이 있는 퍼즐 게임은 속도가 빨라지기도 하고 잘못 놓으면 스트레스를 받는 편이라 잘 안하는 편이지만 그런 스트레스도 덜하고 Blob이 줄줄이 터지면 화려한 음향 효과와 함께 답답한 감정도 한방에 날리는 재미가 있다. 가끔 단판으로 해야지 붙여놓고 한번 더 한번 더 게임을 진행했다가 시간 워프할 수 있으므로 주의할 것. 편의를 위해 앱 링크를 남기는데… 이러니 진짜 광고같은 기분이 난다 -_-

Apple Appstore / Android Appstore

색상을 바꿔요

눈에 편한 색상을 골라보세요 :)

Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.