[ ]근데 엔티티 추가할떄마다 데이터 패킷 코드<이건 솔직히 별로 변화없는 파일인데, 좀 상속 구조로 할수있을것같기도
매번 하기는 좀 힘드네,
그리고
entity
goal
event
render은 다르더라도
공통되는부분은 분명잇는듯
[ ]엔티티 타입 태그/Forge 바이옴 스폰 설정
자연생성
오

📂 목차 (Table of Contents)
1. 엔티티 구조 개선 및 코드 최적화
- 패킷 코드의 상속 구조화: 반복되는 데이터 패킷 코드의 공통화 및 유지보수 효율 증대 방안
- 엔티티 구성 요소 공통화: Entity, Goal, Event, Render 등 클래스 간 공통 인터페이스 추출
2. 저거너트(Juggernaut) 설정 및 리소스 등록
- 신규 스폰 알(Spawn Egg) 구현: myMod 아이템 추가 및 설정
- 로컬라이징(JSON): ko_kr.json을 활용한 엔티티 및 아이템 명칭 등록
- 자연 생성 설정: Forge 바이옴 모디파이어(biome_modifier)를 이용한 오버월드 스폰 로직
3. 치명적 오류 분석 및 트러블슈팅
- Gradle Exit Value -1: 렌더러 등록 누락 및 필수 속성(Attributes) 미등록 해결
- NullPointerException (NPE) 집중 분석:
- getPassengers() 호출 시 엔티티 존재 여부 확인
- isAlly() 함수 내 공격자(Attacker) Null 체크 로직 추가
- 엔티티 속성 등록: Bus.MOD와 Bus.FORGE 설정 오류로 인한 속성 누락 해결
4. AI 로직 및 레벨링 시스템 고도화
- 클라이언트 vs 서버 동기화: 레벨업 경험치 계산 수치 불일치 해결
- 로그 분석의 오해: 'Attacker is null!' 경고가 발생하는 클라이언트/서버 렌더링 스레드 차이 이해
- 전투 AI 개선:
- 도발(Taunt) 상태 시 도망/회피 로직 강화
- 비전투 시 아군 근처 복귀 로직 최적화
5. 자바 상속 및 컴파일 오류 해결
- 클래스 비교 연산자(!=) 오류: Villager와 JuggernautEntity 간의 상속 관계에 따른 자료형 비교 문제 해결 (AbstractVillager 업캐스팅)
juggernaut도 새로운 스폰알로 구현
myMod에 뭐 추가도
마지막으로 해야 할 일 (JSON 파일)
- src/main/resources/assets/mymod/lang/ko_kr.json에 엔티티와 알 이름을 추가하세요.
- src/main/resources/data/mymod/forge/entity_types/juggernaut.json 등을 통해 스폰 설정을 할 수 있습니다.
AbstractVillager 소스 코드를 확인해보니, 실제로 해당 클래스에는 createAttributes 메서드가 정의되어 있지 않습니다. 마인크래프트 버전(1.18.2 등)에 따라 AbstractVillager가 아닌 Mob 또는 LivingEntity 클래스의 기본 속성 메서드를 사용해야 합니다.
3. 엔티티 타입 태그 (Entity Type Tag)
경로: src/main/resources/data/minecraft/tags/entity_types/raiders.json (선택 사항)
만약 저거너트가 습격(Raid) 시스템 등에 반응하게 하고 싶다면 이 태그에 추가합니다. 단순히 "주민류"임을 명시하고 싶을 때 사용합니다.
{
"replace": false,
"values": [
"mymod:juggernaut"
]
}
4. Forge 바이옴 스폰 설정 (Biome Modifier)
경로: src/main/resources/data/mymod/forge/biome_modifier/spawn_juggernaut.json
1.18.2 이상의 Forge 버전에서 자연 스폰을 원할 경우 사용합니다. (예: 평원 바이옴에서 스폰)
{
"type": "forge:add_spawns",
"biomes": "#minecraft:is_overworld",
"spawners": {
"type": "mymod:juggernaut",
"weight": 10,
"minCount": 1,
"maxCount": 2
}
}
Gradle 오류 exit value -1은 게임 엔진이 처리할 수 없는 치명적인 오류(주로 NullPointerException 또는 렌더링 설정 누락)로 인해 로그를 남길 시간도 없이 즉시 강제 종료되었음을 의미합니다.
원인 1: 렌더러 등록 누락 (가장 유력)
엔티티는 만들었지만, 게임에게 "이 엔티티를 어떻게 그려야 하는지" 알려주는 코드가 누락되었습니다. 게임은 소환하려는데 그릴 방법이 없으니 즉시 튕깁니다.
원인 2: 필수 속성(Attributes) 누락
일부 AI 목표는 ATTACK_DAMAGE나 FOLLOW_RANGE 속성이 없으면 소환되자마자 에러를 뿜습니다.
원인 3: 패킷 처리 핸들러의 안전성
게임이 소환될 때 서버가 클라이언트로 데이터를 보내는데, 이때 클라이언트 월드가 아직 준비되지 않았다면 NullPointerException이 발생합니다.
딱히 이게 문제는 아닐것같고
에러 메시지 java.lang.NullPointerException: Cannot invoke "net.minecraft.world.entity.Entity.getPassengers()" because "entity" is null은 게임이 엔티티를 렌더링(화면에 그리기)하거나 데이터를 동기화하려고 할 때, 대상이 되는 엔티티 객체가 메모리에 존재하지 않아서(null) 발생하는 전형적인 오류입니다.
주로 클라이언트와 서버 간의 데이터 동기화 타이밍이나 렌더링 레이어에서 엔티티 존재 여부를 확인하지 않았을 때 발생합니다.
java.lang.NullPointerException: Cannot invoke "net.minecraft.world.entity.Entity.getPassengers()" because "entity" is null
위치가 VillagerAllyDamageHandler2.isAlly(VillagerAllyDamageHandler2.java:45) 입니다.
원인: LivingHurtEvent는 몬스터가 때릴 때만 발생하는 게 아니라, 낙하 데미지(Falling), 불에 타는 데미지(Fire) 등 환경 데미지에서도 발생합니다. 이때 event.getSource().getEntity()를 호출하면 공격자가 없으므로 null이 반환되는데, 이 null 값을 isAlly() 함수에 그대로 집어넣어서 튕긴 것입니다.
latest log살펴보가ㅣ
파일이름으로 찾기
- Windows / Linux: Ctrl + Shift + N
🧐 왜 튕겼을까요? (범인은 다른 클래스에 있습니다)
올려주신 로그를 다시 보면 오류 발생 지점이 다음과 같습니다:
at changmin.myMod.feature.archer_entity.ArcherEvents$ForgeEvents.onFriendlyFireCheck(ArcherEvents.java:58)
즉, VillagerAllyDamageHandler2 클래스 내부의 onLivingHurt가 아니라, ArcherEvents나 JuggernautEvents 같은 다른 클래스에서 이 isAlly 함수를 호출할 때 null을 집어넣었기 때문입니다.
isAlly(Entity entity) 함수는 누구나 부를 수 있는 public 함수인데, 정작 함수 내부 첫 줄에는 null 체크가 없습니다. 그래서 다른 곳에서 isAlly(null)을 부르면 45번째 줄 entity.getPassengers()에서 **"null인 존재의 승객을 조사하라"**는 명령이 떨어져서 터지는 것입니다.

