마인크래프트 모딩으로 '전사 주민'을 만드는 프로젝트를 시작했음.
목표는 간단했음. 특정 직업(무기 대장장이)을 가진 주민이 좀비를 보면 칼을 들고 돌진해서 공격하게 만드는 것.
그런데 이상한 문제가 발생함.
분명히 좀비를 향해 돌진은 하는데, 가장 중요한 칼 휘두르는 애니메이션이 전혀 나오지 않았음.
그냥 좀비한테 몸을 비비면서 데미지를 주는, 아주 이상한 그림이 연출됨.
게다가 평소에 칼을 잘 들고 있지도 않았음.
이 두 가지 문제를 해결하기 위한 기나긴 삽질의 기록을 시작함.

1차 시도: 공격 AI 타이밍 문제인가?
가장 먼저 의심했던 건 공격 타이밍 문제였음.
내 코드는 MeleeAttackGoal을 상속받아 만들었는데, 공격 명령(swing())을 내리는 타이밍이 어긋나서 애니메이션이 씹히는 게 아닐까 싶었음.
핵심 원리: 서버와 클라이언트의 역할 분담
이 문제를 이해하려면 게임의 작동 방식을 알아야 함. 아주 간단한 비유를 들어보겠음.
- 서버 (보이지 않는 두뇌) 🎙️: 라디오 방송국. "지금 공격해!", "데미지 10 입혀!" 같은 모든 계산과 결정을 함.
- 클라이언트 (내 눈에 보이는 화면) 📻: 내 차의 라디오. 방송국에서 신호를 보내줘야만 노래(애니메이션)를 재생함.
지금 상황은 서버가 "데미지 줬음!"이라고 계산은 다 끝냈는데, 정작 클라이언트에게 "팔 휘둘러!"라는 애니메이션 재생 신호를 보내지 않는 상태였음.
그래서 데미지는 들어가는데 우리 눈에는 주민이 가만히 있는 것처럼 보였던 것.
checkAndPerformAttack 메소드 안에서 공격 쿨다운이 딱 초기화되는 시점에 this.mob.swing(InteractionHand.MAIN_HAND);를 호출하도록 코드를 수정하며 타이밍을 계속 맞춰보려 했음. 하지만 결과는 실패.

2차 시도: AI끼리 싸우나?
코드를 수정해도 주민의 행동이 이상했음. 돌진하다가 갑자기 멈칫거리거나, 오히려 도망치면서 공격하는 듯한 기묘한 움직임을 보임.
원인: 배에 캡틴이 둘인 상황
이 현상을 보고 새로운 가설을 세웠음. 바로 AI의 충돌.
내 주민에게는 두 개의 공격 AI가 있었음.
- 캡틴 A: "멈춰서 회전 공격 준비!" (WarriorSpinAttackGoal)
- 캡틴 B: "앞으로 돌격해서 계속 공격!" (WarriorAttackGoal)

주민이 좀비를 발견하면, 우선순위가 더 높은 캡틴 A의 말을 들을지, 아니면 캡틴 B의 말을 들을지 혼란에 빠지는 것이었음. 이 두 명령이 엉키면서 "가려다 멈추고, 멈췄다 가려는" 불안정한 상태가 된 것.
이 문제를 해결하기 위해 일단 하나의 AI(WarriorAttackGoal)만 남겨서 기본 공격을 완벽하게 만들기로 방향을 바꿈.

3차 시도: 아... 아아! 깨달음의 순간
AI를 하나로 통일해도 문제는 해결되지 않았음. 여전히 칼을 휘두르지 않음.
계속 코드를 쳐다보다가 문득 근본적인 질문이 떠올랐음.
"잠깐만, 이 전사 주민이 칼 휘두르는 건... 기본 마인크래프트 몹에는 없는 기능이잖아?"
정확했음. 이게 문제의 최종 보스였음.
지금까지의 모든 시도는 **'팔을 구부릴 수 없는 마리오네트 인형'**에게 "팔을 구부려!" 라고 소리만 치고 있었던 것과 같았음.
주민의 3D 모델(뼈대) 자체에 '팔을 휘둘러 공격하는 애니메이션' 기능이 아예 없었던 것임.
아무리 서버에서 "팔 휘둘러!" 신호를 보내도, 클라이언트에 있는 주민 모델이 그 동작을 실행할 방법이 없으니 당연히 아무 일도 일어나지 않았던 것.

