# 낚시 미니게임 구현 기획안 이 문서는 Kord의 `낚시(Fishing)` 미니게임 시스템에 대한 설계 방향과 구현 범위를 정리한 기획서입니다. ## 개요 낚시 미니게임은 기존 미니게임 경제 시스템에서 사용할 재화를 공급하는, 실시간 이모지 조작형 미니게임입니다. 사용자는 1초마다 갱신되는 상태 메시지를 보며 입력을 미리 선택하고, 물고기가 특정 위치에 일정 시간 머문 뒤 그 입력이 맞았는지 판정받습니다. 핵심 목표는 물고기를 끌어와 거리를 줄이는 동시에, 낚시줄 끊어짐 게이지가 가득 차지 않도록 관리하는 것입니다. 이 게임은 단순 슬래시 명령 기반이 아니라, 버튼 조작과 실시간 게이지 변화, 물고기 이모지 표시를 통해 더 직접적인 조작감을 주는 것을 목표로 합니다. ## 핵심 게임 루프 ### 1. 스레드 입장과 세션 시작 - 사용자는 먼저 `/fishing enter` 명령으로 낚시 전용 스레드에 입장합니다. - 봇은 명령을 실행한 채널에 `UserName's Fishing Spot` 형식의 전용 스레드를 생성합니다. - 이미 같은 사용자의 낚시 스레드가 있으면 기존 스레드를 재사용합니다. - 실제 낚시 세션 시작은 전용 스레드 안에서 `/fishing cast`를 입력했을 때만 가능합니다. - 즉, 스레드 생성과 게임 시작은 별도 단계로 분리합니다. - 봇은 실시간 게임 메시지를 생성합니다. - 메시지에는 다음 요소가 포함됩니다. - 중앙에 표시되는 물고기 이모지 - 거리 게이지 - 낚시줄 끊어짐 게이지 - 네 개의 조작 버튼 - 현재 라운드에 예약된 입력 - 다음 판정까지 남은 시간 ### 1-1. 전용 스레드 규칙 - 스레드 이름은 영문 기준 `UserName's Fishing Spot` 형식을 사용합니다. - 낚시 게임 메시지, 버튼 입력, 결과 메시지는 모두 해당 스레드 안에서만 처리합니다. - 이렇게 하면 일반 채팅이 밀려도 낚시 UI가 섞이지 않고 유지됩니다. - 한 사용자는 동시에 하나의 낚시 스레드만 활성화할 수 있습니다. - `/fishing cast`는 자신의 낚시 스레드 안에서만 사용할 수 있습니다. - 성공이나 실패로 게임이 끝나더라도 스레드는 즉시 삭제하지 않습니다. - 같은 스레드 안에서 다시 `/fishing cast`를 입력해 연속 플레이할 수 있어야 합니다. - `/fishing end`를 실행했을 때만 세션을 정리하고 스레드를 삭제합니다. ### 2. 조작 버튼 사용자는 아래 네 가지 버튼 중 하나를 선택할 수 있습니다. - `⬅️` 왼쪽 - `⏺️` 중앙 - `➡️` 오른쪽 - `🛌` 휴식 ### 3. 물고기 위치 - 물고기는 텍스트가 아니라 이모지로 표시됩니다. - 물고기 이모지는 메시지의 시각적 중앙 라인에 위치해야 합니다. - 내부 상태상 물고기 위치는 왼쪽, 중앙, 오른쪽 중 하나를 가집니다. - 물고기는 한 라운드 동안 같은 위치에 일정 시간 머뭅니다. - 사용자는 그 라운드 안에서 방향 버튼을 미리 입력합니다. - 물고기가 해당 위치에 일정 시간 이상 머문 뒤 입력이 일치하면 성공 판정이 납니다. - 입력이 없거나 다른 방향이면 `타이밍 빗나감`으로 처리됩니다. ### 4. 거리 게이지 - 물고기를 얼마나 더 끌어와야 하는지를 나타내는 게이지입니다. - 시작값은 `100` 같은 양수로 설정합니다. - 방향을 맞춘 입력이 성공 판정되면 거리가 감소합니다. - 휴식을 선택하면 거리가 다시 증가합니다. - 거리가 `0`이 되면 낚시에 성공합니다. ### 5. 낚시줄 끊어짐 게이지 - 낚시줄이 얼마나 끊어질 위기에 가까운지를 나타내는 게이지입니다. - 방향을 맞춘 입력이 성공 판정되면 이 게이지가 증가합니다. - 타이밍을 놓치면 소량 증가합니다. - 휴식을 선택하면 이 게이지가 회복됩니다. - 게이지가 최대치에 도달하면 즉시 낚시에 실패합니다. ### 6. 휴식 행동 - `🛌` 휴식은 해당 라운드에서 즉시 적용됩니다. - 낚시줄 끊어짐 게이지를 회복합니다. - 대신 물고기와의 거리가 다시 증가합니다. - 즉, 휴식은 실패 위험을 낮추지만 진행도를 되돌리는 안전 선택지입니다. ### 7. 틱 업데이트 - 낚시 상태는 1초마다 갱신됩니다. - 메시지도 1초 주기로 갱신됩니다. - 이 갱신 루프는 아래 요소를 처리합니다. - 현재 라운드 경과 시간 계산 - 입력 예약 상태 반영 - 성공 / 타이밍 빗나감 판정 - 다음 물고기 위치로 라운드 전환 - 게이지 렌더링 갱신 - 세션 종료 판정 ## UI / UX 요구사항 ### 실시간 메시지 구성 낚시 메시지는 최소한 아래 요소를 포함해야 합니다. - 물고기 이모지로 표시되는 현재 위치 - 거리 게이지 - 낚시줄 끊어짐 게이지 - 간단한 상태 텍스트 - 현재 예약 입력 - 다음 판정까지 남은 시간 - 예: `입질 중`, `타이밍 빗나감`, `휴식 중`, `성공`, `실패` ### 게이지 디자인 항상 아래 두 개의 게이지가 동시에 보여야 합니다. 1. 거리 게이지 - 성공을 향해 감소하는 게이지 - 휴식 시 다시 증가할 수 있음 - 채워진 블록 / 빈 블록 형태로 표현 가능 2. 낚시줄 끊어짐 게이지 - 실패를 향해 증가하는 게이지 - 거리 게이지와 시각적으로 구분되어야 함 예시: ```text 위치: ⬅️ 🐟 ➡️ 입력: ⏺️ 남은 시간: 2s 거리: ████████░░ 20 / 100 끊어짐: ████░░░░░░ 40 / 100 ``` 최종 렌더링은 더 다듬을 수 있지만, 핵심은 물고기 자체를 반드시 이모지로 유지하는 것입니다. ### 조작 버튼 버튼은 직관적이고 빠르게 눌릴 수 있어야 하므로, 이모지 중심으로 구성합니다. - `⬅️` - `⏺️` - `➡️` - `🛌` ## 성공 / 실패 조건 ### 성공 - 거리 게이지가 `0`이 됩니다. - 보상을 지급합니다. - 세션을 종료하고 버튼을 비활성화합니다. ### 실패 - 낚시줄 끊어짐 게이지가 최대치에 도달합니다. - 세션을 종료하고 버튼을 비활성화합니다. ### 선택적 시간 제한 - 일정 시간 동안 행동하지 않으면 실패하도록 설계할 수도 있습니다. - 다만 이 기능은 MVP에는 필수가 아니며, 템포가 느릴 때만 후속 단계에서 검토합니다. ### 종료 명령 - `/fishing end` 명령을 제공해 사용자가 직접 낚시 스레드를 종료할 수 있어야 합니다. - 종료 명령이 실행되면 진행 중인 낚시 세션이 있다면 즉시 정리됩니다. - 전용 스레드는 `/fishing end`에서만 삭제됩니다. - 성공/실패 후에는 버튼만 비활성화하고 스레드는 유지합니다. - 사용자는 같은 스레드 안에서 다시 `/fishing cast`를 입력해 새 게임을 시작할 수 있어야 합니다. ## 경제 시스템 연동 낚시는 기존 미니게임 경제를 보조하는 수단으로 설계합니다. ### 권장 보상 모델 - 낚시에 성공하면 `gold`를 지급합니다. - 지급된 `gold`는 현재 `refinement` 게임이 사용하는 경제 프로필에 반영합니다. - 이렇게 하면 낚시는 정련 게임을 보조하는 재화 수급형 미니게임이 됩니다. ### 확장 가능성 후속 버전에서는 아래 요소를 추가할 수 있습니다. - 물고기 희귀도 - 물고기 크기(cm) 시스템 - 개별 물고기 인벤토리 - 물고기 도감 / 컬렉션 - 미끼 종류 - 낚싯대 및 업그레이드 - 물고기 판매 시스템 Phase 1에서는 직접 `gold`를 주는 방식이 가장 단순하고 호환성이 좋습니다. ## 설정 모델 낚시는 공용 미니게임 레지스트리에 등록되는 구조로 가야 합니다. ### MiniGame Registry 추가할 키: - `fishing` 이를 통해 다음이 가능해집니다. - 길드별 활성화 / 비활성화 - 전용 채널 제한 - `/minigame`을 통한 상태 조회 낚시의 경우 전용 채널 안에서 다시 개별 스레드를 생성하는 방식으로 운영합니다. ## 기술 구조 ### 서비스 권장 신규 서비스: - `FishingService` 주요 책임: - 낚시 전용 스레드 생성 / 삭제 - 세션 생성 / 종료 - 사용자별 활성 낚시 세션 추적 - 틱 업데이트 루프 관리 - 물고기 위치 생성 - 입력 예약 및 라운드 판정 - 보상 정산 ### 인터랙션 처리 권장 커스텀 ID prefix: - `fishing_` 기존 구조와 일관성을 유지합니다. - `music_` - `refine_` ### 영속 데이터 권장 초기 모델: - `FishingProfile` - `userId` - `guildId` - `totalCatchCount` - `successCount` - `failCount` - `bestCatchReward` - `lastCastAt` - `createdAt` - `updatedAt` 이 정도면 초반 통계 추적에 충분하고, 너무 이르게 인벤토리를 과설계하지 않을 수 있습니다. 도감/크기 확장 시에는 아래 모델을 추가합니다. - `FishingCollectionEntry` - `userId` - `guildId` - `fishId` - `catchCount` - `bestRarity` - `bestSizeCm` - `lastCaughtAt` - `createdAt` - `updatedAt` 이 모델은 유저가 어떤 물고기를 몇 번 잡았는지, 최고 레어도와 최고 크기가 무엇인지 추적하는 데 사용합니다. ### 물고기 크기 시스템 - 낚시 성공 시 물고기마다 `cm` 단위의 크기를 부여합니다. - 기본 크기 범위는 물고기별 데이터에서 관리합니다. - 최종 크기는 `물고기 기본 크기 범위 × 레어도 보정치`로 계산합니다. - 즉, 같은 물고기라도 레어도가 높을수록 더 큰 개체가 등장할 수 있습니다. - 성공 결과 메시지에는 잡은 물고기의 크기를 함께 표시합니다. - 도감에는 해당 물고기의 최고 크기를 기록합니다. ### 도감 (Dex / Collection) - `/fishing dex` 명령을 통해 개인 도감을 조회할 수 있어야 합니다. - 도감에는 아래 정보를 보여줍니다. - 잡아본 물고기 목록 - 각 물고기의 포획 횟수 - 최고 레어도 - 최고 크기(cm) - 마지막 포획 시각 - 아직 잡지 못한 물고기는 후속 버전에서 실루엣/잠금 상태로 표시할 수 있습니다. ### 세션 모델 낚시 플레이 자체는 즉각적인 반응을 위해 메모리 세션 기반이 적합합니다. 권장 런타임 필드: - `guildId` - `userId` - `threadId` - `messageId` - `fishPosition` - `selectedAction` - `phaseStartedAt` - `distance` - `lineTension` - `tickInterval` - `expiresAt` ## 입력 판정 규칙 권장 MVP 규칙은 아래와 같습니다. - 각 라운드마다 물고기는 하나의 위치에 일정 시간 머뭅니다. - 플레이어는 그 라운드에서 방향 입력을 미리 예약합니다. - 물고기가 해당 위치에 `특정 시간 이상` 머문 시점에 입력이 일치하면 - 거리 감소 - 낚시줄 끊어짐 게이지 증가 - 성공 판정 후 다음 라운드로 이동 - 라운드 시간이 끝날 때까지 올바른 입력이 없으면 - `타이밍 빗나감` - 거리 감소 없음 - 소량의 끊어짐 증가 - 휴식을 선택하면 - 낚시줄 끊어짐 게이지 감소 - 대신 거리 증가 - 휴식 적용 후 즉시 다음 라운드로 이동 이 구조는 다음 긴장감을 만듭니다. - 방향을 잘 예약해 두면 안정적으로 거리 감소를 만들 수 있음 - 너무 공격적으로 당기면 줄이 끊어질 위험이 커짐 - 휴식은 실패를 막아주지만 물고기가 다시 멀어짐 ## 단계별 계획 ### Phase 1 - `fishing`을 미니게임 레지스트리에 등록 - `/fishing enter` 추가 - `/fishing cast` 추가 - `/fishing end` 추가 - `/fishing enter` 실행 시 `UserName's Fishing Spot` 스레드 생성 - `/fishing cast`는 전용 스레드 안에서만 허용 - 메모리 기반 낚시 세션 루프 구현 - 1초 주기 메시지 갱신 - 입력 예약형 라운드 판정 구현 - 두 개의 게이지 표시 - 네 개의 이모지 조작 버튼 추가 - 성공 시 `gold` 지급 - 성공/실패 후 스레드 유지 - `/fishing end` 실행 시 스레드 삭제 ### Phase 2 - `/fishing status` 추가 - `/fishing ranking` 추가 - 사용자별 낚시 통계 추가 - 낚시 프로필 영속화 - 물고기 이동 패턴 개선 - 보상 밸런스 조정 - `/fishing dex` 추가 - 물고기 크기(cm) 시스템 추가 - 도감용 포획 기록 저장 - 길드 내 최고 크기 기준 Top 10 랭킹 표시 ### Phase 3 - 희귀도 체계 추가 - 인벤토리 / 도감 고도화 - 미끼 / 낚싯대 보정치 추가 - 리더보드 지원 - 미포획 물고기 잠금/실루엣 UI 추가 ## 검증 / 테스트 ### 수동 테스트 시나리오 1. 허용된 채널에서 `/fishing enter` 실행 2. `UserName's Fishing Spot` 스레드가 자동 생성되는지 확인 3. 자신의 낚시 스레드 밖에서는 `/fishing cast`가 거부되는지 확인 4. 낚시 관련 메시지가 스레드 안에서만 진행되는지 확인 5. 실시간 메시지에 물고기 이모지와 두 개의 게이지가 보이는지 확인 6. 방향 버튼을 미리 눌러 예약 입력이 반영되는지 확인 7. 일정 시간 뒤 방향이 맞았을 때 거리 감소가 적용되는지 확인 8. 타이밍을 놓치면 `타이밍 빗나감`으로 처리되는지 확인 9. 휴식을 눌렀을 때 끊어짐 게이지가 회복되고 거리가 증가하는지 확인 10. 거리 `0` 도달 시 성공 처리되는지 확인 11. 끊어짐 게이지 최대 도달 시 실패 처리되는지 확인 12. 세션 종료 후 버튼이 비활성화되고 스레드는 유지되는지 확인 13. 같은 스레드 안에서 다시 `/fishing cast`가 가능한지 확인 14. `/fishing end` 실행 시 스레드가 자동 삭제되는지 확인 15. 보상이 정상 지급되는지 확인 16. `/minigame` 설정 기반 채널 제한이 적용되는지 확인 ### 엣지 케이스 - 같은 사용자가 중복으로 세션 시작 - 스레드 삭제 권한이 없을 때 종료 처리 - 세션 종료 후 오래된 버튼 클릭 - 낚시 세션 중 봇 재시작 - 인터랙션 응답 지연 - 1초 주기 메시지 갱신 시 Discord rate limit 영향 ## 참고 사항 - 1초 갱신 루프는 이 게임의 손맛을 좌우하므로 메시지 갱신 성능을 꼭 확인해야 합니다. - Discord 메시지는 게임 엔진이 아니므로, 순간 반응보다는 명확한 판정 윈도우가 더 중요합니다. - 최종 설계는 화려함보다 반응성, 명확성, 실패 상태의 가독성을 우선해야 합니다.