프로젝트 개발 동기 및 개요
어렸을 적부터 즐겨 하던 게임인 Minecraft에서 사설 서버에 참가도 하고 직접 열어도 보며 많은 추억이 있기에 Java언어에 대해 어느 정도 다룰 수 있게 된 최근 일반 사용자에게도 Spigot이라는 API가 오픈소스로 제공되고 있다는 소식을 들어 기존에 "있었으면 좋았겠다" 하는 편의기능이 담긴 GUI 메뉴를 서버사이드 플러그인으로 제작해보았습니다.
프로젝트 스크린샷
Spigot Bukkit / 개발환경
전 세계적으로 가장 많이 사용되고 있는 마인크래프트 서버 소프트웨어입니다. 이전 저작권 문제로 개발이 중단된 CraftBukkit 기반으로 제작되어
대부분의 플러그인의 의존성이 호환되지만 CraftBukkit의 저작권문제때문에 따로 빌드 툴과 소스를 다운받아 직접 빌드시켜 사용해야합니다.
호환성이 가장 높은 버킷이기에 개발은 Spigot 환경에서 개발하고 서버 구동은 아래의 Paper 버킷에서 구동하였습니다.
PaperMC Bukkit / 구동환경
Spigot에는 없는 독자 API가 일부 추가되었고 비동기 청크 로딩 등 다양한 최적화 패치가 적용된 버킷으로, Spigot과 플러그인 자체는 호환되지만 서버 구동 시 플레이 환경은 Paper 버킷이 조금 더 쾌적하기에 서버 구동은 Paper 버킷을 사용했습니다.
프로젝트 코드 일부
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @Override public boolean onCommand(CommandSender sender, Command command, String commandName, String[] subCommand) { if(commandName.equalsIgnoreCase("menu") || commandName.equalsIgnoreCase("mm") || commandName.equalsIgnoreCase("gui:menu") || commandName.equalsIgnoreCase("gui:mm")) { if(sender instanceof Player) { Player player = (Player)sender; GUIOpener.openMenu(player, "main"); } else { Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[GUI]" + ChatColor.RED + " 해당 명령어는 버킷에서 실행할 수 없습니다."); } } else if(commandName.equalsIgnoreCase("guiinfo") || commandName.equalsIgnoreCase("gui:guiinfo")) { sender.sendMessage("" + ChatColor.BLUE + ChatColor.BOLD + "----- [ GUI ] -----"); sender.sendMessage(ChatColor.BLUE + "Version: " + ChatColor.GREEN + "1.5.0"); sender.sendMessage(ChatColor.BLUE + "Description: " + ChatColor.GREEN + "각종 메뉴로 간편이동이 가능한 GUI 입니다."); sender.sendMessage(ChatColor.YELLOW + "- Made by SJ"); sender.sendMessage("" + ChatColor.BLUE + ChatColor.BOLD + "----------------------"); } else { sender.sendMessage(ChatColor.GREEN + "[GUI]" + ChatColor.RED + " 존재하지 않는 명령어입니다."); Bukkit.broadcastMessage(commandName); } return false; } | cs |
/menu, /mm 입력 시 openMenu 함수를 통해 입력자에게 GUI메뉴 출력, /guiinfo 입력 시 플러그인 정보가 표시되도록 명령어 처리
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public static void mainMenu(Player player) {
String permission = new LuckPermsAPI().getGroupName(player);
Inventory inv = GUIBuilder.createGUI("GUI - MainMenu", 18);
inv.setItem(0, GUIBuilder.itemBuild(Material.LIME_DYE, ChatColor.GREEN +
"§l편의기능 메뉴", "스폰, 셋홈 등 여러 편의기능을 제공합니다."));
inv.setItem(1, GUIBuilder.itemBuild(Material.PINK_DYE, ChatColor.GREEN +
"§l파티 시스템 메뉴", "파티 시스템에 대한 설정을 할 수 있습니다."));
inv.setItem(2, GUIBuilder.itemBuild(Material.GRAY_DYE, ChatColor.GREEN +
"§lMCMMO 시스템 메뉴", "기술확장(MCMMO)에 대한 조회가 가능합니다."));
inv.setItem(3, GUIBuilder.itemBuild(Material.PURPLE_DYE, ChatColor.GREEN +
"§l상점 시스템 메뉴", "각종 상점으로 이동합니다."));
inv.setItem(6, GUIBuilder.itemBuild(Material.ENDER_EYE, ChatColor.YELLOW +
"§l로그 조회 ON/OFF", "코어 프로텍트 로그 조회\n기능을 토글합니다."));
inv.setItem(7, GUIBuilder.itemBuild(Material.HORN_CORAL, ChatColor.YELLOW +
"§l한글 자동변환 ON", "한글 자동 변환 기능을 켭니다."));
inv.setItem(8, GUIBuilder.itemBuild(Material.DEAD_HORN_CORAL, ChatColor.YELLOW +
"§l한글 자동변환 OFF", "한글 자동 변환 기능을 끕니다."));
inv.setItem(9, GUIBuilder.skullBuild("§l" + ChatColor.LIGHT_PURPLE + player.getName() +
" §l님 환영합니다!", ChatColor.WHITE + "등급 : " + ChatColor.GREEN + permission, player.getName()));
inv.setItem(17, GUIBuilder.itemBuild(Material.BARRIER, ChatColor.RED + "§l닫기"));
playEffectSound(player);
player.openInventory(inv);
}
|
cs |
메인 GUI 접근 시 팝업되는 메뉴 설정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public static ItemStack itemBuild(Material material, String title, String loreString) { ItemStack item = new ItemStack(material); ItemMeta meta = item.getItemMeta(); meta.addItemFlags(ItemFlag.HIDE_POTION_EFFECTS); meta.setDisplayName(title); List<String> lore = new ArrayList<String>(); String loreLines[] = loreString.split("\n"); for(String temp : loreLines) lore.add(ChatColor.RESET + temp); meta.setLore(lore); item.setItemMeta(meta); return item; } | cs |
GUI 메뉴에 표시해줄 아이템의 아이콘, 이름, 설명을 파라미터로 넘기면 해당 정보로 ItemStack 제작 후 리턴
1 2 3 4 5 6 7 8 9 10 11 | public class EssentialAPI { Essentials ess = (Essentials)Bukkit.getPluginManager().getPlugin("Essentials"); public List<String> getHomeList(Player player) { List<String> homeList = new ArrayList<String>(); homeList = ess.getUser(player.getPlayer().getName()).getHomes(); return homeList; } } | cs |
GUI플러그인 실행에 필요한 타 플러그인(Essentials)과 해당 플러그인의 정보(home등)를 현재 구동중인 Bukkit서버의 인스턴스에서 찾아서 가져옴
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@EventHandler
public void useCoupon(PlayerInteractEvent event) throws NumberFormatException, ParseException {
if(event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) {
ItemStack item = event.getPlayer().getInventory().getItemInMainHand();
if(item.getType() == Material.BOOK) {
ItemMeta meta = item.getItemMeta();
if(meta.getDisplayName().contains("플라이 쿠폰")) {
if(meta.getEnchantLevel(Enchantment.LUCK) == 10)
fly_cm.useFlyCoupon(event.getPlayer(),
Integer.parseInt(new StringUtils().getNumberStr(meta.getDisplayName().substring(4))));
}
else if(meta.getDisplayName().contains("TP 쿠폰")) {
if(meta.getEnchantLevel(Enchantment.LUCK) == 10)
tp_cm.useTPCoupon(event.getPlayer());
}
event.setCancelled(true);
}
}
}
|
cs |
[번외] GUI 외 쿠폰으로 제작된 아이템을 들고 우클릭 이벤트 발생시 쿠폰이 종류에 따라 사용되도록 처리 후 우클릭 이벤트 취소
프로젝트 후기
즐겨 하던 게임인 만큼 정말 재미있게 개발하였습니다. 점점 욕심 나는 기능이나 아이디어를 넣다 보니 코드가 계속 길어져 모듈, 캡슐화하는 과정에서 다시 한번 객체 지향의 편리성을 느꼈습니다. 또한, 국내에는 MC 플러그인 개발에 대한 정보가 거의 없다시피 매우 많이 부족하여 해외 포럼을 돌아다니며 새로운 분야의 정보를 수집하고 환경에 맞게 가져다 사용하는 요령 또한 한 단계 더욱 익숙해졌습니다. 새로운 API를 테스트, 실험해보며 사용하는 느낌은 언제나 새로워서 신선한 느낌을 줍니다.
다른 기발한 아이디어가 있으신가요?
신입 개발자로써 많은 것을 배우고 제작해보고싶습니다. 현재 프로젝트에 추가되었으면 하는 기능이나, 완전히 새 프로젝트에 대한 아이디어가 있으시면 저에게 연락주시면 감사하겠습니다.