차원 진입·복귀 로직
MagicPortalBlock.entityInside 오버라이드로 플레이어가 포탈 블록에 접촉할 때 magic_realm ↔ 오버월드 간 양방향 차원 이동을 구현합니다. 무한 TP 루프 방지 쿨다운 패턴도 다룹니다.
차원 진입·복귀 로직
이 챕터에서는 포탈 블록에 엔티티가 접촉할 때 실제 차원 이동이 일어나도록 entityInside 메서드를 구현합니다.
완성 후 동작:
- 오버월드 →
magic_realm: 포탈 블록을 밟으면 마법 차원으로 순간이동 magic_realm→ 오버월드: 마법 차원의 포탈 블록을 밟으면 복귀- 무한 TP 루프 방지: 쿨다운 체크로 즉각 재진입 차단
1. entityInside 오버라이드
왜 entityInside인가?
⚠️ NetherPortalBlock 직접 상속 금지
NetherPortalBlock은 내부 포탈 상태 관리 로직이 복잡합니다.Block을 직접 상속하고entityInside만 오버라이드하면 버전 간 안정성이 높습니다.
entityInside(BlockState, Level, BlockPos, Entity)는 매 틱마다 해당 블록 위치에 엔티티가 존재하면 호출됩니다. 이 메서드에서 서버 측 플레이어를 감지해 차원 이동을 처리합니다.
전체 구현
// examplemod-master-projects/dimension/src/main/java/com/example/master/dimension/block/MagicPortalBlock.java
package com.example.master.dimension.block;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Portal;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.portal.TeleportTransition;
import net.minecraft.world.phys.Vec3;
import net.minecraft.core.registries.Registries;
public class MagicPortalBlock extends Block implements Portal {
/** magic_realm 차원 키 — 데이터팩 JSON과 동일한 네임스페이스/ID */
public static final ResourceKey<Level> MAGIC_REALM_KEY =
ResourceKey.create(Registries.DIMENSION,
Identifier.fromNamespaceAndPath("master_dimension", "magic_realm"));
public MagicPortalBlock(BlockBehaviour.Properties props) {
super(props
.noCollision()
.strength(-1.0F, 3600000.0F)
.noLootTable());
}
public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
// 엔티티가 포탈에 들어오면 PortalProcessor가 작동하도록 등록
entity.setAsInsidePortal(this, pos);
}
@Override
public TeleportTransition getPortalDestination(ServerLevel serverLevel, Entity entity, BlockPos pos) {
ServerLevel target;
if (serverLevel.dimension() == MAGIC_REALM_KEY) {
// magic_realm → 오버월드 복귀
target = serverLevel.getServer().getLevel(Level.OVERWORLD);
} else {
// 오버월드(또는 기타 차원) → magic_realm 진입
target = serverLevel.getServer().getLevel(MAGIC_REALM_KEY);
}
if (target == null) {
// 차원이 로드되지 않았거나 데이터팩 JSON 누락 시 무동작
return null;
}
// 현재 좌표 기준 Y=64 안전 고도로 이동
Vec3 currentPos = entity.position();
return new TeleportTransition(
target,
new Vec3(currentPos.x(), 64.0, currentPos.z()), // 안전 높이 Y=64
entity.getDeltaMovement(),
entity.getYRot(),
entity.getXRot(),
TeleportTransition.PLAY_PORTAL_SOUND // 포탈 사운드 재생
);
}
}2. 무한 TP 루프 방지
⚠️ 안티패턴 — 쿨다운 없는 즉시 TP
// ❌ 쿨다운 체크 없이 즉시 TP public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { if (level.isClientSide() || !(entity instanceof ServerPlayer player)) return; ServerLevel target = player.getServer().getLevel(MAGIC_REALM_KEY); player.changeDimension(...); // ← 매 틱 호출 → 무한 루프 } // ✅ 쿨다운 체크 if (player.isOnPortalCooldown()) return; player.setPortalCooldown();
changeDimension호출 전 반드시setPortalCooldown()를 설정하세요. 도착 차원에서도 같은 포탈 블록에 충돌하면 다시entityInside가 호출되어 무한 루프가 발생합니다.
쿨다운 동작 원리
| 메서드 | 역할 |
|---|---|
player.isOnPortalCooldown() | 포탈 쿨다운이 남아 있으면 true 반환 |
player.setPortalCooldown() | 기본 쿨다운(약 1초) 설정 |
쿨다운 중에는 entityInside가 호출되더라도 return으로 즉시 탈출합니다.
3. DimensionTransition 파라미터
player.changeDimension(new DimensionTransition(
target, // 목적지 ServerLevel
new Vec3(currentPos.x(), 64.0, currentPos.z()), // 스폰 좌표 (Y=64 안전 고도)
player.getDeltaMovement(), // 속도 유지
player.getYRot(), // 수평 회전 유지
player.getXRot(), // 수직 회전 유지
DimensionTransition.PLAY_PORTAL_SOUND // 포탈 사운드 재생
));| 파라미터 | 설명 |
|---|---|
target | 이동할 ServerLevel — getLevel(key)로 획득 |
new Vec3(x, 64.0, z) | 목적지 좌표. currentPos의 XZ 유지, Y=64 고정 |
getDeltaMovement() | 현재 이동 벡터 — 관성 유지 |
getYRot() / getXRot() | 카메라 방향 유지 |
PLAY_PORTAL_SOUND | 포탈 통과 사운드 재생 콜백 |
Y=64 선택 이유: 이 챕터의
magic_realm은min_y: -64,height: 384로 설정되어 있어 Y=64는 지형 중간 안전 지점입니다. 목적지 지형이 달라질 경우 이 값을 조정하세요.
4. 양방향 이동 로직
entityInside 호출
│
├─ isClientSide? → return (클라이언트 무시)
├─ instanceof ServerPlayer? → NO → return (몬스터/아이템 무시)
├─ isOnPortalCooldown? → YES → return (쿨다운 중)
│
└─ setPortalCooldown()
│
├─ dimension == MAGIC_REALM_KEY?
│ ├─ YES → target = OVERWORLD (복귀)
│ └─ NO → target = MAGIC_REALM_KEY (진입)
│
└─ changeDimension(DimensionTransition)
5. PortalForcer 패턴 (선택 — 다음 챕터)
현재 구현에서는 목적지에 포탈이 없어도 Y=64로 이동합니다. 실제 제작 시에는 귀환 포탈 자동 생성 기능이 필요합니다.
PortalForcer 또는 커스텀 텔레포터를 구현해 목적지에 포탈 프레임을 자동 생성하는 로직은 05-finish-build 챕터에서 마무리합니다.
// 05-finish-build에서 추가 예정 — 현재는 스텁
// DimensionTransition의 5번째 파라미터에 커스텀 포탈 콜백 주입 가능6. 2초 대기 후 진입 (선택)
포탈에 닿는 즉시 이동하는 대신 2초 대기 후 진입하는 방식:
// 선택적 구현 — 네더 포탈과 동일한 방식
@Override
public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
if (level.isClientSide()) return;
if (!(entity instanceof ServerPlayer player)) return;
// 진입 틱 카운트 (포탈 블록이 BlockState를 통해 틱을 관리하는 방식)
// 단순 구현: setPortalCooldown 사용 시 즉시 이동
if (player.isOnPortalCooldown()) return;
player.setPortalCooldown();
// 2초 대기는 BlockEntity + tick을 활용하거나
// player.startUsingPortal() 등을 사용 (NeoForge API 참고)
// 이 챕터에서는 즉시 이동 방식을 채택
teleportPlayer(player);
}이 챕터에서는 즉시 이동 방식을 채택합니다. 2초 대기가 필요하다면 BlockEntity 틱 카운터 또는 NeoForge의
IPortalForcer확장을 활용하세요.
7. 서버 전용 실행의 중요성
if (level.isClientSide()) return;| 환경 | 동작 |
|---|---|
| 클라이언트 | isClientSide() == true → 즉시 return |
| 서버 | isClientSide() == false → TP 로직 실행 |
changeDimension은 서버 전용 API입니다. 클라이언트에서 호출하면 ClassCastException 또는 NullPointerException이 발생합니다.
다음 단계
entityInside 구현으로 포탈 진입·복귀 기능이 완성됩니다.
- 05-finish-build — Gradle 빌드 통합 + 인게임 종합 검증
- 포탈 프레임 점화 → 차원 진입 → 복귀 전체 시나리오 테스트
- 귀환 포탈 자동 생성 (PortalForcer)