2026. 2. 25. 21:44ㆍJava/마인크래프트
안녕하세요.
1편에서 개발환경 세팅과 첫 플러그인 로딩,
2편에서 명령어 시스템(/heal, 권한, 인자 파싱, 탭완성),
3편에서 이벤트 처리 실전을 다뤘다면,
이번 4편에서는 실전 서버 운영에서 정말 자주 쓰는 기능을 만듭니다.
바로 config.yml을 이용한:
- 기능 on/off
- 메시지 커스터마이징
- 운영 중 설정 변경 반영(리로드)
입니다.
코드를 하드코딩으로만 만들면, 나중에 서버 운영할 때 작은 문구 하나 바꾸려고도 재빌드/재배포를 해야 합니다.
이번 편에서 그 불편함을 줄여보겠습니다.
이번 편 목표
예제로 지난 편의 /heal 명령어를 확장합니다.
config.yml에서/heal기능 자체를 켜고/끄기- 회복량(하트/체력) 설정값으로 관리
- 안내 메시지(성공/권한 없음/비활성화) 커스터마이징
/healreload명령어로 설정 다시 읽기(관리자용)
즉, 운영자가 코드 수정 없이 서버 설정만 바꿔도 동작이 달라지게 만드는 것이 목표입니다.
먼저 알아둘 용어 (초보자 기준)
config.yml: 플러그인 설정 파일(서버 운영자가 수정)saveDefaultConfig(): 기본config.yml이 없을 때 플러그인 jar 안의 파일을 꺼내 저장getConfig(): 현재 설정값을 읽는 메서드reloadConfig(): 디스크의config.yml을 다시 읽어서 반영permission node: 권한 문자열 이름 (예:myplugin.heal,myplugin.admin)path: 설정 키 경로 (예:heal.enabled,messages.no-permission)
1) plugin.yml에 명령어와 권한 등록
한 줄 요약: /heal, /healreload를 서버에 알리고 권한 노드도 문서화합니다.
name: my-first-plugin
version: 1.0.0
main: com.example.myplugin.MyPlugin
api-version: '1.21'
commands:
heal:
description: Heal yourself or target player
usage: /heal [player]
permission: myplugin.heal
healreload:
description: Reload plugin config
usage: /healreload
permission: myplugin.admin
permissions:
myplugin.heal:
description: Use /heal command
default: op
myplugin.admin:
description: Reload plugin config
default: op
permission:을 써두면 서버 운영자가 명령어 용도를 파악하기 쉽고,
권한 플러그인(LuckPerms 등)과 함께 운영할 때도 관리가 편합니다.
2) 기본 config.yml 만들기
한 줄 요약: 기능 플래그, 수치, 메시지를 분리해두면 운영 중 수정이 쉬워집니다.
src/main/resources/config.yml
heal:
enabled: true
amount: 20.0
allow-target: true
messages:
prefix: "&8[&aHeal&8] &r"
no-permission: "&c권한이 없습니다."
heal-disabled: "&e현재 /heal 기능이 비활성화되어 있습니다."
player-only: "&e플레이어만 사용할 수 있습니다."
player-not-found: "&c대상 플레이어를 찾을 수 없습니다."
healed-self: "&a체력을 회복했습니다."
healed-target: "&a{player}의 체력을 회복했습니다."
healed-by-other: "&a관리자에 의해 체력이 회복되었습니다."
config-reloaded: "&aconfig.yml을 다시 불러왔습니다."
포인트는 3개입니다.
heal.enabled: 기능 on/offheal.amount: 회복량 숫자 설정messages.*: 운영자가 문구를 바꿀 수 있게 분리
나중에 다국어 대응이나 서버별 톤 조정할 때도 이 구조가 유리합니다.
3) 메인 클래스에서 기본 config 저장 + 명령어 등록
한 줄 요약: 플러그인 시작 시 기본 설정 파일을 만들어두고, 명령어 실행기를 연결합니다.
package com.example.myplugin;
import org.bukkit.plugin.java.JavaPlugin;
public final class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
saveDefaultConfig();
HealCommand healCommand = new HealCommand(this);
getCommand("heal").setExecutor(healCommand);
getCommand("healreload").setExecutor(healCommand);
getLogger().info("MyPlugin enabled.");
}
}
왜 saveDefaultConfig()가 중요한가?
이 줄이 없으면 서버 plugins/내플러그인/ 폴더에 config.yml이 자동 생성되지 않습니다.
그러면 getConfig()는 동작하더라도 운영자가 수정할 파일이 처음부터 없어서 불편합니다.
왜 setExecutor(...)를 onEnable에서 하나?
플러그인이 켜질 때 명령어와 실행 클래스를 연결해야 서버가 /heal 입력을 해당 코드로 보낼 수 있습니다.
이 줄이 빠지면 명령어가 등록되어 있어도 실제 로직이 실행되지 않습니다.
4) 설정값/메시지를 읽는 /heal 명령어 만들기
한 줄 요약: config 값을 직접 하드코딩 대신 읽어서 기능 on/off와 메시지를 제어합니다.
package com.example.myplugin;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.attribute.Attribute;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
public final class HealCommand implements CommandExecutor {
private final MyPlugin plugin;
public HealCommand(MyPlugin plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
String name = command.getName().toLowerCase();
if (name.equals("healreload")) {
return handleReload(sender);
}
return handleHeal(sender, args);
}
private boolean handleReload(CommandSender sender) {
if (!sender.hasPermission("myplugin.admin")) {
sender.sendMessage(msg("no-permission"));
return true;
}
plugin.reloadConfig();
sender.sendMessage(msg("config-reloaded"));
return true;
}
private boolean handleHeal(CommandSender sender, String[] args) {
if (!sender.hasPermission("myplugin.heal")) {
sender.sendMessage(msg("no-permission"));
return true;
}
FileConfiguration config = plugin.getConfig();
if (!config.getBoolean("heal.enabled", true)) {
sender.sendMessage(msg("heal-disabled"));
return true;
}
if (args.length == 0) {
if (!(sender instanceof Player player)) {
sender.sendMessage(msg("player-only"));
return true;
}
healPlayer(player, config.getDouble("heal.amount", 20.0));
player.sendMessage(msg("healed-self"));
return true;
}
if (!config.getBoolean("heal.allow-target", true)) {
sender.sendMessage(color("&e대상 지정 heal 기능이 비활성화되어 있습니다."));
return true;
}
Player target = Bukkit.getPlayerExact(args[0]);
if (target == null) {
sender.sendMessage(msg("player-not-found"));
return true;
}
healPlayer(target, config.getDouble("heal.amount", 20.0));
sender.sendMessage(msg("healed-target").replace("{player}", target.getName()));
target.sendMessage(msg("healed-by-other"));
return true;
}
private void healPlayer(Player player, double amount) {
double maxHealth = player.getAttribute(Attribute.MAX_HEALTH).getValue();
double newHealth = Math.min(maxHealth, amount);
player.setHealth(newHealth);
player.setFoodLevel(20);
player.setFireTicks(0);
}
private String msg(String path) {
String prefix = plugin.getConfig().getString("messages.prefix", "");
String body = plugin.getConfig().getString("messages." + path, path);
return color(prefix + body);
}
private String color(String text) {
return ChatColor.translateAlternateColorCodes('&', text);
}
}
코드 설명 (초보자용)
plugin.getConfig():- 현재 메모리에 로드된 설정을 읽습니다.
- 매번 파일을 직접 읽는 게 아니라 Paper/Bukkit이 관리하는 설정 객체를 사용합니다.
getBoolean("heal.enabled", true):heal.enabled값이 있으면 그 값을 사용- 없으면 기본값
true사용 - 설정 누락에도 플러그인이 바로 죽지 않게 만드는 안전장치입니다.
args.length == 0:/heal만 입력한 경우(자기 자신 회복)/heal 닉네임과 분기하기 위한 핵심 조건입니다.
sender instanceof Player:- 콘솔은
Player가 아니므로 자기 자신 회복 로직을 바로 실행하면 안 됩니다. - 콘솔에서
/heal만 입력하면 대상이 없으므로 안내 메시지를 보내는 게 맞습니다.
- 콘솔은
plugin.reloadConfig():- 디스크의
config.yml을 다시 읽습니다. - 서버 재시작 없이 설정 반영이 가능해서 운영 편의성이 크게 올라갑니다.
- 디스크의
5) 왜 이 줄들이 중요한가? (실전 포인트)
hasPermission(...)를 왜 명령어 초반에 검사하나?
권한 없는 사용자가 뒤 로직(대상 탐색, 설정값 처리)까지 들어가지 않게 막기 위해서입니다.
실전에서는 "빨리 차단"이 로그/디버깅도 깔끔합니다.
args.length 분기를 왜 먼저 하냐?
args[0] 접근 전에 길이 검사를 하지 않으면 /heal 입력 시 예외가 납니다.
명령어 코드는 항상 인자 개수 검증이 먼저입니다.
getBoolean(..., true)처럼 기본값을 왜 넣나?
운영자가 config를 잘못 수정하거나 일부 키를 지웠을 때도 플러그인이 최대한 계속 동작하도록 만들기 위해서입니다.
실전 서버에서는 "설정 오타로 플러그인 전체가 죽는 상황"을 피하는 게 중요합니다.
reloadConfig()만 하고 끝내도 되나?
이번 예제처럼 getConfig()를 매번 읽는 구조라면 대부분 충분합니다.
반대로 설정값을 필드에 캐싱해두는 구조라면 reload 후 캐시 재로딩 코드가 추가로 필요합니다.
6) 이벤트 처리에도 같은 방식으로 config 적용 가능
한 줄 요약: 3편에서 만든 이벤트 리스너에도 config.yml 플래그를 붙이면 운영 제어가 쉬워집니다.
예를 들어 "특정 아이템 우클릭 효과"를 만들었다면:
if (!plugin.getConfig().getBoolean("features.magic-stick-enabled", true)) {
return;
}
이렇게 한 줄만 추가해도 운영자는 config에서 기능을 켜고 끌 수 있습니다.
즉, config.yml은 명령어 전용이 아니라
명령어 + 이벤트 + 스케줄러 전체에 공통으로 쓰는 운영 제어판이라고 보면 됩니다.
자주 나오는 실수 (실패 케이스)
1) saveDefaultConfig()를 안 넣음
증상:
plugins/플러그인명/config.yml파일이 안 생김- 운영자가 수정할 설정 파일이 없음
원인:
- onEnable에서 기본 config 저장 누락
2) plugin.yml에 명령어 등록 안 함
증상:
/heal입력 시 Unknown command
원인:
- 코드에서
setExecutor(...)만 하고plugin.yml등록 누락
3) 메시지 경로 오타
증상:
- 메시지 대신
messages.no-permision같은 키 문자열이 그대로 출력됨
원인:
messages.no-permission경로 오타- 설정 키 이름과 코드 경로가 다름
4) reloadConfig()는 했는데 값이 안 바뀌는 것처럼 보임
증상:
/healreload성공 메시지는 뜨는데 동작이 이전 설정처럼 보임
원인:
- 설정값을 클래스 필드에 미리 저장(캐싱)해둔 뒤 다시 읽지 않음
수동 테스트 체크리스트 (실전 운영 기준)
- 서버 시작 후
plugins/플러그인명/config.yml이 생성되는지 확인 heal.enabled: true상태에서/heal이 정상 동작하는지 확인heal.enabled: false로 바꾸고/healreload후/heal이 차단되는지 확인heal.amount를20.0->10.0으로 바꾸고/healreload후 체력 회복량이 달라지는지 확인messages.healed-self문구를 바꾸고/healreload후 변경 메시지가 출력되는지 확인- 권한 없는 계정에서
/heal,/healreload시no-permission이 출력되는지 확인 - 콘솔에서
/heal입력 시player-only가 출력되는지 확인 /heal 없는닉네임입력 시player-not-found가 출력되는지 확인
이번 편 정리
이번 편에서는 config.yml을 이용해 플러그인을 "코드 중심"에서 "운영 가능한 형태"로 한 단계 올렸습니다.
핵심은 이 4가지입니다.
saveDefaultConfig()로 기본 설정 파일 제공하기getConfig()로 기능 on/off와 수치 설정 읽기messages.*경로로 문구 하드코딩 줄이기reloadConfig()로 서버 재시작 없이 운영 편의성 확보하기
이 패턴을 익혀두면 다음 편의 데이터 저장(YAML/SQLite)에서도
설정 경로, 기본값, 운영자 친화적인 구조를 훨씬 쉽게 설계할 수 있습니다.
다음 편에서는 YAML을 이용해 유저별 설정을 저장하는 방법을 실전으로 다뤄보겠습니다.
'Java > 마인크래프트' 카테고리의 다른 글
| [Paper 플러그인 실전 제작기 #06] 데이터 저장 2: SQLite로 랭킹/통계 관리 (0) | 2026.03.02 |
|---|---|
| [Paper 플러그인 실전 제작기 #05] 데이터 저장 1: YAML로 유저별 설정 저장 (0) | 2026.02.26 |
| [Paper 플러그인 실전 제작기 #03] 이벤트 리스너 실전 (스폰 보호구역에서 블록 설치/파괴 막기) (0) | 2026.02.23 |
| [Paper 플러그인 실전 제작기 #02] 명령어 시스템 실전 (/heal 권한, 인자 파싱, 탭 완성) (0) | 2026.02.21 |
| [Paper 플러그인 실전 제작기 #01] Java 21 + Maven으로 첫 플러그인 만들기 (0) | 2026.02.20 |