앱
상태·데이터
연출·에셋
한국외대 마스코트 캐릭터 부를 펫처럼 키우는 육성형 캠퍼스 앱입니다. 학식·퀴즈·미니게임·마이룸·친구 같은 학교 생활 요소를 캐릭터 성장에 연결해서, 정보를 "찾아보는" 대신 게임 안에서 자연스럽게 소비하게 만드는 것이 목표였습니다. 캡스톤디자인 팀 프로젝트이고, 저는 앱(프론트엔드) 전체를 맡았습니다.
Expo(React Native) 앱 전체입니다. 6명 팀에서 프론트엔드는 저 혼자였습니다(백엔드 2명, 기획 3명). 화면과 게임 로직, 상태 설계, 서버 API 연동, 실기기 빌드/테스트를 담당했습니다. 백엔드는 FastAPI(JWT, 9자리 학번 로그인, @hufs.ac.kr 메일 인증으로 외대 구성원만 가입)였고, 프론트를 붙이면서 계약이 어긋나는 지점을 문서로 정리해 백엔드에 넘겼습니다.
접속 → 부 상태 확인 → 학식/퀴즈 → XP 성장 → 진화 → 마이룸 확장이라는 하루 루프를 먼저 설계하고 그 위에 기능을 붙였습니다.
화면만 있는 앱이 아니라 성장 게임이라, 행동 하나가 여러 도메인으로 연쇄됩니다. 퀴즈 정답 하나가 XP → 학년 상승 → 진화 화면 → 서버 통계까지 번지고, 미니게임 성공은 하트 차감 검증 → 결과 저장 → 코인 보상 → 랭킹으로 이어집니다. 초기에는 로컬 데이터 중심으로 빠르게 만들었는데, 서버가 붙으면서 이 연쇄의 단계마다 무엇을 권위 있는 값으로 볼 것인가를 다시 정해야 했습니다.
로그인 상태인데도 로컬 더미가 화면에 보이면, 서버가 데이터를 안 주고 있는 건지 잘 주고 있는 건지 구분할 수가 없습니다. 장애가 더미에 가려집니다. 그래서 로그인 상태에서는 로컬 fallback을 대부분 차단하고, 핵심 데이터가 비면 화면이 의도적으로 실패를 드러내도록 뒀습니다. 대신 서버 없이 넓게 테스트해야 하는 기능을 위해 로컬과 서버를 완전히 분리한 게스트 모드를 따로 만들었습니다.
이 원칙 덕분에 실제 장애가 드러났습니다. 마이룸 가구 구매가 안 되던 건은 프론트 버튼 문제가 아니라 서버 /shop/items가 200으로 빈 배열을 주고 있었던 것이었고, 로그를 근거로 백엔드에 전달했습니다. 신규 유저의 캐릭터 동기화 403도 같은 방식으로 잡았습니다.
같은 기준으로 낙관적 업데이트의 범위도 정했습니다. 친구 요청 수락이나 가구 장착처럼 단순한 동작은 성공을 가정하고 화면을 먼저 바꾼 뒤 실패하면 되돌리지만, 하트 차감·상점 구매처럼 코인과 소유권이 걸린 작업은 그렇게 하면 실패를 숨기게 됩니다. 이런 경로는 로딩 오버레이로 진행 상황을 보여주고 서버 성공을 권위값으로 삼았습니다.
로컬에 쌓이는 값이 늘면서, 새 기기에서 로그인하면 무엇이 어긋나는지 불안해졌습니다. 증상을 기다리는 대신 저장 상태를 전부 나열하고 서버에서 복구 가능한 것과 아닌 것으로 갈라 문서로 정리했습니다.
위험한 항목이 분명해졌습니다. 끼니 누락 횟수와 이미 적용한 XP 패널티가 로컬에만 있어서, 기존 기기에서는 배고픈 상태였는데 새 기기에서는 멀쩡해 보이고 패널티도 기기마다 다르게 적용될 수 있었습니다. 퀴즈 일일 카운터도 마찬가지였고, 둘 다 XP·코인이 걸린 값이라 경제 밸런스로 이어집니다. 로그인 상태에서는 서버 값만 기준으로 삼고 로컬 값은 비로그인 fallback 전용으로 격리했습니다.
React Native는 게임 엔진이 아닙니다. 전공책 받기나 부 잡기는 수월했지만, 자유투는 물리적 상호작용이 필요해서 애를 먹었습니다. 물리 엔진을 도입했는데 공이 의도대로 움직이지 않았습니다.
추측 대신 시간별 궤적 지점을 화면에 찍어 시각화하면서 디버깅했습니다. 그러자 눈으로 바로 보였습니다 — 던지기도 전에 이미 중력이 작용하고 있었습니다. 결국 궤적은 시간별 이동 경로를 좌표로 직접 지정하는 방식으로 만들었습니다. 물리 엔진을 제대로 다루지 못해 택한 우회였고, 자연스러운 느낌을 내는 게 끝까지 가장 어려웠습니다.
기능이 도는지가 아니라 목표를 달성했는지를 확인하는 쪽으로 테스트를 짰습니다.



