틱 이벤트 — 매 tick 실행되는 로직 작성
ServerTickEvent, ClientTickEvent, EntityTickEvent, LevelTickEvent의 종류와 사용법을 배우고, 카운터 패턴으로 TPS를 지키는 성능 최적화 기법을 익힙니다.
틱이란?
Minecraft의 게임 루프는 초당 20번 실행됩니다. 이 한 번의 실행 단위를 **틱(tick)**이라고 부릅니다.
| 단위 | 값 |
|---|---|
| 1 tick | 0.05초 = 50ms |
| 20 tick | 1초 |
| 1200 tick | 1분 |
서버가 정상 상태라면 TPS(Ticks Per Second)는 20을 유지합니다. 틱 이벤트 핸들러에서 무거운 연산을 실행하면 TPS가 떨어지고, 게임이 느려집니다.
틱 이벤트 종류
NeoForge 26은 네 가지 틱 이벤트를 제공합니다.
ServerTickEvent
서버 게임 루프의 매 tick마다 발생합니다. Pre는 tick 처리 전, Post는 tick 처리 후에 호출됩니다.
@SubscribeEvent
public static void onServerTickPre(ServerTickEvent.Pre event) {
// tick 처리 시작 전
}
@SubscribeEvent
public static void onServerTickPost(ServerTickEvent.Post event) {
// tick 처리 완료 후
MinecraftServer server = event.getServer();
}ServerTickEvent.Post에서 event.getServer()로 MinecraftServer 인스턴스에 접근할 수 있습니다.
ClientTickEvent
클라이언트 게임 루프의 매 tick마다 발생합니다. 클라이언트 전용 로직(HUD 업데이트, 파티클 등)에 사용합니다.
@SubscribeEvent
public static void onClientTick(ClientTickEvent.Post event) {
// 클라이언트 tick 완료 후
Minecraft mc = Minecraft.getInstance();
}주의:
ClientTickEvent는 클라이언트 전용입니다.@EventBusSubscriber(value = Dist.CLIENT)로 등록해야 합니다.
EntityTickEvent
엔티티별로 매 tick마다 발생합니다. 특정 엔티티 타입에만 로직을 적용할 때 유용합니다.
@SubscribeEvent
public static void onEntityTick(EntityTickEvent.Post event) {
Entity entity = event.getEntity();
if (entity instanceof Zombie zombie) {
// 좀비 tick마다 실행
}
}LevelTickEvent
월드(Level) 단위로 매 tick마다 발생합니다. 특정 차원의 로직을 처리할 때 사용합니다.
@SubscribeEvent
public static void onLevelTick(LevelTickEvent.Post event) {
Level level = event.getLevel();
if (level.dimension() == Level.NETHER) {
// 네더 월드 tick마다 실행
}
}실습 — 5초마다 메시지 전송
카운터를 사용해 5초(100 tick)마다 모든 플레이어에게 메시지를 보내는 핸들러를 작성합니다.
examplemod-template-26.1.2/src/main/java/com/example/examplemod/events/TickHandlers.java
package com.example.examplemod.events;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.event.tick.ServerTickEvent;
@EventBusSubscriber(modid = "examplemod")
public class TickHandlers {
private static int counter = 0;
@SubscribeEvent
public static void onServerTick(ServerTickEvent.Post event) {
counter++;
if (counter >= 100) { // 5초 = 100 tick
MinecraftServer server = event.getServer();
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
player.sendSystemMessage(
Component.literal("5초마다 메시지")
);
}
counter = 0;
}
}
}핵심 포인트:
counter는static필드로 선언합니다. 인스턴스가 매 tick 생성되지 않도록 클래스 수준에서 상태를 유지합니다.counter >= 100조건이 참이면 로직을 실행하고 즉시counter = 0으로 초기화합니다.server.getPlayerList().getPlayers()는 현재 접속 중인 모든 플레이어 목록을 반환합니다.
카운터 패턴 심화
5초 간격 외에도 다양한 주기를 카운터로 표현할 수 있습니다.
@SubscribeEvent
public static void onServerTick(ServerTickEvent.Post event) {
counter++;
// 1초마다 (20 tick)
if (counter % 20 == 0) {
doEverySecond();
}
// 5초마다 (100 tick)
if (counter % 100 == 0) {
doEvery5Seconds();
}
// 1분마다 (1200 tick)
if (counter % 1200 == 0) {
doEveryMinute();
counter = 0; // 오버플로 방지
}
}% 연산자를 사용하면 단일 카운터로 여러 주기를 동시에 처리할 수 있습니다. 단, counter가 Integer.MAX_VALUE를 넘지 않도록 적절한 시점에 초기화해야 합니다.
성능 고려사항
틱 이벤트는 초당 20번 호출됩니다. 핸들러 하나가 1ms만 지연돼도 TPS에 영향을 줄 수 있습니다.
TPS 확인 방법: 게임 내에서 F3 키를 누르면 디버그 화면이 열립니다. 우측 상단에 TPS 수치가 표시됩니다. 20 이하로 떨어지면 성능 문제가 있다는 신호입니다.
비용이 큰 연산 목록:
| 연산 | 비용 | 대안 |
|---|---|---|
| 파일 I/O | 매우 높음 | 비동기 처리 또는 주기 늘리기 |
| 네트워크 요청 | 매우 높음 | 별도 스레드 |
| 복잡한 경로 탐색 | 높음 | 캐시 또는 주기 늘리기 |
| 대규모 컬렉션 순회 | 중간 | 청크 단위 분할 처리 |
| 새 객체 생성 | 낮음~중간 | 객체 재사용 (풀링) |
안티패턴
⚠️ 매 tick I/O → TPS 감소
// ❌ 매 tick 파일 쓰기 → 디스크 I/O 병목 @SubscribeEvent public static void onServerTick(ServerTickEvent.Post event) { Files.writeString(Path.of("data.txt"), "tick"); // 초당 20번 쓰기 } // ❌ 매 tick 새 List 생성 → GC 압박 @SubscribeEvent public static void onServerTick(ServerTickEvent.Post event) { List<Player> players = new ArrayList<>(server.getPlayerList().getPlayers()); // 매 tick } // ✅ 카운터 + 캐시 @SubscribeEvent public static void onServerTick(ServerTickEvent.Post event) { if (counter++ % 100 == 0) { // 5초마다 한 번 // 필요한 로직만 실행 } }F3 화면에서 TPS를 확인하세요. 20 이하로 떨어지면 틱 핸들러를 점검해야 합니다.
정리
- 틱 이벤트는 초당 20번 호출됩니다. 1 tick = 50ms.
ServerTickEvent.Post에서event.getServer()로 서버 인스턴스에 접근합니다.- 카운터 패턴(
counter % N == 0)으로 실행 빈도를 조절합니다. - 매 tick 파일 I/O, 네트워크 요청, 대규모 객체 생성은 TPS를 떨어뜨립니다.
- F3 디버그 화면으로 TPS를 실시간 확인할 수 있습니다.
다음 챕터에서는 플레이어 이벤트를 다룹니다. 로그인, 로그아웃, 아이템 사용 등 플레이어 행동에 반응하는 핸들러를 작성합니다.