파티클 등록과 스폰 — 시각 효과 직접 만들기
DeferredRegister로 커스텀 파티클 타입을 등록하고, 파티클 JSON과 텍스처를 구성한 뒤 서버에서 모든 클라이언트로 파티클을 전파하는 방법을 배웁니다.
파티클이란?
파티클은 Minecraft의 시각 효과 시스템입니다. 불꽃, 연기, 마법 가루처럼 화면에 짧게 나타났다 사라지는 작은 스프라이트 애니메이션입니다. 바닐라에도 수백 가지 파티클이 있고, 모드에서 직접 등록해 사용할 수 있습니다.
파티클은 클라이언트 전용 렌더링 객체입니다. 서버는 파티클을 직접 그리지 않고, 클라이언트에게 "이 위치에 파티클을 그려라"는 패킷을 보냅니다. 이 차이를 이해하는 것이 이번 챕터의 핵심입니다.
1단계 — PARTICLE_TYPES DeferredRegister
파티클 타입은 Registries.PARTICLE_TYPE 레지스트리에 등록합니다. 아이템·블록 등록과 동일한 DeferredRegister 패턴을 사용합니다.
examplemod-template-26.1.2/src/main/java/com/example/examplemod/ExampleMod.java
public static final DeferredRegister<ParticleType<?>> PARTICLE_TYPES =
DeferredRegister.create(Registries.PARTICLE_TYPE, "examplemod");
public static final Supplier<SimpleParticleType> SPARKLE = PARTICLE_TYPES.register(
"sparkle",
() -> new SimpleParticleType(false) // false = 멀리서도 보임
);SimpleParticleType 생성자의 boolean 인자는 overrideLimiter입니다. false로 설정하면 파티클 설정에서 "최소"로 낮춰도 이 파티클은 항상 렌더링됩니다. 중요한 시각 피드백에는 false를 권장합니다.
등록 후 메인 클래스의 생성자에서 PARTICLE_TYPES.register(modEventBus)를 호출해야 합니다.
public ExampleMod(IEventBus modEventBus, ModContainer modContainer) {
PARTICLE_TYPES.register(modEventBus);
// ... 다른 레지스터들
}2단계 — 파티클 JSON 정의
파티클 타입을 등록했다면 어떤 텍스처를 사용할지 JSON으로 알려줘야 합니다.
파일 위치: assets/examplemod/particles/sparkle.json
{
"textures": [
"examplemod:sparkle_0",
"examplemod:sparkle_1"
]
}textures 배열의 항목은 순서대로 프레임 애니메이션처럼 재생됩니다. 항목이 하나면 정적 파티클, 여러 개면 순환 애니메이션입니다.
각 텍스처 ID는 assets/examplemod/textures/particle/ 아래의 PNG 파일을 가리킵니다.
| JSON 항목 | 실제 파일 경로 |
|---|---|
"examplemod:sparkle_0" | assets/examplemod/textures/particle/sparkle_0.png |
"examplemod:sparkle_1" | assets/examplemod/textures/particle/sparkle_1.png |
텍스처 크기는 16×16 픽셀을 권장합니다. 더 크면 렌더링 비용이 올라가고, 더 작으면 흐릿하게 보입니다.
3단계 — 클라이언트 파티클 프로바이더 등록
파티클 JSON과 텍스처만으로는 부족합니다. 클라이언트가 파티클을 어떻게 렌더링할지 알려주는 Provider를 등록해야 합니다.
RegisterParticleProvidersEvent는 모드 이벤트 버스에서 발생하며, 클라이언트 전용입니다.
examplemod-template-26.1.2/src/main/java/com/example/examplemod/client/ClientSetup.java
@EventBusSubscriber(modid = "examplemod", value = Dist.CLIENT)
public class ClientSetup {
@SubscribeEvent
public static void onRegisterParticleProviders(RegisterParticleProvidersEvent event) {
event.registerSpriteSet(ExampleMod.SPARKLE.get(), MagicParticle.Provider::new);
}
}registerSpriteSet은 파티클 JSON에 정의된 텍스처 스프라이트 세트를 자동으로 로드해 Provider에 전달합니다. MagicParticle.Provider는 직접 구현하거나 바닐라의 SparkleParticle.Provider 같은 기존 Provider를 재사용할 수 있습니다.
value = Dist.CLIENT필수: 이 이벤트는 클라이언트에서만 발생합니다. 서버에서 이 클래스가 로드되면Minecraft클래스를 찾지 못해 크래시가 납니다.
4단계 — 파티클 스폰
파티클을 실제로 화면에 띄우는 방법은 두 가지입니다.
level.addParticle (클라이언트 전용)
// 클라이언트 사이드에서만 동작
level.addParticle(
ExampleMod.SPARKLE.get(),
x, y, z, // 스폰 위치
0, 0.1, 0 // 초기 속도 (vx, vy, vz)
);이 메서드는 현재 클라이언트의 월드에만 파티클을 추가합니다. 멀티플레이에서 다른 플레이어에게는 보이지 않습니다. 클라이언트 틱 이벤트나 클라이언트 전용 핸들러에서만 사용하세요.
serverLevel.sendParticles (서버 → 전체 전파, 권장)
// 서버에서 호출 → 범위 내 모든 클라이언트로 전파
if (level instanceof ServerLevel serverLevel) {
serverLevel.sendParticles(
ExampleMod.SPARKLE.get(),
x, y, z, // 스폰 위치
10, // count: 한 번에 스폰할 파티클 수
0.3, 0.3, 0.3, // dx, dy, dz: 스폰 범위 (반경)
0.1 // speed: 초기 속도 크기
);
}sendParticles의 매개변수 정리:
| 매개변수 | 타입 | 설명 |
|---|---|---|
particleData | ParticleOptions | 스폰할 파티클 타입 |
x, y, z | double | 스폰 중심 좌표 |
count | int | 한 번에 스폰할 개수 |
dx, dy, dz | double | 각 축 방향 스폰 범위 (반경) |
speed | double | 초기 속도 크기 |
📷 스크린샷 자리 (직접 캡처해 추가하세요)
sendParticles 호출 코드와 인게임 파티클 스폰 결과
실습 — 블록 우클릭 시 파티클 스폰
플레이어가 특정 블록을 우클릭하면 파티클이 터지는 예제입니다.
examplemod-template-26.1.2/src/main/java/com/example/examplemod/events/ParticleHandlers.java
@EventBusSubscriber(modid = "examplemod")
public class ParticleHandlers {
@SubscribeEvent
public static void onPlayerInteract(PlayerInteractEvent.RightClickBlock event) {
Level level = event.getLevel();
BlockPos pos = event.getPos();
// 서버 사이드에서만 처리
if (level instanceof ServerLevel serverLevel) {
double cx = pos.getX() + 0.5;
double cy = pos.getY() + 1.0;
double cz = pos.getZ() + 0.5;
serverLevel.sendParticles(
ExampleMod.SPARKLE.get(),
cx, cy, cz,
20, // 20개 스폰
0.5, 0.5, 0.5,
0.05
);
}
}
}블록 중심 좌표를 계산할 때 pos.getX() + 0.5처럼 0.5를 더하면 블록 정중앙에서 파티클이 나옵니다.
안티패턴
⚠️ 서버에서 addParticle 호출 → 효과 없음
// ❌ 서버에서 level.addParticle → 클라이언트 안 보임 @SubscribeEvent public static void onServerEvent(ServerTickEvent.Post event) { Level level = ...; level.addParticle(ExampleMod.SPARKLE.get(), x, y, z, 0, 0.1, 0); // 서버 사이드 Level에서 addParticle → 아무 일도 일어나지 않음 } // ✅ sendParticles 사용 (네트워크 전파) @SubscribeEvent public static void onServerEvent(ServerTickEvent.Post event) { if (level instanceof ServerLevel serverLevel) { serverLevel.sendParticles(ExampleMod.SPARKLE.get(), x, y, z, 10, 0.3, 0.3, 0.3, 0.1); } }
addParticle은 클라이언트 사이드Level에서만 처리됩니다. 서버Level에서 호출하면 내부적으로 무시됩니다.sendParticles는 서버에서 호출하면 해당 위치를 추적 중인 모든 클라이언트로 파티클 패킷을 자동 전파합니다.
파티클 디버깅
파티클이 보이지 않을 때 체크리스트:
- 파티클 JSON 경로 확인:
assets/<modid>/particles/<name>.json형식이 맞는지 확인합니다. - 텍스처 경로 확인: JSON의 텍스처 ID와 실제 PNG 파일 위치가 일치하는지 확인합니다.
- 게임 설정 확인: 옵션 → 비디오 설정 → 파티클이 "최소"로 설정돼 있으면 일부 파티클이 안 보입니다.
overrideLimiter = false로 등록하면 이 설정을 무시합니다. - Dist.CLIENT 확인:
RegisterParticleProvidersEvent핸들러가value = Dist.CLIENT로 등록됐는지 확인합니다. - 서버/클라이언트 구분: 서버 이벤트 핸들러에서
addParticle대신sendParticles를 사용하는지 확인합니다.
F3 디버그 화면에서 P 키를 누르면 파티클 히트박스가 표시됩니다. 파티클이 스폰됐지만 안 보이는 경우 위치 문제를 파악하는 데 도움이 됩니다.
정리
- 파티클 타입은
DeferredRegister<ParticleType<?>>+Registries.PARTICLE_TYPE으로 등록합니다. assets/<modid>/particles/<name>.json에 사용할 텍스처 목록을 정의합니다.- 텍스처는
assets/<modid>/textures/particle/<name>.png(16×16 권장)에 둡니다. RegisterParticleProvidersEvent로 클라이언트 렌더링 Provider를 등록합니다. 반드시Dist.CLIENT로 제한합니다.- 서버 이벤트에서는
sendParticles를 사용해 모든 클라이언트로 전파합니다.addParticle은 클라이언트 전용입니다.
다음 챕터에서는 커스텀 파티클 클래스를 직접 구현해 더 복잡한 움직임과 렌더링을 제어하는 방법을 다룹니다.