걸어서 땅따먹기 서비스, 그라운드 플립을 거의 3달째 운영중이다. 대부분의 기능은 완성했지만 기획 단계에서 계획했던 한가지 기능이 남아서 후딱 개발했다. 이번에 추가한 기능은 업적기능이다. 주어진 목표를 달성하면 뱃지를 얻을 수 있는 기능이다. 이번 기능은 기획부터 디자인, 백엔드, 프론트 까지 혼자 개발하며 나에게 의미가 있던 기능이라 개발과정을 소개해보려한다.
우선 그라운드 플립은 서비스를 소개하겠다. 그라운드 플립을 한마디로 소개하자면 땅따먹기 만보기 서비스이다. 단순히 걷는 것에서 끝나지 않고, 걸은 길을 점령할 수 있는 시스템을 제공한다. 내가 걷는 경로가 내 땅이 되고, 다른 사람들이 점령한 영역도 뺏을 수도 있다.
걸으면 걸을수록 점령한 영역이 지도에 표시되고, 다른 사용자와 경쟁하는 재미를 느낄 수 있는 서비스이다. 현재 앱스토어와 플레이스토어에 배포되어 있으니, 언제나 다운로드해서 땅따먹기를 할 수 있다!
[iOS] : https://apps.apple.com/app/ground-flip/id6550922550
[Android] : https://play.google.com/store/apps/details?id=com.m3pro.ground_flip
[랜딩페이지] https://ground-flip2.imweb.me/
우리 앱에는 리더보드를 통해 다른 사용자들의 기록을 확인하고 경쟁심을 자극하여 땅따먹기 게임에 동기를 부여하는 기능이 존재한다. 하지만 많은 땅을 차지하더라도 이를 보상받는 느낌이 부족한 상황이었다. 이런 문제를 해결하기 위해 목표를 달성하면 보상처럼 뱃지를 획득할 수 있는 기능을 기획하게 되었다.
목표를 달성하면 뱃지를 제공하는 앱들은 이미 다양한 사례에서 성공적인 결과를 보여주고 있다. 예를 들어, 애플 피트니스, 나이키, 당근마켓 등에서 특별한 도전 목표를 달성했을 때 뱃지를 제공하여 사용자들의 참여를 유도하고 있다.
나 또한 애플 피트니스의 설날 맞이 특별 도전 목표 뱃지를 받기 위해 열심히 운동했던 기억이 있다. 이러한 경험을 바탕으로, 우리 앱 사용자들도 뱃지 획득을 목표로 더 적극적으로 앱을 활용하도록 유도할 수 있겠다는 생각에 이번 기능을 추가하기로 했다.
어떤 목표들을 추가할지 고민했다. 땅따먹기 앱인 만큼 땅과 관련된 목표들을 주로 넣고, 그 외 다른 활동을 통해 얻을 수 있는 목표를 추가하는 것이 적절하다고 판단했다. 그렇게 생각한 목표들이 아래 다섯 종류의 목표이다.
이렇게 기획한 목표들의 세부 달성 목표를 정하여 표로 정리해두고 디자인과 개발을 시작했다.
다은은 개발을 시작하기 전 디자인을 할 차례이다. 사실 이전 기능까지는 외주를 맡긴 디자인이 있어 디자인을 할 필요는 없었지만, 뱃지 기능은 디자인이 없어 내가 처음부터 디자인을 해야했다. 굉장히 막막했다. 디자인을 배운적도 없고, 피그마를 잘 다룰줄도 모르기 때문이다.. 😭😭
여러 뱃지 디자인을 피그마를 사용하여 만들어보고 그 중 제일 괜찮은 것 몇개를 추렸다. 복잡한 그림이나, 아이콘 같은 것들은 도저히 만들수가 없어서 최대한 다각형으로만 만들어보았다. (디자이너가 아니라 디자인이 조잡해도 이해해주시길…)
새로운 땅을 방문할 때 마다 땅이 쌓여 간다는 느낌으로 만들어보았다.
우리 앱에서 다른 유저들의 땅이 빨간색으로 표시 되고, 자신의 땅은 초록색으로 표시 된다. 다른 유저들의 땅을 초록색으로 채우면서 뺏는다는 느낌으로 만들어보았다.
랭킹은 색으로 차별점을 주고 싶었다. 여러 게임에 등장하는 티어 처럼 아이언, 브론즈, 실버, 골드, 플래티넘 느낌의 색으로 만들어보았다.
탐험가와 비슷할 수 있는 데 이번엔 꾸준하게 탑을 쌓는 느낌으로 만들어보았다.
ui 디자인도 해야 했다. 하지만 개발자라 그런지 피그마로 만드는 것 보다 코드로 바로 바로 만드는게 편했다. 그냥 바로 플러터를 사용하여 실제 동작하는 기능은 제외하고 위젯들을 사용해서 UI 를 내 느낌대로 만들어보았다.
기획과 디자인이 끝났으니 이제는 개발을 할 차례이다. 기획 단계에서는 아직 느낌정도만 있어 시스템을 설계하기 어려웠는데 디자인까지 끝내니 시각적으로 기능이 눈에 보여서 설계하기가 쉬워진 것 같다. 먼저 개발해야할 요구 사항을 정리하고 개발을 시작 했다. 요구사항은 아래와 같이 정의 했었다.
먼저 프론트 부분부터 개발했다. 사실 프론트는 특별한 것은 없다. 가장 중요했던 것은 확장성이었다. 지금 기획한 뱃지들만으로 유지 되는 것이 아니라 서비스를 유지 하다 보면 분명히 새로운 종류들의 뱃지들이 추가 될 것이었다.
따라서 뱃지 목록을 코드에 하드 코딩하는 것이 아닌, 서버에서 뱃지 목록을 실시간으로 받아 올 수 있게 했다. 이렇게 되면 추후에 뱃지가 추가되더라도 프론트는 수정을 하지 않아도 되었다.
그리고 프론트 측에서는 뱃지를 조회하는 기능만 만들면 되었다. 뱃지를 얻기 위한 목표 행위들은 이미 기능 구현이 되있는 상태이기 때문에 백엔드 쪽에서 해당 행위마다 업적 달성율을 업데이트만 하면 되기 때문이다.
가장 핵심이 되는 부분이다. 가장 중점을 둔 것은 확장가능성이다. 앞서 프론트 부분에서도 언급 했지만 확장될 가능성이 가장 큰 기능이었다. 크게 아래 2가지가 걱정되었다.
그래서 설계 단계에서부터 다양한 방향으로 확장될 가능성을 염두 해두기로했다.
그리고 업적의 목표를 정해보니 크게 2가지 분류로 나누어야했다.
각 테이블 별로 주요 특징만 소개한다.
next_achievement_id
: 누적형 업적을 위해 필요하다. 해당 업적의 다음에 진행되는 업적의 id를 저장해둔다. 만약 누적형 업적의 마지막이거나 단발성 업적이라면 null
이 들어간다.reward_type
, reward_amount
: 추후에 보상 기능이 추가될 경우 활용하기 위해 추가해두었다.start_at
, end_at
: 기간 한정 업적이 필요할 경우를 대비 하여 추가해두었다.current_value
: 유저가 해당 업적에서 지금 가지 달성한 값이 들어간다.obtained_at
: 업적의 목표를 달성하여 뱃지를 획득한 날짜가 들어간다.is_reward_received
: 추후 추가 될 수 있는 보상을 받았는지의 여부가 들어간다.로직은 크게 2가지로 나뉜다.
이 모든 로직을 하나의 클래스에서 구현하기에는 무리가 있다고 판단했다. 2가지 로직의 성격이 달랐고, 업적을 업데이트 하는 로직은 추후에 새로운 기능이 많이 추가 될 가능성이 있었기 때문에 2가지의 클래스로 분리하여 구현했다.
AchievementReader
: 업적을 조회하는 비즈니스 로직만을 담당한다.AchievementManager
: 업적을 업데이트하는 비즈니스 로직만을 담당한다.위 그림의 로직대로 업데이트가 이루어진다. 누적형 업적의 경우 연관된 모든 업적이 같은 카테고리에 속하기 때문에 카테고리 id 로 업적 진행 여부를 찾고 가장 상위 업적을 업데이트 해주면 된다.
user_id
가 1이고 category_id
가 1인 업적 업데이트를 하는 경우user_achievement
의 id 가 4인 데이터의 current_value
가 32로 업데이트단발성 업적의 경우 해당 업적의 획득 데이터가 없는 경우에만 업적 진행상황을 업데이트 해주면 된다.
업데이트 로직의 경우는 해당 업적의 목표 행동이 일어난 경우에만 호출해주면된다. 예를 들어 새로운 땅을 방문하는 요청이 들어오거나, 다른 유저의 땅을 빼앗는 등의 요청이 들어 올때이다. 해당로직에 AchievementManager
를 사용해 업적 종류에 맞는 카테고리 id 와 함께 updateAccumulateAchievement
를 호출 하면된다.
조회 로직은 특별히 복잡한 로직은 없고 단순히 테이블을 join 하여 가져오는 것이라 쿼리만 소개하겠다.
# 업적 상세 정보 조회
SELECT * FROM achievement a
LEFT JOIN user_achievement ua ON a.achievement_id = ua.id AND ua.user_id = 15
WHERE a.achievement_id = 2;
# 내 업적 조회
SELECT * FROM user_achievement ua
JOIN achievement a on ua.achievement_id = a.achievement_id
WHERE ua.user_id = 14 AND ua.obtained_at is not null
LIMIT 1;
# 내 업적 개수 조회
SELECT COUNT(*) FROM user_achievement ua
WHERE ua.user_id = 14 AND ua.obtained_at is not null;
# 카테고리별 업적 조회
SELECT * FROM achievement a
LEFT JOIN user_achievement ua ON a.achievement_id = ua.achievement_id AND ua.user_id = 14 AND ua.obtained_at is not null
WHERE category_id = 1
이번 기능을 끝으로 초반에 기획 했던 모든 기능들을 다 구현해서 뿌듯하다. 이번 기능은 디자인 까지 해보느라 좀 어려웠던 것 같다. 그래도 처음에 중점을 두었던 확장이 용이하게 구현하는 것은 성공한 듯 하여 좋은 것 같다. 이제 이 시스템들을 기반으로 더 새롭고 재밌는 기능들을 추가 할 수 있을 것 같다.
다음번에는 땅따먹기 시스템을 업그레이드 시켜보려고 한다. 지금은 단순히 방문하기만 하면 다른 유저들의 땅을 점령할 수 있어서 시스템이 단조롭다는 생각이든다. 그래서 레벨이나 포인트 시스템을 도입하여 좀 더 게이미피케이션 요소를 추가해보려한다. 이번에 만든 업적 기능과도 연계하여 업적을 획득하면 레벨이나 포인트를 획득할 수 있는 방식으로도 접근해볼 수 있을 것 같다.
이제 출시하는 기능이라 얼마나 유의미한 사용자 행위가 있을지 모르겠다. 경과를 지켜보고 유의미한 결과가 발견되면 업데이트 해보도록 하겠다.