Cat Rescue 작업에서 가장 먼저 정해야 했던 것은 화면이 아니라 규칙이었다. 퍼즐 게임은 귀여운 테마를 입히기 쉽지만, 레벨을 계속 만들려면 규칙이 흔들리지 않아야 한다.

그래서 레벨을 코드에 직접 박지 않고 JSON 포맷으로 분리했다. 목표는 단순했다. 웹 에디터에서 레벨을 만들고, 같은 데이터를 게임 런타임에서도 읽을 수 있게 하는 것이다.

문제

나와라 냥이의 기본 루프는 간단하다. 고양이가 있는 방에 아이템을 배치하고, 불러보기를 누르면 고양이가 규칙에 따라 자동 이동한다. 고양이가 함정과 방해 요소를 피해 출구까지 가면 클리어다.

그런데 이 구조는 보기보다 데이터 의존성이 강하다. 한 스테이지에는 고양이 시작 위치, 출구, 비활성 칸, 장애물, 인벤토리, 정답 배치, 별점 기준이 모두 들어간다. 이걸 Godot 코드에 직접 넣으면 레벨을 늘릴 때마다 코드와 콘텐츠가 섞인다.

처음부터 에디터를 생각한 이유가 여기에 있다. 퍼즐 게임은 첫 3개 레벨을 만드는 것보다, 20개 이상의 레벨을 계속 만들고 검증하는 비용이 더 중요하다.

레벨을 데이터로 고정하기

MVP 기준 레벨은 6x8 격자다. 모든 오브젝트는 격자 좌표에 정렬되고, 고양이는 직접 조작하지 않는다. 이동 우선순위는 장난감 -> 간식 -> 출구로 둔다.

레벨 JSON은 이런 정보를 담는다.

{
  "id": "room_001",
  "chapter": "자취방",
  "cat_name": "모모",
  "cols": 6,
  "rows": 8,
  "cat": [0, 3],
  "exit": [5, 3],
  "exit_side": "right",
  "disabled": [[4, 0], [5, 0], [5, 1]],
  "obstacles": [
    {"id": "trap_1", "kind": "trap", "label": "함정", "cell": [1, 3]},
    {"id": "noise_1", "kind": "noise", "label": "소음", "cell": [3, 1]}
  ],
  "inventory": [
    {"id": "box_1", "kind": "box", "label": "상자 덮개"},
    {"id": "treat_1", "kind": "treat", "label": "간식"}
  ],
  "perfect_moves": 2,
  "solution": {"box_1": [1, 3], "treat_1": [4, 3]}
}

이 포맷을 잡아두면 웹 에디터, 테스트, Godot 런타임이 같은 기준을 공유할 수 있다.

규칙 엔진이 맡는 일

규칙 엔진은 UI와 분리해서 생각했다.

이렇게 하면 UI는 “어떻게 보여줄지”에 집중하고, rules core는 “이 배치가 맞는지”만 판단한다.

Godot/Toss로 옮길 때의 기준

웹 에디터에서 만든 레벨은 Godot 쪽에서도 그대로 읽을 수 있어야 한다. 그래서 기술 구조도 작게 나눴다.

Toss 미니앱 쪽으로 갈 때도 같은 원칙을 유지한다. 플랫폼 shell이 바뀌어도 규칙과 레벨 데이터는 흔들리지 않아야 한다.

검증 기준

출시 전에는 모든 스테이지가 정답 배치로 클리어 가능해야 한다. 레벨 에디터가 있어도, 잘못된 레벨이 들어가면 게임은 바로 깨진다.

그래서 검증은 다음 순서로 봐야 한다.

남겨둘 기준

이 프로젝트에서 좋은 점은 “귀여운 고양이 퍼즐”을 만들었다는 사실보다, 콘텐츠 제작 도구와 게임 규칙을 같은 데이터 모델 위에 올렸다는 점이다. 취업용으로도 이 부분이 더 강한 신호다.