삽질기를 하나 풀어볼까 함.
목표는 아주 심플하고 낭만적이었음.
"주민한테 빵을 던져주면, 그 빵을 향해 달려가고! 빵을 주운 주민은 운명의 상대를 찾아 또 달려가서! 둘이 만나면 하트 뿅뿅 아기를 낳게 하자!"
크으... 완벽한 시나리오 아님?
"그냥 Goal 하나 만들어서 순서대로 시키면 되겠지?" 라고 생각했던 과거의 나에게 심심한 애도를 표함.

1단계: 야심 찬 통합 AI 설계 (그리고 와장창)
처음엔 모든 걸 한 방에 해결하는 '만능 Goal'을 만들려고 했음. 이름하여 FindPartnerAndBreedGoal.
대충 이런 느낌으로 코드를 짰음. (의사 코드 주의)
Generated java
// 내 머릿속 완벽한 로직
public void tick() { // 매 틱(0.05초)마다 실행되는 부분
// 1. 짝이 없으면? 당장 찾아!
if (짝이_없다) {
findNearbyPartner(); // 주변 스캔해서 짝꿍 찾기
return; // 일단 찾았으니 이번 틱은 끝
}
// 2. 짝을 찾았으면? 일단 그윽하게 쳐다봐주고
주민.시선고정(짝꿍);
// 3. 짝을 향해 돌진!
주민.네비게이션.이동시켜(짝꿍); // 여기가 바로 주민을 움직이는 핵심 코드!
// 4. 거리가 3블록 안으로 가까워졌나?
if (주민과_짝꿍_거리가_가깝다) {
// 5. 그럼 사랑을 나눠라! (교배 시작)
forceBreed();
}
}
캬, 완벽하지 않음? 주민이 알아서 척척 움직이고 교배까지 할 것 같았음.
근데 결과는?
대참사.
2단계: 버그의 향연 (feat. 무한증식과 돌부처)
두 가지 심각한 문제가 터졌음.
문제 1: 공포의 무한증식 버그 😱
빵을 한 번 던졌을 뿐인데... 아기 주민이 10초마다 계속 태어남. 서버 로그를 까보니 가관이었음.
Generated log
[11:46:42] ... 주민이 짝을 찾았습니다!
[11:46:42] ... 주민이 교배를 시도합니다!
[11:46:42] ... 아기 주민이 태어났습니다!
(10초 후)
[11:46:52] ... 주민이 교배를 시도합니다!
[11:46:52] ... 아기 주민이 태어났습니다!
(또 10초 후)
[11:46:52] ... 주민이 교배를 시도합니다!
... (이하 반복) ...
원인은 간단했음.
내 코드는 주민 둘이 만나면 forceBreed()를 호출함. 근데 이 Goal 자체가 끝나질 않음.
두 주민은 계속 붙어있으니, 매 틱마다 "가깝네? 교배해!" 명령을 계속 내리고 있었던 거임.
쉽게 말해: "5초 뒤에 아기 낳아!" 라는 예약 문자를 1초에 20번씩 보내는 스팸봇을 만들어버린 셈.
문제 2: 움직이질 않는 돌부처 주민 ಠ_ಠ
더 황당한 건, 주민이 이동조차 안 함.
분명히 네비게이션.이동시켜() 코드를 넣었는데 왜?
이것도 원인은 '너무 빠른' 내 코드 때문이었음.
- Goal이 시작됨.
- 첫 tick()에서 findNearbyPartner() 호출.
- 운 좋게 바로 옆에 있던 다른 주민을 짝으로 찾음.
- 주민이 한 발짝 떼기도 전에 "어? 거리가 가깝네? 교배해!" 조건이 바로 참이 됨.
- 주민은 움직일 기회도 없이 그 자리에서 교배 모드로 들어가 버림.
쉽게 말해: 소개팅 앱에서 매칭되자마자 상대방 프로필 사진 보고 "우리 집에서 1m 거리네? 바로 결혼하자!" 하는 거랑 똑같음. 과정 따위 생략.
3단계: 진짜 범인의 등장 (feat. 주민의 '뇌')
코드를 아무리 뜯어고쳐도 해결이 안 됐음. 이동은 계속 씹히고, 버그는 터지고.
그러다 진짜 범인을 찾았음. 범인은 바로...
주민의 '뇌(Brain)' 시스템이었음!
마크 AI에는 두 가지 중요한 시스템이 있음.
- GoalSelector (행동 목록): "내가 할 수 있는 일들" 리스트. (예: 돌아다니기, 문 열기, 몬스터로부터 도망치기)
- Brain (총사령관): "지금은 이 리스트 중에서 뭘 해야 할 시간이지?"를 결정하는 스케줄 관리자.
나는 내 FindPartnerAndBreedGoal을 GoalSelector에 추가했음. "자, 주민아! 이제 연애도 할 수 있어!" 하고 선택지를 준 거임.
하지만 주민의 '뇌'는 내 말을 쌩깠음.
뇌: "어? 지금은 일할 시간(Activity.WORK)인데? 연애 같은 소리 하네. 딴짓 말고 밭이나 갈아!"
뇌는 자기 스케줄에 따라 GoalSelector의 행동 목록을 지 맘대로 초기화하고, '일하기', '돌아다니기' 같은 기본 행동을 강제로 우선시킴. 내 커스텀 Goal은 실행될 틈도 없이 덮어씌워지거나 순위에서 밀려버린 거임.
이동 명령이 씹힌 이유도 이거였음. [AI DEBUG] 이동 시작! 로그가 찍힌 0.05초 뒤에, 뇌가 "이제 한가한 시간(Activity.IDLE)이니까 어슬렁거려" 라며 내 명령을 덮어써 버린 것.
4단계: 해법을 찾아서 (상태 관리와 뇌 해킹)
이 모든 삽질 끝에 두 가지 중요한 깨달음을 얻었음.
깨달음 1: 깔끔한 '상태 전이'가 핵심이다
애초에 한 개의 Goal에 모든 걸 욱여넣은 게 문제였음. AI는 바보라서, 한 번에 하나씩 시켜야 함. 이럴 때 쓰는 게 바로 '기억(Memory)'을 이용한 바통 터치임.
이게 원래 정석적인 방법임.
- 빵 발견! ➡️ 이벤트 핸들러가 주민의 뇌에 MEMORY_BREAD_LOCATION (빵 위치) 기억을 저장함.
- 빵 먹으러 가기 Goal 활성화: 이 Goal은 뇌에 빵 위치 기억이 있을 때만 작동함. 빵에 도착하면 이 기억을 지우고, 대신 MEMORY_READY_TO_FIND_PARTNER (짝 찾을 준비 완료) 기억을 true로 설정.
- 짝 찾기 Goal 활성화: 이 Goal은 '짝 찾을 준비 완료' 기억이 true일 때만 작동함. 짝을 찾으면 이 기억을 지우고, 찾은 짝을 MEMORY_BREED_TARGET (교배 대상) 기억에 저장.
- 만나서 교배하기 Goal 활성화: 이 Goal은 '교배 대상' 기억이 있을 때만 작동함. 교배가 끝나면 이 기억을 지우면서 모든 과정이 깔끔하게 끝남.
이렇게 각 Goal이 자기 할 일만 하고, 다음 Goal에게 '기억'이라는 바통을 넘겨주는 방식이 훨씬 안정적임.
깨달음 2: 뇌를 거역하지 말고, 뇌를 이용하라
하지만 이 '상태 전이' 방식도 주민의 '뇌'가 허락해야 쓸 수 있음.
그래서 진짜 해결책은 Goal이 아니라 **Behavior (행동)**을 만드는 거임.
- 실패한 방식: GoalSelector(행동 목록)에 "이런 것도 해봐~" 하고 추가하기. (결과: 뇌가 무시함)
- 성공할 방식: Brain(총사령관)에게 직접 "이건 새로운 '업무'의 일종입니다!" 라고 보고해서, 뇌의 스케줄 자체에 **Behavior(행동)**를 끼워 넣기.
이건 훨씬 복잡해서... 나중으로 미루기로 했음. (도망)
※ 번외: 사이드 퀘스트
Q. 주민은 왜 내가 만든 다이아몬드 좀비랑 다른가요?
- 좀비: AI가 단순함. 뇌가 없음. 목표는 오직 "플레이어 발견 → 돌진 → 공격". GoalSelector만 건드려도 충분함.
- 주민: AI가 복잡함. '뇌'와 '스케줄'이 있음. 아침엔 일하고, 점심엔 잡담하고, 밤엔 잠. 이 스케줄을 관리하는 뇌를 직접 다뤄야 해서 훨씬 어려움.
Q. 렌더러 등록은 왜 '클라이언트 이벤트'에서 하죠?
- 서버 (주방장): 게임의 모든 규칙과 AI 행동을 계산함. "주민이 A에서 B로 이동했다" 같은 정보를 처리함.
- 클라이언트 (홀 서빙 직원): 주방장이 계산한 결과를 받아서 플레이어 눈에 '보여주는' 역할만 함. 모델링, 텍스처, 파티클 효과 등.
커스텀 주민의 겉모습을 그리는 Renderer는 당연히 '보여주는' 역할이므로, 플레이어의 PC, 즉 클라이언트 측에서만 등록하면 됨. 이걸 "클라이언트 이벤트에 등록한다"고 표현하는 거임. 프로젝트 구조를 feature/custom_villager 같은 패키지로 나누는 건 아주 좋은 습관임!
결론
간단한 주민 연애 시뮬레이션인 줄 알았는데, 주민의 뇌 구조까지 파헤치는 대장정이 되어버렸음.
결론은? 마크 모딩은 쉽지 않고, 특히 주민은 더럽게 똑똑하고 까다롭다.
오늘도 처절한 삽질로 한 단계 성장했다!
다음엔 진짜로 주민 '뇌'를 해킹하는 법을 들고 오겠음. 그럼 20000
'모딩 > 마인크래프트 모드 개발 일지' 카테고리의 다른 글
| [마인크래프트 모딩]#10 모든 살아있는 생명을 공격하는 좀비 (4) | 2025.06.28 |
|---|---|
| [마인크래프트 모딩]#9 주민 ai수정의 어려움에 대해서 (9) | 2025.06.28 |
| [마인크래프트 모딩] #7 침대 없이 강제 번식시키는 마법의 빵 만들기 (Feat. 무한 버그) (12) | 2025.06.26 |
| [마인크래프트 모딩] #6 "분명 데미지는 들어가는데..." 칼 안 휘두르는 주민, 범인은 의외의 곳에 있었다 (14) | 2025.06.25 |
| [마인크래프트 모딩] #5 주민 AI가 단체로 파업한 이유 (9) | 2025.06.24 |