411 lines
14 KiB
Markdown
411 lines
14 KiB
Markdown
# 낚시 미니게임 구현 기획안
|
||
|
||
이 문서는 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 메시지는 게임 엔진이 아니므로, 순간 반응보다는 명확한 판정 윈도우가 더 중요합니다.
|
||
- 최종 설계는 화려함보다 반응성, 명확성, 실패 상태의 가독성을 우선해야 합니다.
|