*본 강의는 1.20.2 Paper 기준으로 제작되었습니다*
이전 강의
[자바로 마크 Paper 플러그인 만들기]1. 기본 세팅하기 (tistory.com)
참고하면 좋은 강의
[인텔리제이로 마크 플러그인 개발하기] 3. 흙을 캐면 다이아가 나오게 해보자! (이벤트와 리스너에 관하여) (tistory.com)
개요
어느 정도 규모가 있는 플러그인을 제작할 땐
플레이어 데이터에 대한 관리는 필수적입니다.
이번 강의에선, 서버에 접속 중인 유저들을 관리할 수 있도록
기반을 다져보겠습니다.
유저에 관한 데이터가 뭐가 있을까요?
1. UUID
Universally Unique IDentifier의 약자로
자바에서 제공하는 식별자입니다.
마인크래프트에선 이를 이용하여 플레이어의 고유 식별자를 부여 및 관리합니다.
마크의 주민등록번호라고 생각하시면 됩니다.
2. 이름
UUID에 해당하는 마인크래프트 닉네임입니다.
플러그인 상에서 추가로 부여한 데이터
1. 랭크
사용자 관리에 필요한 랭크 등급을 부여해 보았습니다.
newbie -> user -> admin
현재 구상은 위와 같습니다.
2. 돈
경제 기능 구현을 위한 돈입니다.
3. 직업
직업 구현을 위한 데이터로 기본값은 jobless입니다.
4. 칭호
기본값은 [뉴비]입니다.
총 6개의 유저 데이터가 있다고 생각하고 진행해 보겠습니다.
미리 보는 결과물
전체 구조에 대해
기본적인 패키지 구조는
MVC 디자인 패턴 + 도메인형 구조를 차용하였습니다.
/ (참고) 도메인형 구조란? /
도메인이란?
어느 한 기능(문제 해결)의 범위를 나타냅니다.
기능(문제 해결)의 범위.. 이 말 자체가 쉽게 와닿는 말은 아니죠.
이렇게 생각하시면 됩니다.
지금 여기서 만들고자 하는 플러그인의 기능은
"유저에 대한 데이터 관리"입니다.
이게 도메인입니다.
만약 현재 만들고 있는 유저 관리 기능 말고 낚시에 대한 기능을 만든다고 가정해 봅시다.
그러면 위와 같이 fishing라는 이름으로 최상위 패키지를 만들어주는 겁니다.
그리고 그 안에 똑같이 controller, entity 만듭니다.
이게 패키지의 도메인형 구조입니다.
이렇게 사용하게 되면 도메인의 흐름을 파악하기가 쉬워집니다.
/ (참고) MVC 패턴이란? /
MVC - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN (mozilla.org)
MVC 패턴은 설명하기 위해선 이 글이 너무 번잡스럽게 됩니다.
링크를 참고해 주세요.
밑에 짧게 표로 MVC 패턴에 대한 설명을 남기겠습니다.
다 필요 없고 이렇게만 아시면 됩니다.
패키지(폴더)명 | 부류 | 역할 |
controller | controller | 사용자의 입력을 받고 로직을 수행 |
controller.commands | ||
entity | model | 데이터의 정보(상태)를 저장 |
service | model | 비즈니스 로직 및 데이터 조작을 수행 |
(잘 보면 view가 없습니다. 이는 차후 강의인 Kyori Adventure API에서 다루도록 하겠습니다.)
이제 각각의 클래스에 대해 살펴보겠습니다.
controller.UserManagementController
유저 관리 기능 진입점으로
유저 관리에 필요한 이벤트나 커맨드 등록을 하는 곳입니다.
차후 다른 기능에서 유저 정보 수정이 필요하다면 이곳을 거치게 됩니다.
controller.UserConnectionController
유저 입퇴장에 관한 이벤트를 처리하는 객체입니다.
controller.commands.UserInfoCommand
유저 정보를 보여줄 명령어인 "/uinfo" 로직을 가지고 있는 객체입니다.
entity.User
유저 데이터를 정의하는 객체입니다.
service.UserManager
유저 데이터를 입력, 삭제, 수정 등을 진행하는 객체입니다.
미리 패키지 구조를 만드시고 진행하는 걸 추천드립니다.
코딩 단계
중간중간 코드를 그대로 사용했을 때
오류가 발생할 수도 있습니다.
다음 단계로 넘어가면 없어질 겁니다.
entity.User
public class User {
private UUID uuid;
private String displayName;
private String rank;
private Long money;
private String job;
private String prefix;
public User(UUID uuid, String displayName, String rank, Long money, String job, String prefix) {
this.uuid = uuid;
this.displayName = displayName;
this.rank = rank;
this.money = money;
this.job = job;
this.prefix = prefix;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getRank() {
return rank;
}
public void setRank(String rank) {
this.rank = rank;
}
public Long getMoney() {
return money;
}
public void setMoney(Long money) {
this.money = money;
}
}
유저에 대한 정보를 정의하는 객체이므로
Getter/Setter만 있으면 됩니다.
(Getter는 데이터를 가져오는 메소드를, Setter는 데이터를 변경하는 메소드를 의미합니다)
service.UserManager
public class UserManager {
private HashMap<Player, User> onlineUserData = new HashMap<>();
public void addUser(Player player) {
User newUser = new User(
player.getUniqueId(),
player.getDisplayName(),
"newbie",
1000L,
"jobless", "[뉴비]");
onlineUserData.put(player, newUser);
}
public void removeUser(Player player){
onlineUserData.remove(player);
}
public User getUserData(Player player){
return onlineUserData.get(player);
}
}
앞서 만들었던 User 객체를 이용하여
현재 접속하고 있는 유저들의 정보들을 담고 있는 객체입니다.
HashMap이라는 자료 구조를 사용하여 서버 접속 시에 추가하고
퇴장 시에 삭제할 수 있고
명령어를 통해 사용자의 정보를 불러오기 위해
addUser, removeUser, getUserData 메소드를 만들었습니다.
/ (참고) HashMap 이란? /
키와 값을 쌍으로 저장하는 자료 구조입니다.
여기서 키는 Player 객체이고
값은 User 객체입니다.
유저가 서버에 접속 시 Player 객체를 가져올 수 있으니
이를 통해 저장할 User 객체를 넣고 뺴고 할 수 있게 되는 겁니다.
이는 각 유저에 맞게 정보를 저장하기 위함입니다.
다른 유저에게 자신의 정보가 가면 안되겠죠??
현재는 파일이나 DB를 통해 사용자의 데이터를 저장할 수 없기 때문에
사용자가 들어온다면 무조건 기본 값을 부여하는 방식으로 진행합니다.
(나가면 다 삭제됩니다)
PaperEdu (메인 클래스)
public final class PaperEdu extends JavaPlugin {
private static PaperEdu serverInstance;
@Override
public void onEnable() {
serverInstance = this;
getLogger().info("플러그인 시작 테스트");
}
@Override
public void onDisable() {
serverInstance = null;
getLogger().info("플러그인 종료 테스트");
}
public static PaperEdu getServerInstance() {
return serverInstance;
}
}
차후 진행할 때 서버의 인스턴스가 필요한 경우가 있습니다.
이벤트, 명령어, 서버 콘솔창에 메시지 남기는 기능 등은 서버의 인스턴스를 요구합니다.
하지만 서버는 단 하나의 인스턴스로 존재해야 합니다.
(서버가 여러 개면 당연히 오류가 발생하겠죠??)
그렇기 때문에 메인 클래스 상에서 static으로 하나의 인스턴스로 만들어주고
다른 객체에서 getServerInstance() 메소드를 사용해 가져오는 방식인 Singleton 기법을 사용합니다.
/ (참고) Singleton 기법 /
controller.UserConnectionController
public class UserConnectionController implements Listener {
private final UserManager userManager;
UserConnectionController(UserManager userManager){
this.userManager = userManager;
}
@EventHandler
public void onUserJoinServer(PlayerJoinEvent event) {
userManager.addUser(event.getPlayer());
PaperEdu.getServerInstance().getLogger().info("플레이어 데이터 저장");
}
@EventHandler
public void onUserQuitFromServer(PlayerQuitEvent event) {
userManager.removeUser(event.getPlayer());
PaperEdu.getServerInstance().getLogger().info("플레이어 데이터 삭제");
}
}
이제 오늘 메인 주제인 유저 입퇴장에 관한 이벤트를 처리하는 곳입니다.
버킷에서 제공하는 Listener 인터페이스를 implements(구현) 함으로써
이벤트에 대한 처리를 할 수 있습니다.
@EventHandler는 어노테이션으로 이벤트의 처리를 하는 메소드임을 명시적으로 표시합니다.
(반드시 있어야 합니다)
메소드의 명은 아무렇게 하셔도 상관없습니다.
이제 메소드의 파라미터를 보시면 PlayerJoinEvent 객체를 받는 걸 볼 수 있죠.
이게 바로 플레이어가 입장을 했을 때를 나타내며 이 onUserJoinServer 메소드가 실행되는 겁니다.
즉, PlayerJoinEvent가 이벤트인 겁니다.
/ (참고) 이벤트가 무슨 의미일까요? /
이벤트란?
게임에서 일어난 모든 어떤 사건(행동)을 말합니다.
예를 들어보겠습니다.
어느 한 플레이어가 걷고 있다가 돌을 발견했습니다. 그리고 돌을 캐서 돌 하나를 획득하였습니다.
위 문장에서 이벤트가 과연 어떤 걸까요?
답은 플레이어가 걷는 행동, 돌을 시야 내에서 발견, 돌을 캠, 돌 하나를 획득 이 모든 것이 이벤트입니다.
이 강의에선 플레이어가 입장, 퇴장한 것이 이벤트라고 볼 수 있습니다.
이벤트가 발생할 때 메소드가 실행된다고 생각하시면 됩니다.
생성자를 통해 UserManager를 가져와서
플레이어 입장 이벤트가 발생하면 유저 정보를 추가하고
퇴장 이벤트가 발생하면 유저 정보를 삭제합니다.
이로써 이벤트에 처리는 끝났으나 이벤트를 생성했으면 서버 인스턴스에 등록을 해줘야 합니다.
controller.UserManagementController
public class UserManagementController {
private static UserManager userManager;
private final PaperEdu serverInstance;
private UserConnectionController userConnectionController;
public UserManagementController() {
this.userManager = new UserManager();
this.serverInstance = PaperEdu.getServerInstance();
this.userConnectionController = new UserConnectionController(userManager);
registerEvents();
}
private void registerEvents() {
serverInstance.getServer().getPluginManager().registerEvents(userConnectionController, serverInstance);
}
}
그걸 하는 곳이 UserManagementController입니다.
실질적으로 유저 관리 기능의 모든 것을 총괄하는 컨트롤러로
private static UserManager userManager;
현재 접속한 유저의 정보를 가지고 있는 UserManager 인스턴스 또한 가지고 있습니다.
(싱글턴으로 생성)
private void registerEvents() {
serverInstance.getServer().getPluginManager().registerEvents(userConnectionController, serverInstance);
}
서버 인스턴스에 이벤트를 등록하기 위해서 메인 클래스에서 서버 인스턴스를 가져오고
이를 이용해 이벤트를 등록합니다.
이제 메인 클래스에 UserManagementController를 생성하면 끝이 납니다.
PaperEdu (메인 클래스)
public final class PaperEdu extends JavaPlugin {
private static PaperEdu serverInstance;
private static UserManagementController userManagement;
@Override
public void onEnable() {
serverInstance = this;
userManagement = new UserManagementController();
getLogger().info("플러그인 시작 테스트");
}
@Override
public void onDisable() {
serverInstance = null;
userManagement = null;
getLogger().info("플러그인 종료 테스트");
}
public static PaperEdu getServerInstance() {
return serverInstance;
}
public static UserManagementController getUserManagement(){
return userManagement;
}
}
이벤트 등록은 끝났습니다.
명령어 만드는 다음 편으로 돌아오겠습니다.
감사합니다.
'마인크래프트 > 플러그인 제작 강좌(자바)' 카테고리의 다른 글
[자바로 마크 Paper 플러그인 만들기]4. 유저 데이터 관리하기(3) - 다른 플레이어 조회하기 (2) | 2024.06.26 |
---|---|
[자바로 마크 Paper 플러그인 만들기]3. 유저 데이터 관리하기(2) - 명령어 만들기 (17) | 2024.01.16 |
[자바로 마크 Paper 플러그인 만들기]1. 기본 세팅하기 (25) | 2023.12.23 |
[인텔리제이로 마크 플러그인 개발하기](보충) Gradle로 jar 빌드하기 2 (3) | 2023.06.30 |
[인텔리제이로 마크 플러그인 개발하기](보충)Config Reload 명령어 만들기 (2) | 2023.03.20 |
댓글