차원 전용 새 몹 — GemGuardian
Magic Realm 전용 적대 몹 GemGuardian을 Monster 상속으로 구현합니다. EntityType 등록, AttributeSupplier, 4큐브 GemGuardianModel, MobRenderer 설정까지 단계별로 다룹니다.
차원 전용 새 몹 — GemGuardian
이번 챕터에서는 master_capstone 전용 새 몹 GemGuardian을 만듭니다. GemGuardian은 Magic Realm(차원) 안에서만 스폰되며, 플레이어를 추적해 근접 공격합니다. 처치 시 Ruby Gem 계열 드롭을 제공해 캡스톤 진행 루프의 핵심 축이 됩니다.
이번 챕터에서 다루는 파일:
entity/GemGuardianEntity.java— Monster 상속 AI 설정client/model/GemGuardianModel.java— 4큐브 인간형 모델client/renderer/GemGuardianRenderer.java— MobRenderer 연결MasterCapstoneMod.java— EntityType + AttributeSupplier 등록
1. GemGuardianEntity 클래스
examplemod-master-projects/capstone/src/main/java/com/example/master/capstone/entity/GemGuardianEntity.java
public class GemGuardianEntity extends Monster {
public GemGuardianEntity(EntityType<? extends Monster> type, Level level) {
super(type, level);
}
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes()
.add(Attributes.MAX_HEALTH, 30.0)
.add(Attributes.ATTACK_DAMAGE, 6.0)
.add(Attributes.MOVEMENT_SPEED, 0.25)
.add(Attributes.FOLLOW_RANGE, 24.0);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2D, false));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0f));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
}속성 설계
| 속성 | 값 | 역할 |
|---|---|---|
MAX_HEALTH | 30.0 | HP 15하트 — 기본 좀비(20)보다 강함 |
ATTACK_DAMAGE | 6.0 | 근접 3하트 피해 |
MOVEMENT_SPEED | 0.25 | 기본 좀비(0.23)보다 약간 빠름 |
FOLLOW_RANGE | 24.0 | 기본 16보다 긴 탐지 범위 |
Goal 우선순위
Goal은 번호가 낮을수록 우선순위가 높습니다. FloatGoal(0)은 물에 빠지지 않는 필수 Goal — Monster 상속 시 반드시 추가하세요.
0: FloatGoal ← 물에서 떠다니기 (크래시 방지)
1: MeleeAttackGoal ← 1.2× 이동속도로 돌진 공격
2: WaterAvoidingRandomStrollGoal ← 물 피해 배회
3: LookAtPlayerGoal ← 8블록 이내 플레이어 응시
4: RandomLookAroundGoal ← 무작위 주시
targetSelector:
1: NearestAttackableTargetGoal<Player> ← 플레이어 우선 공격
2. EntityType 등록 + AttributeSupplier
MasterCapstoneMod.java에서 EntityType과 속성을 연결합니다.
public static final DeferredRegister<EntityType<?>> ENTITY_TYPES =
DeferredRegister.createEntities(MOD_ID);
public static final DeferredHolder<EntityType<?>, EntityType<GemGuardianEntity>> GEM_GUARDIAN =
ENTITY_TYPES.registerEntityType("gem_guardian",
GemGuardianEntity::new, MobCategory.MONSTER,
builder -> builder
.sized(0.6f, 1.95f)
.clientTrackingRange(80));생성자에서 레지스트리와 이벤트를 연결합니다.
public MasterCapstoneMod(IEventBus modEventBus, ModContainer container) {
ENTITY_TYPES.register(modEventBus);
modEventBus.addListener(MasterCapstoneMod::onEntityAttributes);
}
@SubscribeEvent
public static void onEntityAttributes(EntityAttributeCreationEvent event) {
// ⚠️ Monster 상속 Entity는 반드시 여기에 등록해야 합니다.
// 누락 시 /summon 에서 NullPointerException 발생.
event.put(GEM_GUARDIAN.get(), GemGuardianEntity.createAttributes().build());
}⚠️ AttributeSupplier 미등록 → /summon 크래시
Monster를 상속하지만createAttributes()를EntityAttributeCreationEvent에 등록하지 않으면:
→/summon시NullPointerException발생
항상entity.createAttributes()+event.put매핑 필수
3. GemGuardianModel — 4큐브 인간형 구조
client/model/GemGuardianModel.java는 텍스처 크기 64×32 기준 인간형 큐브 구조입니다.
public class GemGuardianModel extends EntityModel<LivingEntityRenderState> {
public static final ModelLayerLocation LAYER_LOCATION =
new ModelLayerLocation(
Identifier.fromNamespaceAndPath("master_capstone", "gem_guardian"),
"main"
);
public static LayerDefinition createBodyLayer() {
MeshDefinition mesh = new MeshDefinition();
PartDefinition partDef = mesh.getRoot();
partDef.addOrReplaceChild("head",
CubeListBuilder.create().texOffs(0, 0)
.addBox(-4.0f, -8.0f, -4.0f, 8, 8, 8, new CubeDeformation(0.0f)),
PartPose.offset(0.0f, 0.0f, 0.0f));
partDef.addOrReplaceChild("body",
CubeListBuilder.create().texOffs(16, 16)
.addBox(-4.0f, 0.0f, -2.0f, 8, 12, 4, new CubeDeformation(0.0f)),
PartPose.offset(0.0f, 0.0f, 0.0f));
// 오른팔, 왼팔, 오른다리, 왼다리 동일 구조로 추가
// (GemGuardianModel.java 전체 코드 참고)
return LayerDefinition.create(mesh, 64, 32);
}
@Override
public void setupAnim(LivingEntityRenderState renderState) {
this.head.yRot = renderState.yRot * Mth.DEG_TO_RAD;
this.head.xRot = renderState.xRot * Mth.DEG_TO_RAD;
float pos = renderState.walkAnimationPos;
float speed = renderState.walkAnimationSpeed;
this.rightArm.xRot = Mth.cos(pos * 0.6662f + Mth.PI) * 2.0f * speed * 0.5f;
this.leftArm.xRot = Mth.cos(pos * 0.6662f) * 2.0f * speed * 0.5f;
this.rightLeg.xRot = Mth.cos(pos * 0.6662f) * 1.4f * speed;
this.leftLeg.xRot = Mth.cos(pos * 0.6662f + Mth.PI) * 1.4f * speed;
}
}텍스처 레이아웃 (64×32)
UV 좌표 | 파트
---------|--------
0,0 | 머리 (8×8×8)
16,16 | 몸통 (8×12×4)
40,16 | 오른팔 (4×12×4)
40,16 미러 | 왼팔
0,16 | 오른다리 (4×12×4)
0,16 미러 | 왼다리
textures/entity/gem_guardian.png파일을 이 레이아웃에 맞게 그리면 됩니다.
현재 파일은 테스트용 보라색 Placeholder입니다.
4. GemGuardianRenderer
public class GemGuardianRenderer
extends MobRenderer<GemGuardianEntity, LivingEntityRenderState, GemGuardianModel> {
private static final Identifier TEXTURE =
Identifier.fromNamespaceAndPath("master_capstone",
"textures/entity/gem_guardian.png");
public GemGuardianRenderer(EntityRendererProvider.Context ctx) {
super(ctx, new GemGuardianModel(ctx.bakeLayer(GemGuardianModel.LAYER_LOCATION)), 0.5f);
}
@Override
public LivingEntityRenderState createRenderState() {
return new LivingEntityRenderState();
}
@Override
public Identifier getTextureLocation(LivingEntityRenderState renderState) {
return TEXTURE;
}
}렌더러와 레이어를 EntityRenderersEvent에 등록합니다.
@SubscribeEvent
public static void onRegisterRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerEntityRenderer(MasterCapstoneMod.GEM_GUARDIAN.get(),
GemGuardianRenderer::new);
}
@SubscribeEvent
public static void onRegisterLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) {
event.registerLayerDefinition(GemGuardianModel.LAYER_LOCATION,
GemGuardianModel::createBodyLayer);
}두 이벤트 모두 클라이언트 전용입니다.
@EventBusSubscriber(modid = ..., value = Dist.CLIENT)로 등록하거나(26.1.2:bus속성 없이 버스 자동 판별) 별도ClientEvents.java로 분리하세요.
5. 자연 스폰 설계 (다음 챕터 예고)
GemGuardian의 자연 스폰은 magic_realm 차원 안에서만 허용해야 합니다. Overworld 스폰은 캡스톤 설계 의도에 맞지 않습니다.
스폰 설정은 챕터 04 (04-event-integration)에서 BiomeModifier + SpawnData 방식으로 구현합니다.
챕터 03 (이번 챕터) — Entity 등록, Goal, 모델, 렌더러
챕터 04 (다음 챕터) — magic_realm 차원 내 자연 스폰 + LivingDeathEvent 드롭 확장
6. 인게임 검증
빌드 후 Minecraft 클라이언트에서 직접 소환해 동작을 확인합니다.
/summon master_capstone:gem_guardian ~ ~ ~
확인 항목:
| 항목 | 예상 동작 |
|---|---|
| 소환 성공 | 콘솔 에러 없이 엔티티가 나타남 |
| 플레이어 추적 | 24블록 이내 접근 시 달려옴 |
| 근접 공격 | 밀착 시 3하트 피해 |
| HP | /data get entity @e[type=master_capstone:gem_guardian,limit=1] Health → 30.0 |
/summon 직후 NullPointerException이 발생한다면 onEntityAttributes 이벤트 등록이 누락된 것입니다.
7. 이 챕터의 완료 기준
GemGuardianEntity.java가master_capstone:gem_guardian으로 등록됩니다.EntityAttributeCreationEvent에 속성 맵이 등록되어/summon크래시가 없습니다.GemGuardianModel과GemGuardianRenderer가 컴파일되고 레이어 등록이 완료됩니다.- 보라색 Placeholder 텍스처로 정상 렌더링이 확인됩니다.
- 다음 챕터(04)에서
magic_realm내 자연 스폰과 LivingDeathEvent 확장을 진행합니다.