NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
이벤트 핸들러와 @SubscribeEventModBus vs NeoForge EventBus — 두 버스 완전 정복플레이어 이벤트틱 이벤트 — 매 tick 실행되는 로직 작성커맨드 등록 — Brigadier로 /명령어 만들기사운드 등록과 재생파티클 등록과 스폰 — 시각 효과 직접 만들기
이벤트

파티클 등록과 스폰 — 시각 효과 직접 만들기

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의 매개변수 정리:

매개변수타입설명
particleDataParticleOptions스폰할 파티클 타입
x, y, zdouble스폰 중심 좌표
countint한 번에 스폰할 개수
dx, dy, dzdouble각 축 방향 스폰 범위 (반경)
speeddouble초기 속도 크기
ℹ️

📷 스크린샷 자리 (직접 캡처해 추가하세요)

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는 서버에서 호출하면 해당 위치를 추적 중인 모든 클라이언트로 파티클 패킷을 자동 전파합니다.


파티클 디버깅

파티클이 보이지 않을 때 체크리스트:

  1. 파티클 JSON 경로 확인: assets/<modid>/particles/<name>.json 형식이 맞는지 확인합니다.
  2. 텍스처 경로 확인: JSON의 텍스처 ID와 실제 PNG 파일 위치가 일치하는지 확인합니다.
  3. 게임 설정 확인: 옵션 → 비디오 설정 → 파티클이 "최소"로 설정돼 있으면 일부 파티클이 안 보입니다. overrideLimiter = false로 등록하면 이 설정을 무시합니다.
  4. Dist.CLIENT 확인: RegisterParticleProvidersEvent 핸들러가 value = Dist.CLIENT로 등록됐는지 확인합니다.
  5. 서버/클라이언트 구분: 서버 이벤트 핸들러에서 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은 클라이언트 전용입니다.

다음 챕터에서는 커스텀 파티클 클래스를 직접 구현해 더 복잡한 움직임과 렌더링을 제어하는 방법을 다룹니다.

사운드 등록과 재생

NeoForge 26에서 커스텀 사운드를 DeferredRegister로 등록하고, sounds.json과 OGG 파일을 구성한 뒤 양 사이드에서 올바르게 재생하는 방법을 설명합니다.

BlockEntity 기초 — CounterBlockEntity

DeferredRegister로 BlockEntityType을 등록하고, BlockEntity 클래스에서 NBT 직렬화로 데이터를 저장·복원하는 전체 과정을 학습합니다.

On this page

파티클이란?1단계 — PARTICLE_TYPES DeferredRegister2단계 — 파티클 JSON 정의3단계 — 클라이언트 파티클 프로바이더 등록4단계 — 파티클 스폰level.addParticle (클라이언트 전용)serverLevel.sendParticles (서버 → 전체 전파, 권장)실습 — 블록 우클릭 시 파티클 스폰안티패턴파티클 디버깅정리
NeoForge 26.1 Docs

NeoForge 26.1 모딩 개발 문서 사이트

GitHubDiscord

문서

  • 문서
  • 노트

GitHub

  • GitHub
  • Discord

© 2026 NeoForge 26.1 Docs. 콘텐츠는 MIT 라이선스로 제공됩니다.

Built with Next.js · Tailwind CSS · shadcn/ui