일단, 이때까지는 갑옷제작자 주민이 탱커역할을,
성직자가 힐러 역할을 햇는데
그냥 스폰알로도 생성할수있게 바꿈
또한 상속 이용할수있게함
공통적인
체력바등은

사건 파일 #1: UI의 반란 (사라지거나, 눕거나, 발밑에 있거나)
현상: 야심 차게 만든 커스텀 체력바/경험치바가 안 보임. 혹은 보여도 주민과 함께 바닥에 눕거나, 머리가 아닌 발밑에 생기는 기현상 발생.
진실: 이건 한 가지 원인이 아님. **'UI를 그리는 방식과 타이밍, 그리고 데이터의 부재'**라는 세 가지 용의자가 얽힌 복합적인 사건임.
용의자 A: 옷에 그림 그리기 (Render Layer의 한계)
이전 방식은 'Render Layer'라고 해서, 주민 모델에 체력바를 '덧입히는' 방식이었음.
- 비유: '주민'이라는 마네킹에 '체력바' 그림이 인쇄된 옷을 입히는 것과 같음.
- 문제점: 마네킹을 옆으로 돌리면? 옷에 그려진 그림도 당연히 돌아감. 마네킹을 바닥에 눕히면? 그림도 같이 누워버림.
- 결과: 이게 바로 체력바가 주민과 함께 바닥에 드러눕는 이유임. UI를 그릴 때 이미 주민 모델의 모든 회전 값이 적용된 상태라, UI도 그 영향을 그대로 받은 것.
용의자 B: 드론 띄우기 (Render Event로 해결)
새로운 방식은 'Render Event'를 사용함. 렌더링이 '끝난 후'에 개입하는 방식임.
- 비유: '머리 위에 드론 띄우기'와 같음.
- 해결책: 일단 마네킹(주민) 렌더링을 완전히 끝냄. 그 후, 별도의 드론(이벤트 핸들러)이 날아와 마네킹의 머리 위 좌표만 딱 따옴. 그리고는 "항상 카메라(플레이어)를 정면으로 바라보도록" 설정된 '체력바' 간판을 그 위치에 띄움.
- 결과: 주민이 어떤 자세를 취하든, 간판은 항상 우리를 정면으로 바라봄. 이게 바로 우리가 원했던 빌보드 효과임.
용의자 C: 좌표 계산 순서의 오류
만약 UI가 머리가 아닌 발밑 같은 엉뚱한 곳에 생긴다면, 이건 좌표 계산 순서 문제임.
- 잘못된 순서: 1. 엔티티 키만큼 허공으로 이동 -> 2. 머리 모델 기준으로 다시 이동. (결과: 좌표가 꼬여서 발밑으로 감)
- 올바른 순서: 1. 먼저 모델의 머리 위치로 좌표계를 이동 -> 2. 그 머리 위치를 기준으로 살짝 위로 이동. 순서가 모든 것을 결정함.
용의자 D: 데이터 저장소(Capability)의 부재
"코드는 완벽한데 UI가 아예 안 보여요!" 이건 십중팔구 이 문제임.
- 비유: 자동차에 최첨단 디지털 계기판(UI)을 설치했음. 근데 계기판을 엔진(데이터)에 연결하는 선을 빼먹은 거임. 계기판은 보여줄 데이터(현재 레벨, 경험치)가 없으니 아무것도 표시하지 못함.
- 해결책: 엔티티가 생성될 때(AttachCapabilitiesEvent 이벤트에서), LevelData를 저장할 수 있는 능력(LevelDataProvider)을 반드시 붙여줘야 함. 이 '연결선' 작업이 빠지면 UI는 영원히 보이지 않음.
사건 파일 #2: 게임 로딩 실패와 의문의 충돌
현상: 모드를 넣었더니 게임이 켜지지도 않고 바로 꺼짐. 로그 파일에는 Registry Object not present나 Duplicate Capability Key 같은 알 수 없는 메시지만 가득함.
진실: 이건 게임이 시작되기 전, '준비 단계'에서 규칙이 어긋났기 때문임.
용의자 A: 성급한 주방 보조 (Registry Object not present)
게임 로딩 과정을 '주방 오픈 준비'에 비유할 수 있음.
- 비유: 주방장이 '오늘의 메뉴' 등록부에 "삼지창 파스타"를 등록해야 함. 그런데 성격 급한 주방 보조가 등록부에 이름이 올라가기도 전에 "삼지창 파스타 레시피(속성)는 이거예요!"라고 먼저 들이민 상황임.
- 주방장의 반응: "잠깐... '삼지창 주민'이 누군데? 아직 내 등록부에 그런 메뉴 없어!" 라고 소리치며 주방 문을 닫아버림 (게임 크래시).
- 원인: 엔티티나 아이템을 등록하는 코드(MyMod.java 등)가 실행되기도 전에, 그 엔티티의 속성을 설정하거나 사용하려는 이벤트 핸들러(TridentVillagerEvents)가 먼저 호출되어서 발생한 로딩 순서 문제임.
용의자 B: 똑같은 이름의 주머니 (Duplicate Capability Key)
이건 Capability, 즉 데이터 저장소를 중복으로 등록했을 때 발생함.
- 비유: 한 사람에게 "mymod:archer_data"라는 똑같은 이름표가 붙은 주머니를 두 번 달아주려고 하는 것과 같음. 시스템은 혼란에 빠지며 모든 것을 중단시킴.
- 원인: 보통 코드를 리팩토링하거나 옮기면서 발생함. 예전 패키지에 있던 이벤트 핸들러(feature/archer)와 새로 만든 이벤트 핸들러(feature/archer_entity)가 둘 다 똑같은 이름의 Capability를 붙이려고 동시에 작동하면서 충돌한 것.
사건 파일 #3: 이름 없는 스폰알의 비극
현상: 다른 아이템은 이름이 잘만 나오는데, 유독 새로 만든 스폰알만 item.mymod.mage_villager_spawn_egg 같은 코드명으로 뜸.
진실: 이건 **'이름표(ID) 연쇄 반응'**과 '잘못된 주소로 보낸 택배' 문제임.
연결의 원리: 이름표 연쇄 반응
스폰알이 주인(엔티티)을 찾아가고, 자기 이름표를 찾는 과정은 약속된 연쇄 반응임.
- 1단계 (코드): 관계 설정. ForgeSpawnEggItem을 만들 때, "이 알은 TRIDENT_VILLAGER 엔티티를 소환하는 알이다!" 라고 코드로 직접 관계를 맺어줌. 이게 핵심.
- 2단계 (모양): 게임은 1단계에서 정한 아이템 ID(trident_villager_spawn_egg)를 보고, assets/mymod/models/item/ 폴더에서 똑같은 이름의 .json 파일을 찾아 모델을 적용함.
- 3단계 (이름): 게임은 lang 파일에서 item.모드ID.아이템ID 형식의 키를 찾음. item.mymod.trident_villager_spawn_egg 키를 찾아 "Trident Villager Spawn Egg"라는 값을 화면에 보여줌.
문제의 원인: 잘못된 주소로 보낸 택배
DeferredRegister로 아이템을 등록할 때, 우리는 MyMod.MODID를 인자로 넘겨줌. 이건 **'보내는 사람의 공식 주소(모드 ID)'**와 같음.
- 비유:
- 정상: "보내는 주소: mymod" 라고 적힌 신청서를 냄. 게임(우체국)은 mymod 앞으로 온 우편물(lang 파일)을 뒤져서 item.mymod... 라는 이름표를 찾아줌.
- 문제: 어떤 이유로 인해, "보내는 주소: changmin" 이라고 적힌 신청서가 제출됨. 하지만 lang 파일이라는 우편물은 mymod 주소로 배달되었음.
- 우체국의 반응: "어? item.mymod.mage_villager_spawn_egg를 찾아야 하는데, 우편물은 다 changmin 앞으로 왔네? 주소가 다르니 맞는 이름이 없음. 그냥 송장 번호(코드명) 그대로 보여주자."
- 결론: 아이템 등록 시 사용한 모드 ID와, lang 파일의 키에 사용된 모드 ID가 달라서 생긴 문제. 모든 리소스는 **하나의 공식 MODID**로 통일해야 함.
사건 파일 #4: 코드 구조의 미학 (상속과 구현의 올바른 위치)
현상: "이 코드는 어디에 넣어야 하지?", "implements는 어떤 파일에?", "추상 클래스는 왜 쓰는 거지?" 등 코드 구조 자체에 대한 근본적인 질문들.
템플릿 메서드 패턴: 총괄 셰프와 전문 셰프
AbstractLevelingVillagerEntity 같은 추상 클래스는 **'코스 요리'**와 같음.
- 비유:
- 부모 (총괄 셰프): "코스 요리는 1.애피타이저, 2.메인 요리, 3.디저트 순서로 나간다!" 라는 전체적인 **'흐름(템플릿)'**만 결정함. 그리고 각 전문 셰프에게 "메인 요리는 makeMainCourse() 라는 이름으로 만들어 와!" 라고 **'규칙'**을 알려줌.
- 자식 (전문 셰프): '파스타 전문 셰프'는 makeMainCourse() 안에 '삼지창 파스타' 레시피를 구현하고, '스테이크 전문 셰프'는 똑같은 이름의 메서드 안에 'T-본 스테이크' 레시피를 구현함.
- 결과: 공통된 로직(레벨업, 경험치 추가)은 부모가 관리하고, 각자 달라야 할 부분(특수 AI, 기본 무기)만 자식이 구현하게 하여 코드의 중복을 막고 구조를 명확하게 함.
implements의 위치: 자동차 공장 비유
"이 기능은 어떤 파일에 implements 해야 할까?"는 **"그 기능이 무엇에 대한 설명인가?"**를 생각하면 쉬움.
- 비유:
- TridentVillagerEntity.java: 자동차의 '설계도'. 엔진, 바퀴 등 본질(Is-A)을 정의.
- TridentVillagerRenderer.java: 자동차의 '도색 가이드'. 색상, 로고 등 외형(Looks-Like)을 정의.
- TridentVillagerEvents.java: 공장의 '생산 라인 규칙'. 상호작용(Interacts-With)을 정의.
- 질문: "5초마다 자가 수리하는 기능"은 어디에 속할까? 이건 자동차 자체가 가진 고유한 능력이므로, 당연히 **'설계도'**에 포함되어야 함. 따라서 implements IRegeneratingMob는 TridentVillagerEntity.java에 하는 것이 맞음.
'모딩 > 마인크래프트 모드 개발 일지' 카테고리의 다른 글
| [마인크래프트 모딩]#17 나만의 소환사 주민 만들기 (9) | 2025.07.02 |
|---|---|
| [마인크래프트 모딩]#16 나만의 RPG 파티 만들기! (마법사 주민 편) (12) | 2025.07.01 |
| [마인크래프트 모딩]#14 주변 아군에게 광역 버프를 주는 '좀비 지휘관' (9) | 2025.06.29 |
| [마인크래프트 모딩]#13 삼지창을 퍼붓는 좀비 만들기 (9) | 2025.06.29 |
| [마인크래프트 모딩]#12 해골마 궁수 좀비 만들기 (8) | 2025.06.28 |