말풍선과 로컬 모델
게임 속 어떤 유닛이든 원하는 신분으로 머리 위에 대화 말풍선을 띄웁니다. 로컬 LLM을 연결하면 한 마디가 들어가고 답변 한 마디가 유닛 머리 위에 나타납니다.
말풍선은 관전용 레이어입니다. 승패에 영향을 주지 않으며 방송, 해설, 디버깅에 적합합니다.
- 어떤 유닛이든, 어떤 신분으로든 말할 수 있고, 여러 유닛이 동시에 말할 수도 있습니다.
- 말풍선마다 글자 크기, 색상, 너비, 꼬리, 투명도, 타이핑 속도를 따로 설정할 수 있습니다.
- 로컬 LLM(LM Studio)에 바로 연결할 수 있고 스트리밍 출력을 지원합니다. 생성되는 대로 말풍선이 갱신됩니다.
Bot에서 사용하기
가장 간단한 방법은 SDK에 내장된 say입니다.
g.say(hero, "나를 따르라!", seconds=4)
시작과 화면
가장 간단한 방법은 Farsight 첫 화면 “컨트롤 센터”입니다. 먼저 “로컬 LLM → 시작하고 모델 로드”를 누르고(LM Studio 로컬 서버 + 설정해 둔 모델을 VRAM에 로드), 그다음 “머리 위 채팅 말풍선 → 시작”을 누르세요. 카드에서 로그 보기, 중지, 재시작도 할 수 있습니다.
화면은 Farsight 왼쪽의 “머리 위 말풍선” 페이지에 있습니다. 유닛이 말하게 하기(유닛 선택, 글 입력, 스타일 조정, 모델과 대화), 일꾼 수다 모임, 카메라 대화, 전황 트리거, 모델 설정을 제공하며, 상단 바에서 고른 인스턴스를 대상으로 동작합니다.
명령줄로도 할 수 있습니다:
python speech/speak_launch.py # 로컬 모델 서버 시작 + 모델 로드 및 워밍업 + 말풍선 API 시작
python speech/speak_launch.py --restart # 코드를 고친 뒤 API 재시작
python speech/speak_launch.py --stop # API를 멈추고 모델을 VRAM에서 내림
모든 단계가 “이미 있으면 건너뛰기”이므로 여러 번 실행해도 부작용이 없습니다.
HTTP API
기본 주소는 http://127.0.0.1:8872/입니다(포트는 openwar3.json의 ports.speech). 어떤 프로그램이든 호출할 수 있습니다.
유닛이 말하게 하기 POST /api/say
{
"inst": 16,
"bubbles": [
{ "unit": "0x14A12614", "name": "마운틴 킹", "text": "나를 따르라!" },
{ "unit": "0x14A12924", "name": "아크메이지", "text": "눈보라는 내가 맡지.",
"style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } },
{ "screen": [960, 110], "key": 1, "name": "내레이션", "text": "첫 번째 오크 부대가 30초 후 도착합니다.",
"style": { "tail": false, "type_ms": 0 } },
{ "world": [-4684, 2644], "key": 2, "text": "집결 지점", "style": { "font_px": 16 } }
]
}
| 필드 | 설명 |
|---|---|
unit / world / screen | 셋 중 하나: 유닛을 따라다님(체력 바가 있으면 체력 바 바로 위에 붙음) / 맵 좌표 / 화면 픽셀(내레이션용) |
name | 첫 줄에 표시되는 화자. 자유롭게 쓰면 되며 해당 유닛일 필요는 없음 |
text | 본문. 자동 줄바꿈 |
duration_ms | 표시 시간. 0 = 자동 3 ~ 5초 |
key | 월드 / 화면 말풍선의 번호. 같은 key의 새 메시지가 이전 메시지를 대체 |
update | 같은 말풍선이 이미 있으면 글자만 바꾸고 타이머는 초기화하지 않음(스트리밍 출력용) |
style | font_px, max_width_px, text_color, bg_color, border_color, tail, side_px, opacity, type_ms, font…… |
말풍선은 동시에 최대 32개까지 띄울 수 있으며, 프레임당 오버헤드는 평균 약 0.1 ~ 0.2 ms입니다.
로컬 모델과 대화하기 POST /api/chat
{
"inst": 16, "unit": "0x14A12614", "name": "마운틴 킹",
"persona": "너는 워크래프트의 마운틴 킹 무라딘을 연기한다. 호탕하고 술을 좋아한다. 구어체로 한두 문장, 40자 이내.",
"message": "앞에 오우거 무리가 있는데, 돌격할까?",
"stream": true
}
{"reply": "...", "first_token_ms": 283, "total_ms": 342}를 반환하며, 그와 동시에 답변이 이미 해당 유닛 머리 위에 나타나 있습니다. 같은 유닛은 최근 6턴의 대화를 기억합니다.
기타
| API | 설명 |
|---|---|
GET /api/instances | 실행 중인 게임 |
GET /api/units?inst=16&mine=true&heroes=true | 유닛 목록(중국어 이름, 좌표, 체력 포함) |
POST /api/clear | 말풍선 하나 또는 전부 지우기 |
GET /api/llm, POST /api/llm | 모델 설정 보기 / 변경(base_url, model, max_tokens, temperature) |
POST /api/banter | 일꾼 수다 모임: 본진의 일꾼들이 캐릭터 설정에 따라 돌아가며 투덜거리고, 게임 시작 때 소개 멘트를 함(전황은 모두 실제 데이터) |
POST /api/camtalk | 카메라 대화: 화면에 잡힌 영웅과 수행원이 신분에 맞게 대화 |
POST /api/events | 전황 트리거: 교전 시작, 교전 종료, 영웅 사망, 티어 업, 건물 파괴…… 일이 생겼을 때만 말함 |
로컬 모델 고르기
RTX 5090 한 장에서 실측한 결과입니다(게임 대사 5문장).
| 모델 | VRAM | 속도 | 답변 1회 | 결론 |
|---|---|---|---|---|
| Qwen3.6-35B-A3B(MoE, 매번 3B만 활성화), Q4, 사고 끔 | 20.6 GB | 약 142 token/s | 약 0.3초(첫 토큰 약 0.27초) | 추천: 빠르고, 중국어 롤플레이가 자연스러움 |
| gpt-oss-20b(MXFP4), 추론 low | 11.3 GB | 약 280 token/s | 0.3 ~ 0.8초 | VRAM이 빠듯할 때 사용. 중국어가 다소 밋밋함 |
| Qwen3.6-27B(dense), Q4 | 17.2 GB | 약 39 token/s | 5.5초가 지나도 아직 사고 중 | 실시간 대화에 부적합 |
- 속도는 총 파라미터가 아니라 “매번 활성화되는 파라미터 수”가 결정합니다: 35B MoE는 3B만 활성화하므로 27B dense 모델보다 3 ~ 4배 빠릅니다.
- “사고(thinking)“는 반드시 꺼야 합니다: 끄지 않으면 토큰을 모두 사고에 써 버려 답변이 한 글자도 나오지 않습니다.
- 말풍선은 초당 약 22자씩 한 글자씩 찍히므로 생성 속도는 더 이상 병목이 아닙니다. 체감에 실제로 영향을 주는 것은 첫 토큰 지연입니다.
대사를 “진짜처럼” 만들기
모델에 주는 전황은 모두 실제 데이터(경기 수, 승패, 병력, 보유 자원)로 채우고, “이 사실만 사용할 것”을 명시하세요. 실측해 보니 이 제한을 두지 않으면 모델이 일어나지도 않은 전투를 지어냈습니다.