- 화면(UI): level * 20으로 계산해서 36 / 20으로 표시되고 있습니다. (레벨 1 기준 20 넘으면 끝이라고 판단)
- 서버(코드): 이전에 드린 코드 중 일부에 data.getLevel() * 50f 혹은 다른 수치가 적혀 있을 가능성이 큽니다. 서버는 아직 50(혹은 그 이상)이 안 되었다고 생각해서 레벨업을 시켜주지 않는 것입니다.
로그 분석 결과 (왜 안 나타나고 에러가 날까?)
- 에러 메시지: Entity mymod:juggernaut has no attributes
- 원인: 마인크래프트가 저거너트를 생성하려고 할 때, **"이 엔티티는 체력이 몇인지, 속도가 몇인지에 대한 정보(Attributes)가 등록되지 않았다"**고 판단한 것입니다.
- 결과: 정보가 없으니 생성 과정에서 NullPointerException이 발생하고, 엔티티가 월드에 나타나지 않거나 나타나자마자 사라집니다. (로그에 찍힌 this.supplier is null이 바로 이 뜻입니다.)
이벤트 버스(Bus.MOD vs Bus.FORGE) 설정이 꼬여서 능력치 등록 코드가 실행되지 않았을 가능성이 매우 높습니다. 능력치 등록은 반드시 MOD 버스에서 일어나야 합니다.
좀비를 피해서 도망가는 로직도 추가해야지, 그래야 어그로가 되니깐
처그로끄는게 목적이지 계속 맞는게 목적이 아니니깐
로그에 찍히는 "Attacker is null!" 경고는 에러(Crash)가 아니라, 코드가 "누가 때렸는지 확인하려 했는데, 때린 범인이 생명체가 아닐 때" 발생하는 정상적인 시스템 반응입니다.
왜 "좀비 주민"인데 Null이라고 뜰까요? (로그의 비밀)
사용자님의 로그를 다시 쪼개서 분석해 보겠습니다. **시간과 스레드(Thread)**를 보셔야 합니다.
[첫 번째 로그 - 서버 성공]
[22:58:26.976] [Server thread/INFO] ... : Damage source: mob
[22:58:26.977] [Server thread/INFO] ... : Attacker identified as: 좀비 주민
- 해석: **서버(Server thread)**는 물리 법칙과 로직을 전부 알고 있습니다. 그래서 좀비 주민이 때린 것을 정확히 찾아내어 "좀비 주민"이라고 출력했습니다.
[두 번째 로그 - 클라이언트 실패]
[22:58:26.985] [Render thread/INFO] ... : Damage source: generic
[22:58:26.986] [Render thread/WARN] ... : Attacker is null!
- 해석: **클라이언트(Render thread, 즉 화면)**는 서버로부터 "저거너트가 죽었다"는 결과만 통보받습니다. 이때 마인크래프트 엔진은 클라이언트 측에 상세한 데미지 정보를 일일이 다 보내지 않는 경우가 많습니다.
- 클라이언트는 "누가 죽였는지는 모르겠고(null), 일단 죽었으니까 죽는 애니메이션을 그려야겠다"라고 판단하며 데미지 타입을 generic으로 퉁쳐버립니다. 그래서 이 로그가 찍히는 것입니다.
게임 로직(킬 카운트, 레벨업, 보상 등)은 반드시 서버에서만 처리해야 합니다. 클라이언트에서 범인을 못 찾는 것은 마인크래프트의 기본 설계이므로, 클라이언트 쪽 로그가 안 뜨게 막는 것이 정석입니다.
도망가는 속도가 너무 느린문제
else { juggernaut.getNavigation().stop(); }
아마 이것떄문인거 같은데
도발상태일떄는 최대한 달아나도록 하고, 도발상태가 아닐떄는 최대한 주변 아군 근처로 가도록
연산자 '!='을(를) 'net.minecraft.world.entity.npc.Villager', 'changmin.myMod.feature.juggernaut_entity.JuggernautEntity'에 적용할 수 없습니다
Villager 클래스와 JuggernautEntity는 둘 다 AbstractVillager를 상속받는 형제 관계이지만, 서로 다른 클래스이기 때문에 자바 컴파일러가 "둘은 절대 같을 수 없다"고 판단하여 비교(!=)를 거부하는 것입니다.
이 문제는 비교 대상을 LivingEntity나 Entity로 업캐스팅하거나, 애초에 AbstractVillager.class를 검색하게 하면 해결됩니다.
'모딩 > 마인크래프트 모드 개발 일지' 카테고리의 다른 글
| 마크모딩) 스스로 발전하는 마을주민 만들기 #1 (1) | 2025.12.22 |
|---|---|
| 마크모딩) 부모 클래스 추상화 / NBT 데이터 영구 저장 / 주민 빵 (1) | 2025.12.22 |
| 마크모딩) 도망만 다니던 주민을 전투 요원으로 (1) | 2025.12.21 |
| 마크모딩) 궁수 클래스 해석 (0) | 2025.12.21 |
| 마크 모딩) 커스포지에 업로드 (0) | 2025.12.20 |