4차 시도: 렌더러 바꿔치기 (그리고 좌절)

원인을 알았으니 해결책은 명확했음. '팔을 휘두를 수 있는 모델'을 가져와서 쓰면 됨.
가장 만만한 건 '좀비 주민' 모델이었음. 좀비 주민은 팔을 휘두르며 공격하기 때문.
그래서 '렌더링 가로채기'라는 기법을 시도했음.
게임이 무기 대장장이 주민을 화면에 그리려고 할 때, 이 과정을 가로채서 원래의 VillagerRenderer 대신 ZombieVillagerRenderer를 사용해 그리도록 하는 방식.
하지만 이 방법은 자바의 강력한 타입 시스템에 막혀 처참하게 실패함.
Generated java
// 이런 코드를 시도했지만...
cachedZombieVillagerRenderer.render(villager, ...);
// 에러 메시지:
// 필요한 타입: ZombieVillager
// 제공된 타입: Villager
ZombieVillagerRenderer는 오직 ZombieVillager만 그릴 수 있는데, 엉뚱하게 일반 Villager를 그리라고 하니 당연히 거부하는 것이었음. 이 접근은 원천적으로 불가능했음.
최종 해결: "카멜레온 렌더러" 만들기

결국 가장 정석적이고 확실한 방법으로 돌아옴.
다른 것을 빌려 쓰는 게 아니라, **"스스로 변신할 수 있는 우리만의 전용 렌더러"**를 만드는 것.
해결 과정 요약:
- 새로운 모델 생성: VillagerModel을 기반으로, 팔을 휘두르는 애니메이션을 추가한 WarriorVillagerModel을 만듦.
- 새로운 렌더러 생성: 이 렌더러는 내부에 '일반 주민 모델'과 '전사 주민 모델'을 둘 다 가지고 있음.
- 렌더러의 변신 로직 추가: 렌더러가 주민을 그리기 직전, "이 주민이 무기 대장장이인가?"를 확인함.
- 맞으면? -> '전사 주민 모델'로 변신해서 그림.
- 아니면? -> '일반 주민 모델'을 사용해 그림.
- 게임에 등록: 이 똑똑한 카멜레온 렌더러를 게임의 공식 주민 렌더러로 덮어씌움.
이 방법은 AI 같은 다른 파일을 전혀 건드리지 않고, 오직 겉모습을 그리는 Renderer와 Model 파일만 수정하여 문제를 완벽하게 해결했음.
엔진(AI)은 이미 완벽했음. 문제는 차체(모델)에 있었고, 우리는 도색 공장(렌더러)에서 상황에 맞게 차체를 바꿔 끼우도록 만든 것임.
끝남

결국 '전사 주민'은 성공적으로 칼을 휘두르게 되었음.
단순한 AI 타이밍 문제라고 생각했던 것이, 서버/클라이언트 개념을 거쳐, AI 충돌, 그리고 엔티티의 모델과 렌더러라는 게임 그래픽의 깊은 부분까지 파고들게 된 대장정이었음.
역시 모딩은 삽질하는 만큼 배우는 것 같음.
'모딩 > 마인크래프트 모드 개발 일지' 카테고리의 다른 글
| [마크 모딩 삽질기] #8 주민아, 연애 좀 해! (feat. 뇌 제어 실패기) (11) | 2025.06.27 |
|---|---|
| [마인크래프트 모딩] #7 침대 없이 강제 번식시키는 마법의 빵 만들기 (Feat. 무한 버그) (12) | 2025.06.26 |
| [마인크래프트 모딩] #5 주민 AI가 단체로 파업한 이유 (9) | 2025.06.24 |
| [마인크래프트 모드 개발 일지]25.6.22/25.6.23 (0) | 2025.06.23 |
| [마인크래프트 모드 개발 일지]25.06.23 (14) | 2025.06.23 |