선을 그어 칸 위의 유닛을 한 칸씩 회전시키는 그리드 기반 모바일 퍼즐 게임입니다. “꽃을 흙 위로 옮긴다”는 단순한 목표에서 출발하지만, 타일과 유닛을 조합하면서 난이도가 차근차근 올라갑니다. 처음에는 엔트리로 프로토타입을 만들어 게임성을 검증한 뒤 Unity로 옮겼습니다.
한눈에 보기
| 항목 | 내용 |
|---|---|
| 장르 | 캐주얼 퍼즐 (선 잇기 / 순환 이동) |
| 엔진 | Unity 6 (6000.3.2f1), 2D URP |
| 플랫폼 | Android (세로 모드, 1080×2280), com.GDH.ChainPuzzle |
| 버전 | 1.0.2 |
| 분량 | 2개 월드 × 12레벨 = 기본 레벨 24종 + 유저 제작 맵 무제한 |
| 언어 | 한국어 / 영어 (런타임 전환) |
역할 분담
3인 협업 프로젝트입니다. 제가 맡은 부분과 협업 내용은 다음과 같습니다.
본인 (이영호) 담당
- 핵심 게임 제작 — 퍼즐 로직, 선 잇기·순환 이동, 게임 상태 머신, 타일/유닛 규칙 등 게임플레이 전반
- 맵 에디터 및 플레이테스트 기반 정답·최소 이동 수 자동 생성
- Google Play 스토어 출시 및 업적(Google Play Games) 연동
- Spine 애니메이션 엔진 연동 — 디자이너가 만든 애니메이션을 게임 유닛에 적용
협업
- 개발 친구 — 홈(메인 메뉴)·레벨 선택·설정 화면, Firebase로 사용자 제작 맵 불러오기
- 디자이너 친구 — 꽃 / 돌꽃 유닛의 Spine 2D 애니메이션 제작
게임 소개
플레이어는 격자 위에서 인접한 칸을 이어 선(체인)을 그립니다. 시작 칸으로 돌아와 닫힌 도형을 만들거나 선만 이어도, 경로 위의 유닛들이 한 칸씩 순환 이동합니다. 이 회전을 이용해 꽃을 흙 타일 위로 모두 올리면 클리어입니다.
튜토리얼 문구
- “꽃을 흙으로 옮기세요! 사각형을 그려서 옮기세요.”
- “사각형이 아니여도 선만 이어지면 이동합니다.”
규칙은 한 문장이지만, 타일과 유닛 속성이 더해지면 경로를 거꾸로 돌려야 하거나 두 칸씩 밀어야 하는 상황이, 또 같은 유닛을 너무 많이 건드리면 안 되는 상황이 생깁니다.
핵심 게임플레이
타일 (바닥 속성)
| 타일 | 설명 |
|---|---|
| 흙 (Dirt) | 목표 칸. 위에 꽃 / 돌꽃이 있어야 정답 |
| 일반 (Normal) | 아무 제약 없는 기본 칸 |
| 쓰레기 (Trash) | 꽃이 올라가면 실패 → 자동으로 이동 취소. 단, 돌꽃은 허용 |
| 더블 (Double) | 이 칸을 지나는 경로는 2칸씩 이동 |
| 역방향 (Reverse) | 이 칸을 지나는 경로는 반대 방향으로 이동 (더블과 겹치면 반대로 2칸) |
유닛 (칸 위의 말)
| 유닛 | 설명 |
|---|---|
| 꽃 (Flower) | 흙 위로 옮겨야 하는 주인공. Spine 스켈레톤 애니메이션 |
| 돌 (Stone) | 장애물 겸 채움 유닛. 클리어 시 화면 밖으로 튕겨 나가는 연출 |
| 돌꽃 (StoneFlower) | 여분의 꽃. 흙·쓰레기 모두 허용되는 예비 정답 유닛 |
| 얼음 (Ice) | 3번 이상 움직이면 깨져서 실패. 이동·취소에 따라 카운트가 증감하며 단계별 이미지로 표시 |
조작과 이동
- 인접 판정: 상하좌우 1칸만 연결(대각선 불가), 이미 지난 칸 재방문 금지로 꼬임 방지.
- 도형 완성: 시작 칸으로 복귀 + 3칸 이상 연결 시 닫힌 경로 확정.
- 순환 이동: 경로 위 유닛들을 모듈러 연산으로 한 칸씩 회전.
step값 하나로 일반(+1)·더블(+2)·역방향(-)을 모두 처리. - 되돌리기 / 리셋 / 힌트 / 정답: 되돌리기 최대 3회, 힌트 1코인, 정답 자동 재생 5코인.
- 정답/실패 판정: 모든 칸이 조건을 만족하면 승리, 꽃이 쓰레기 칸에 놓이면 그 즉시 이동을 자동으로 되돌립니다.
진행 구조
- 월드 → 레벨 2단계 구조 (월드당 12레벨). 레벨을 깨면 다음 레벨이, 월드의 진행도가 차면 다음 월드가 열립니다.
- 진행 상태 4단계: 잠김 → 해제 → 클리어 → 퍼펙트 클리어(최소 이동 수 이내 클리어).
- 코인 경제: 클리어 1코인, 퍼펙트 3코인 보상(상태가 오를 때 차액만 지급). 힌트·정답에 코인을 씁니다.
- 신규 요소 해금 연출: 진행에 맞춰 돌꽃·쓰레기·더블·얼음·역방향 타일과 맵 에디터/공유 기능이 차례로 잠금 해제됩니다.
맵 에디터 & 커스텀 맵 공유 (대표 기능)
단순 플레이를 넘어, 유저가 직접 맵을 만들고 공유할 수 있는 파이프라인을 구현했습니다.
- 인게임 에디터: 그리드에 타일/유닛을 찍어 맵을 만듭니다. 기존 레벨을 불러와 수정하는 모드(Edit)도 지원합니다.
- 제작 검증: 빈 칸 위 유닛 금지, 모든 칸 채우기,
꽃 수 ≤ 흙 수 ≤ 꽃 + 돌꽃균형, 최소 1송이 필요 등 풀이 가능성을 보장하는 규칙을 코드로 검증합니다. - 플레이테스트 → 정답 자동 생성: 제작자가 자기 맵을 직접 풀면, 그 풀이 경로가 그대로 정답으로, 이동 수가 최소 이동 수로 기록됩니다. “클리어해야 업로드 가능”이라는 규칙이 곧 정답 데이터 검증인 셈입니다.
- 공유: 완성한 맵을 Firebase Firestore에 업로드합니다. 다른 유저는 최신순 / 별점순 / 제목 검색 / 제작자 검색으로 찾아 내려받아 플레이합니다.
게임 모드는
Normal / PlayTest / Custom / Edit4종으로 나뉘며, 같은 게임 씬이 모드에 따라 다르게 동작합니다.
기술 구조
혼자 처음 만들었던 이전 프로젝트들의 시행착오를 정리해, 확장 가능한 구조 설계에 집중한 프로젝트입니다.
매니저 허브 패턴
Managers 싱글톤 하나가 DontDestroyOnLoad로 살아남아 10개 하위 매니저(Data·Firebase·Input·Localization·Level·Resource·Scene·Setting·Sound·UI)에 정적 프로퍼티로 접근합니다. 어디서든 Managers.Sound.Play(...) 식으로 일관되게 호출합니다.
유한 상태 머신(FSM)으로 게임 흐름 관리
게임 진행을 GameState(Idle·Draw·Move·Result·UndoMove·GameWin·ResetLevel·Hint·Answer) 열거형으로 정의하고, SetState() 한 곳에서 상태 전이와 부작용을 통제했습니다. 입력·이동·판정·연출이 상태별 함수로 나뉘어 흐름을 따라가기 쉽습니다.
데이터 기반 레벨
레벨을 압축 문자열로 표현했습니다. 예를 들어 tile_data: "111111112000121", unit_data: "000110000000000"처럼 한 칸을 한 글자로 인코딩해 width × height 격자로 복원합니다. 레벨은 JSON으로 관리하고 ILoader 제네릭 패턴으로 로드해 Dictionary<int, Level>로 변환합니다(Newtonsoft.Json).
UI 프레임워크
UI_Base에서 열거형 이름으로 자식 오브젝트를 자동 바인딩(Bind<T>)하고, 버튼은 바인딩할 때 클릭 사운드를 자동으로 연결합니다. 팝업은 스택으로 관리하며 sortingOrder를 자동으로 올려 중첩 순서를 보장합니다(UI_Scene / UI_Popup).
저장 시스템
Easy Save 3를 제네릭 + 열거형 키로 감싸 타입 안전한 Save<TKey,TValue> / Load API로 추상화했습니다. 키 충돌 없이 코인·레벨 상태·세팅을 저장합니다.
연출
모든 이동·트윈에 DOTween 시퀀스를 썼습니다(순환 이동, 2단 이동, 돌 폭발 낙하 연출 등). 꽃·돌꽃은 Spine 2D 스켈레톤 애니메이션(Idle/Move/Win)으로 표현합니다.
현지화 & 입력
- 로케일별 JSON 사전(ko-KR/en-US)을 키 기반으로 치환해 런타임 언어 전환을 지원합니다.
- 마우스/터치 입력을
Action<MouseEvent>이벤트로 래핑하고, UI 위 입력은 무시하도록 처리했습니다.
백엔드 (Firebase)
익명 인증 + Google Play Games 로그인, Firestore로 커스텀 맵 CRUD를 처리합니다. 맵 본문은 JSON 문자열(payloadJson)로 저장하고, 정렬·검색용 메타데이터(제목·제작자·별점·생성시각)는 필드로 분리해 접두어 검색과 페이지네이션을 구현했습니다.
기억에 남는 구현
- 그릴 때 들리는 멜로디: 칸을 이을 때마다 연결음을 재생하는데,
num = count % 16; if (num >= 8) num = 15 - num;식으로 인덱스를 접어 올라갔다 내려오는 음계가 들리게 했습니다. 길게 그을수록 리듬감이 살아납니다. step하나로 끝낸 이동 규칙: 더블(2칸)·역방향(-)을 별도 분기 없이step의 부호·크기와 모듈러 연산만으로 처리합니다. “역방향 + 더블 = 반대로 2칸”이 자연스럽게 성립합니다.- 규칙이 곧 검증이 되는 에디터: “내가 못 깨는 맵은 못 올린다”는 제약을 풀이 경로 기록과 묶어, 별도 정답 입력 UI 없이 정답·최소 이동 수를 확보했습니다.
사용 기술 스택
Unity 6 · C# · 2D URP · DOTween · Spine (2D 스켈레톤) · Easy Save 3 · Newtonsoft.Json · TextMesh Pro · Firebase (Auth · Firestore) · Google Play Games