시작하기에 앞서
아직 개발 환경을 설정하지 못했다면 이전 포스트를 참고해 주세요.
이번 시간에는 다음 항목을 학습하는 것을 목표로 합니다.
- 전체 메세지와 플레이어 메시지를 전송하는 방법
- 커맨드를 제작하고 등록하는 방법
- 이벤트 리스너를 제작하고 등록하는 방법
프로젝트 목표
플레이어가 화살을 쏘아 상대 플레이어를 맞추거나 처치했을 때 스코어가 오르는 게임을 제작합니다.
해당 게임을 구현하기 위해 다음과 같은 항목을 필요로 합니다.
- 게임 시작/종료 기능
- 플레이어의 스코어 정보를 저장하는 기능
- 조건 만족 시 플레이어의 스코어를 변경하는 기능
결과물은 다음과 같습니다.
전체 코드는 깃허브에서 볼 수 있습니다.
1. 게임 관리자 제작
먼저, 게임의 진행을 관리할 수 있는 관리자 클래스를 제작합니다.
관리자 클래스에서는 게임의 진행 상태 및 플레이어의 스코어 정보를 관리합니다.
게임 관리자는 단 하나의 인스턴스만 생성되기를 바라기 때문에 싱글톤 클래스로 제작합니다.
public class GameController {
// 인스턴스를 단 하나만 생성하기 위해 Holder클래스를 제작합니다.
private static class GameControllerHolder {
private static final GameController INSTANCE = new GameController();
}
// Holder클래스에서 생성된 인스턴스를 반환합니다.
public static GameController getInstance() {
return GameControllerHolder.INSTANCE;
}
// 게임의 진행 상태를 저장합니다.
private boolean isPlaying = false;
// 플레이어의 스코어 정보를 저장합니다.
private Map<String, Integer> scores = Maps.newHashMap();
// 직접적으로 객체를 생성할 수 없도록 private으로 처리합니다.
private GameController() {}
// 게임을 시작합니다.
public void startGame() {
if (!isPlaying) { // 게임이 실행중이지 않을 때
isPlaying = true; // 게임을 시작 상태로 변경합니다.
scores = Maps.newHashMap(); // 플레이어의 스코어 정보를 초기화 합니다.
}
}
// 게임을 종료합니다.
public void stopGame() {
if (isPlaying) { // 게임이 실행중일 때
isPlaying = false; // 게임을 종료 상태로 변경합니다.
}
}
// 플레이어의 스코어를 설정합니다.
public void setScore(@NotNull Player player, int score) {
if (isPlaying) { // 게임이 실행중일 때
// 플레이어의 UUID를 가져옵니다.
final String uuid = player.getUniqueId().toString();
// 플레이어의 스코어를 변경합니다.
scores.put(uuid, score);
}
}
// 플레이어의 스코어 정보를 반환합니다.
public int getScore(@NotNull Player player) {
// 플레이어의 UUID를 가져옵니다.
final String uuid = player.getUniqueId().toString();
// 플레이어의 스코어 정보를 조회하고
// 스코어가 존재한다면 가져온 스코어를, 그렇지 않다면 0을 반환합니다.
return scores.getOrDefault(uuid, 0);
}
// 모든 플레이어의 스코어를 반환합니다.
public Map<String, Integer> getScores() {
return Maps.newHashMap(scores);
}
// 게임이 진행중이면 true, 그렇지 않다면 false를 반환합니다.
public boolean isPlaying() {
return isPlaying;
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
2. 게임 커맨드 제작
다음으로 게임을 시작하고 종료할 수 있는 커맨드를 제작합니다.
커맨드의 자동 완성 기능을 제작합니다.
CommandExecutor 인터페이스를 구현하여 커맨드 입력 시 작동할 기능을 작성합니다.
TabCompleter 인터페이스를 구현하여 커맨드 자동 완성 기능을 구현합니다.
먼저, 커맨드 클래스를 제작합니다.
// CommandExecutor: onCommand() 메소드를 구현하여 커맨드의 작동 방식을 구현합니다.
// TabCompleter: onTabComplete() 메소드를 구현하여 커맨드의 자동 완성을 구현합니다.
public class GameCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(
@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args
) {
// 입력한 커맨드 인자의 길이가 0이라면 종료합니다.
// /shoot <- 인자의 길이가 0인 커맨드 입니다.
// /shoot start <- 인자의 길이가 1인 커맨드 입니다.
if (args.length == 0) {
return false;
}
// 가장 처음으로 입력받은 인자가 무엇인지 확인합니다.
switch (args[0]) {
// /shoot start 커맨드를 입력하였을 때 실행됩니다.
case "start" -> {
// GameController 객체를 가져옵니다.
GameController.getInstance().startGame();
// Bukkit.broadcast() 메소드를 사용하여 모든 유저에게 메시지를 전송합니다.
// 또한, Paper 에서는 문자열 처리를 String 대신 Component를 사용하는것을 권장합니다.
Bukkit.broadcast(Component.text("Start Game."));
}
// /shoot stop 커맨드를 입력하였을 때 실행됩니다.
case "stop" -> {
// GameController 객체를 가져옵니다.
GameController.getInstance().stopGame();
// 모든 유저에게 메세지를 전송합니다.
Bukkit.broadcast(Component.text("Stop Game."));
}
}
return false;
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args
) {
// 입력한 커맨드 인자의 길이가 1이 아니라면 종료합니다.
if (args.length != 1) {
return null;
}
// 첫번째 인자의 자동완성을 "start" 와 "stop" 으로 설정합니다.
return Lists.newArrayList("start", "stop");
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
다음으로, plugin.yml 파일에 커맨드 정보를 입력합니다.
name: ShootingGame
version: '${version}'
main: com.molruexception.shootinggame.ShootingGame
api-version: 1.19
# 등록할 커맨드 목록
commands:
# 등록할 커맨드 이름 /shoot <arg 1>
shoot:
# 커맨드를 사용할 때 필요한 퍼미션
permission: game.command.shoot
관련 코드는 깃허브에서도 확인할 수 있습니다.
마지막으로, 제작한 커맨드를 메인 클래스의 onEnable() 메소드에서 등록합니다.
public final class ShootingGame extends JavaPlugin {
@Override
public void onEnable() {
// plugin.yml에 작성한 커맨드를 불러옵니다.
final PluginCommand shootCommand = getCommand("shoot");
// 커맨드를 정상적으로 불러왔다면
if (shootCommand != null) {
// 커맨드 객체를 생성합니다.
GameCommand command = new GameCommand();
// 불러온 커맨드의 실행 객체를 위에서 생성한 커맨드 객체로 설정합니다.
shootCommand.setExecutor(command);
// 불러온 커맨드의 자동완성 객체를 위에서 생성한 커맨드 객체로 설정합니다.
shootCommand.setTabCompleter(command);
}
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
3. 스코어 리스너 제작
먼저, 리스너 클래스를 제작합니다.
리스너 클래스는 Listener 인터페이스를 구현하여 이벤트 리스너를 추가합니다.
// Listener 인터페이스를 구현하여 이벤트 리스너를 추가합니다.
public class GameListener implements Listener {
// 화살 적중시 스코어를 5점으로 설정합니다.
private static final int ARROW_HIT_SCORE = 5;
// 적 처치시 스코어를 20점으로 설정합니다.
private static final int PLAYER_KILL_SCORE = 20;
// EventHandler 어노테이션을 부착하여 이벤트를 수신합니다.
@EventHandler
public void onArrowHit(ProjectileHitEvent event) { // ProjecttileHitEvent에서 발사체 피격을 감지합니다.
// 게임이 실행중인지 확인하고 실행중이지 않다면 종료합니다.
final GameController controller = GameController.getInstance();
if (!controller.isPlaying()) {
return;
}
// 투사체를 피격한 엔티티가 플레이어가 아니라면 종료합니다.
if (!(event.getHitEntity() instanceof Player)) {
return;
}
// 투사체가 화살이 아니라면 종료합니다.
if (!(event.getEntity() instanceof Arrow arrow)) {
return;
}
// 화살을 날린 주체가 플레이어가 아니라면 종료합니다.
if (!(arrow.getShooter() instanceof Player shooter)) {
return;
}
// 화살을 날린 플레이어의 스코어를 증가시킵니다.
final int score = controller.getScore(shooter) + ARROW_HIT_SCORE;
controller.setScore(shooter, score);
// Player#sendMessage() 메소드를 사용하여 플레이어에게 메시지를 전송합니다.
// Paper 에서는 String 대신 Component 사용을 권장합니다.
shooter.sendMessage(Component.text(String.format(
"적을 공격하여 %d 포인트를 획득하였습니다. 현재 스코어: %d",
ARROW_HIT_SCORE, score
)));
}
// EntityDeathEvent 에서 엔티티의 사망을 감지합니다.
@EventHandler
public void onDeath(EntityDeathEvent event) {
// 게임이 실행중인지 확인하고 실행중이지 않다면 종료합니다.
final GameController controller = GameController.getInstance();
if (!controller.isPlaying()) {
return;
}
// 사망한 엔티티가 플레이어라면 victim 으로 캐스팅하고
// 그렇지 않다면 종료합니다.
if (!(event.getEntity() instanceof Player victim)) {
return;
}
// 사망한 플레이어를 누가 죽였는지 불러옵니다.
final Player killer = victim.getKiller();
// 공격한 플레이어를 불러올 수 있다면
if (killer != null) {
// 공격한 플레이어의 스코어를 증가시킵니다.
final int score = controller.getScore(killer) + PLAYER_KILL_SCORE;
controller.setScore(killer, score);
// Player#sendMessage() 메소드를 사용하여 메시지를 전송합니다.
// Paper 에서는 String 대신 Component 사용을 권장합니다.
killer.sendMessage(Component.text(String.format(
"적을 처치하여 %d 포인트를 획득하였습니다. 현재 스코어: %d",
PLAYER_KILL_SCORE, score
)));
}
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
다음으로, 제작한 리스너를 메인 클래스의 onEnable() 메소드에서 등록합니다.
public final class ShootingGame extends JavaPlugin {
@Override
public void onEnable() {
// Register Command
final PluginCommand shootCommand = getCommand("shoot");
if (shootCommand != null) {
GameCommand command = new GameCommand();
shootCommand.setExecutor(command);
shootCommand.setTabCompleter(command);
}
// 플러그인 매니저를 불러옵니다.
final PluginManager pm = Bukkit.getPluginManager();
// 이벤트 리스너 객체를 생성하여 등록합니다.
pm.registerEvents(new GameListener(), this);
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
마치며
이해가 되지 않는 부분이나 궁금하신 점이 있다면 댓글로 남겨주세요.
'튜토리얼 > 마인크래프트 플러그인' 카테고리의 다른 글
[Minecraft Plugin Tutorial] 1. 플러그인 개발환경 설정 (0) | 2023.04.14 |
---|
시작하기에 앞서
아직 개발 환경을 설정하지 못했다면 이전 포스트를 참고해 주세요.
이번 시간에는 다음 항목을 학습하는 것을 목표로 합니다.
- 전체 메세지와 플레이어 메시지를 전송하는 방법
- 커맨드를 제작하고 등록하는 방법
- 이벤트 리스너를 제작하고 등록하는 방법
프로젝트 목표
플레이어가 화살을 쏘아 상대 플레이어를 맞추거나 처치했을 때 스코어가 오르는 게임을 제작합니다.
해당 게임을 구현하기 위해 다음과 같은 항목을 필요로 합니다.
- 게임 시작/종료 기능
- 플레이어의 스코어 정보를 저장하는 기능
- 조건 만족 시 플레이어의 스코어를 변경하는 기능
결과물은 다음과 같습니다.
전체 코드는 깃허브에서 볼 수 있습니다.
1. 게임 관리자 제작
먼저, 게임의 진행을 관리할 수 있는 관리자 클래스를 제작합니다.
관리자 클래스에서는 게임의 진행 상태 및 플레이어의 스코어 정보를 관리합니다.
게임 관리자는 단 하나의 인스턴스만 생성되기를 바라기 때문에 싱글톤 클래스로 제작합니다.
public class GameController {
// 인스턴스를 단 하나만 생성하기 위해 Holder클래스를 제작합니다.
private static class GameControllerHolder {
private static final GameController INSTANCE = new GameController();
}
// Holder클래스에서 생성된 인스턴스를 반환합니다.
public static GameController getInstance() {
return GameControllerHolder.INSTANCE;
}
// 게임의 진행 상태를 저장합니다.
private boolean isPlaying = false;
// 플레이어의 스코어 정보를 저장합니다.
private Map<String, Integer> scores = Maps.newHashMap();
// 직접적으로 객체를 생성할 수 없도록 private으로 처리합니다.
private GameController() {}
// 게임을 시작합니다.
public void startGame() {
if (!isPlaying) { // 게임이 실행중이지 않을 때
isPlaying = true; // 게임을 시작 상태로 변경합니다.
scores = Maps.newHashMap(); // 플레이어의 스코어 정보를 초기화 합니다.
}
}
// 게임을 종료합니다.
public void stopGame() {
if (isPlaying) { // 게임이 실행중일 때
isPlaying = false; // 게임을 종료 상태로 변경합니다.
}
}
// 플레이어의 스코어를 설정합니다.
public void setScore(@NotNull Player player, int score) {
if (isPlaying) { // 게임이 실행중일 때
// 플레이어의 UUID를 가져옵니다.
final String uuid = player.getUniqueId().toString();
// 플레이어의 스코어를 변경합니다.
scores.put(uuid, score);
}
}
// 플레이어의 스코어 정보를 반환합니다.
public int getScore(@NotNull Player player) {
// 플레이어의 UUID를 가져옵니다.
final String uuid = player.getUniqueId().toString();
// 플레이어의 스코어 정보를 조회하고
// 스코어가 존재한다면 가져온 스코어를, 그렇지 않다면 0을 반환합니다.
return scores.getOrDefault(uuid, 0);
}
// 모든 플레이어의 스코어를 반환합니다.
public Map<String, Integer> getScores() {
return Maps.newHashMap(scores);
}
// 게임이 진행중이면 true, 그렇지 않다면 false를 반환합니다.
public boolean isPlaying() {
return isPlaying;
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
2. 게임 커맨드 제작
다음으로 게임을 시작하고 종료할 수 있는 커맨드를 제작합니다.
커맨드의 자동 완성 기능을 제작합니다.
CommandExecutor 인터페이스를 구현하여 커맨드 입력 시 작동할 기능을 작성합니다.
TabCompleter 인터페이스를 구현하여 커맨드 자동 완성 기능을 구현합니다.
먼저, 커맨드 클래스를 제작합니다.
// CommandExecutor: onCommand() 메소드를 구현하여 커맨드의 작동 방식을 구현합니다.
// TabCompleter: onTabComplete() 메소드를 구현하여 커맨드의 자동 완성을 구현합니다.
public class GameCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(
@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args
) {
// 입력한 커맨드 인자의 길이가 0이라면 종료합니다.
// /shoot <- 인자의 길이가 0인 커맨드 입니다.
// /shoot start <- 인자의 길이가 1인 커맨드 입니다.
if (args.length == 0) {
return false;
}
// 가장 처음으로 입력받은 인자가 무엇인지 확인합니다.
switch (args[0]) {
// /shoot start 커맨드를 입력하였을 때 실행됩니다.
case "start" -> {
// GameController 객체를 가져옵니다.
GameController.getInstance().startGame();
// Bukkit.broadcast() 메소드를 사용하여 모든 유저에게 메시지를 전송합니다.
// 또한, Paper 에서는 문자열 처리를 String 대신 Component를 사용하는것을 권장합니다.
Bukkit.broadcast(Component.text("Start Game."));
}
// /shoot stop 커맨드를 입력하였을 때 실행됩니다.
case "stop" -> {
// GameController 객체를 가져옵니다.
GameController.getInstance().stopGame();
// 모든 유저에게 메세지를 전송합니다.
Bukkit.broadcast(Component.text("Stop Game."));
}
}
return false;
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args
) {
// 입력한 커맨드 인자의 길이가 1이 아니라면 종료합니다.
if (args.length != 1) {
return null;
}
// 첫번째 인자의 자동완성을 "start" 와 "stop" 으로 설정합니다.
return Lists.newArrayList("start", "stop");
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
다음으로, plugin.yml 파일에 커맨드 정보를 입력합니다.
name: ShootingGame
version: '${version}'
main: com.molruexception.shootinggame.ShootingGame
api-version: 1.19
# 등록할 커맨드 목록
commands:
# 등록할 커맨드 이름 /shoot <arg 1>
shoot:
# 커맨드를 사용할 때 필요한 퍼미션
permission: game.command.shoot
관련 코드는 깃허브에서도 확인할 수 있습니다.
마지막으로, 제작한 커맨드를 메인 클래스의 onEnable() 메소드에서 등록합니다.
public final class ShootingGame extends JavaPlugin {
@Override
public void onEnable() {
// plugin.yml에 작성한 커맨드를 불러옵니다.
final PluginCommand shootCommand = getCommand("shoot");
// 커맨드를 정상적으로 불러왔다면
if (shootCommand != null) {
// 커맨드 객체를 생성합니다.
GameCommand command = new GameCommand();
// 불러온 커맨드의 실행 객체를 위에서 생성한 커맨드 객체로 설정합니다.
shootCommand.setExecutor(command);
// 불러온 커맨드의 자동완성 객체를 위에서 생성한 커맨드 객체로 설정합니다.
shootCommand.setTabCompleter(command);
}
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
3. 스코어 리스너 제작
먼저, 리스너 클래스를 제작합니다.
리스너 클래스는 Listener 인터페이스를 구현하여 이벤트 리스너를 추가합니다.
// Listener 인터페이스를 구현하여 이벤트 리스너를 추가합니다.
public class GameListener implements Listener {
// 화살 적중시 스코어를 5점으로 설정합니다.
private static final int ARROW_HIT_SCORE = 5;
// 적 처치시 스코어를 20점으로 설정합니다.
private static final int PLAYER_KILL_SCORE = 20;
// EventHandler 어노테이션을 부착하여 이벤트를 수신합니다.
@EventHandler
public void onArrowHit(ProjectileHitEvent event) { // ProjecttileHitEvent에서 발사체 피격을 감지합니다.
// 게임이 실행중인지 확인하고 실행중이지 않다면 종료합니다.
final GameController controller = GameController.getInstance();
if (!controller.isPlaying()) {
return;
}
// 투사체를 피격한 엔티티가 플레이어가 아니라면 종료합니다.
if (!(event.getHitEntity() instanceof Player)) {
return;
}
// 투사체가 화살이 아니라면 종료합니다.
if (!(event.getEntity() instanceof Arrow arrow)) {
return;
}
// 화살을 날린 주체가 플레이어가 아니라면 종료합니다.
if (!(arrow.getShooter() instanceof Player shooter)) {
return;
}
// 화살을 날린 플레이어의 스코어를 증가시킵니다.
final int score = controller.getScore(shooter) + ARROW_HIT_SCORE;
controller.setScore(shooter, score);
// Player#sendMessage() 메소드를 사용하여 플레이어에게 메시지를 전송합니다.
// Paper 에서는 String 대신 Component 사용을 권장합니다.
shooter.sendMessage(Component.text(String.format(
"적을 공격하여 %d 포인트를 획득하였습니다. 현재 스코어: %d",
ARROW_HIT_SCORE, score
)));
}
// EntityDeathEvent 에서 엔티티의 사망을 감지합니다.
@EventHandler
public void onDeath(EntityDeathEvent event) {
// 게임이 실행중인지 확인하고 실행중이지 않다면 종료합니다.
final GameController controller = GameController.getInstance();
if (!controller.isPlaying()) {
return;
}
// 사망한 엔티티가 플레이어라면 victim 으로 캐스팅하고
// 그렇지 않다면 종료합니다.
if (!(event.getEntity() instanceof Player victim)) {
return;
}
// 사망한 플레이어를 누가 죽였는지 불러옵니다.
final Player killer = victim.getKiller();
// 공격한 플레이어를 불러올 수 있다면
if (killer != null) {
// 공격한 플레이어의 스코어를 증가시킵니다.
final int score = controller.getScore(killer) + PLAYER_KILL_SCORE;
controller.setScore(killer, score);
// Player#sendMessage() 메소드를 사용하여 메시지를 전송합니다.
// Paper 에서는 String 대신 Component 사용을 권장합니다.
killer.sendMessage(Component.text(String.format(
"적을 처치하여 %d 포인트를 획득하였습니다. 현재 스코어: %d",
PLAYER_KILL_SCORE, score
)));
}
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
다음으로, 제작한 리스너를 메인 클래스의 onEnable() 메소드에서 등록합니다.
public final class ShootingGame extends JavaPlugin {
@Override
public void onEnable() {
// Register Command
final PluginCommand shootCommand = getCommand("shoot");
if (shootCommand != null) {
GameCommand command = new GameCommand();
shootCommand.setExecutor(command);
shootCommand.setTabCompleter(command);
}
// 플러그인 매니저를 불러옵니다.
final PluginManager pm = Bukkit.getPluginManager();
// 이벤트 리스너 객체를 생성하여 등록합니다.
pm.registerEvents(new GameListener(), this);
}
}
관련 코드는 깃허브에서도 확인할 수 있습니다.
마치며
이해가 되지 않는 부분이나 궁금하신 점이 있다면 댓글로 남겨주세요.
'튜토리얼 > 마인크래프트 플러그인' 카테고리의 다른 글
[Minecraft Plugin Tutorial] 1. 플러그인 개발환경 설정 (0) | 2023.04.14 |
---|