이번 글에서 만들 기능은 게임 내에 유저의 돈을 저장하는 계좌같은 기능을 만들어 볼 것이다. 마인크래프트 외에도 여러가지 게임을 하다보면 유저의 돈, 레벨, 경험치 등 시시각각으로 바뀌는 데이터가 게임 내에 저장된다. NeoForge에서도 Data Attatchment라는 이런 기능을 제공한다.
처음에는 데이터를 저장할 때, 모드 안에 데이터베이스에 접속하는 코드를 두어야하나 생각했는데 모드는 여러 유저가 설치해야 하기 때문에 데이터베이스의 비밀번호가 노출될것 같아 접고 인터넷을 찾아보던 중 공식 문서에서 Data Attatchment를 알게되었다.
https://docs.neoforged.net/docs/datastorage/attachments/
더 자세히 알고 싶은 경우 위 공식 문서를 참고하길 바란다.
Data Attatchment
Data Attatchment는 다음 세가지 방식으로 할 수 있다.
- INBTSerializable를 통한 직렬화
- 코덱을 통한 직렬화
- 직렬화 하지 않기
만약 게임에서 나갈 경우 데이터가 사라지는 것을 원할 경우 세 번째 방식을 사용하면 된다. 하지만 게임에서 나갔다 다시 들어와도 데이터가 유지되게 하고 싶다면 1,2번째 방식을 사용해야 한다.
직렬화(Serialization)
직렬화(Serialization)는 객체 또는 데이터 구조를 연속적인 바이트 스트림으로 변환하여 파일, 데이터베이스, 네트워크 등을 통해 저장하거나 전송할 수 있도록 만드는 과정이다. 3번째 방식은 직렬화가 되지 않았기 떄문에 데이터가 파일을 통해 저장될 수 없다. 따라서 게임을 종료하면 삭제된다.
하지만 첫번째와 두번째 방식은 직렬화 되었기 때문에 게임을 종료했다 다시 들어가도 유지된다.
그리고 INBTSerializable와 코덱을 통한 직렬화는 간단히 말하자면 전자는 마인크래프트의 블록이나 엔티티, 아이템 객체를 저장할 때 사용하고, 코덱은 그 외를 저장할 때, 사용하면 된다.
Data Attatchment를 이용한 유저의 “돈” 데이터 관리
필자는 화폐시스템 모드를 만들 것이기 때문에, 유저들의 돈을 저장하거나 수정하기 위한 계좌 같은 데이터를 저장하는 기능이 필요하다. 이것을 Data Attatchment로 구현해보자.
먼저 player이라는 패키지를 새로 만들고 PlayerDataAttatchment 라는 클래스를 만들어주었다.

#1에서 배웠다시피 등록이 필요하므로 DeferredRegister 메서드를 만들어 준다.
private static final DeferredRegister<AttachmentType<?>> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, MODID);
그 후, 데이터 공급자를 만들어 준다. 이 부분이 데이터를 저장하고 가져오는 기능을 한다.
public static final Supplier<AttachmentType<Integer>> MONEY = ATTACHMENT_TYPES.register(
"money", () -> AttachmentType.builder(() -> 100000).serialize(Codec.INT).build()
);
100000은 초기값이다. 나는 처음에 유저가 게임에 접속 되었을 때, 필수품은 살 수 있도록 10 만원에 기초 지원금을 주기로 했다. 그리고 이 값은 블럭이나 엔티티가 아니고 단순한 숫자이므로 Codec으로 직렬화 했다
public static void register(IEventBus eventBus) {
ATTACHMENT_TYPES.register(eventBus);
}
ATTACHMENT_TYPES을 이벤트버스에 등록하는 메서드도 만들어준다. 이 메서드가 실행되어야 Data attatchment를 정상적으로 사용할 수 있다.
전체코드는 다음과 같다.
public class PlayerDataAttatchment {
private static final DeferredRegister<AttachmentType<?>> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, MODID);
public static final Supplier<AttachmentType<Integer>> MONEY = ATTACHMENT_TYPES.register(
"money", () -> AttachmentType.builder(() -> 100000).serialize(Codec.INT).build()
);
public static void register(IEventBus eventBus) {
ATTACHMENT_TYPES.register(eventBus);
}
}
그리고 메인 클래스에는 PlayerDataAttatchment.*register*(modEventBus);
를 작성해서 등록을 해주자.
다음으로는 이런 데이터들을 수정하거나 가져오는 기능을 하는 메서드들을 모아둔 클래스를 만들어주자
클래스 구조를 정의하기 위한 인터페이스인 IPlayerData와 메서드들을 모아둔 PlayerData 클래스를 만들어준다.

나는 유저의 돈을 가져오는 기능, 유저에게 돈을 추가하는 기능, 유저에게 돈을 설정하는 기능이 필요하므로 다음 3가지 메서들을 인터페이스에 정의해 주었다.
public interface IPlayerData {
int getMoney(Player player);
void addMoney(Player player, int amount);
void setMoney(Player player, int amount);
}
매개변수로 들어오는 player은 돈을 가져오거나 설정할 플레이어의 정보를 담고있고 amount는 설정하거나 추가할 돈의 액수이다.
그리고 playerData클래스는 인터페이스에 맞추어 다음과 같이 만들어주었다.
public class PlayerData implements IPlayerData{
@Override
public int getMoney(Player player) {
int money = player.getData(MONEY);
return money;
}
@Override
public void addMoney(Player player, int amount) {
player.setData(MONEY, player.getData(MONEY) + amount);
}
@Override
public void setMoney(Player player, int amount) {
player.setData(MONEY, amount);
}
}
아까 DataAttatchment를 만들었을 때, 공급자 이름을 MONEY로 지었으므로, player.getData(MONEY);
를 하면 저장된 돈 데이터를 불러온다. 또한 player.setData(MONEY, player.getData(MONEY) + amount);
는 저장된 데이터를 수정한다.
위와 같이 플레이어들에게 데이터를 저장하고 수정하는 메서드들을 만들어주었으니 이 메서드들을 사용해야한다. 나는 “돈” 명령어를 입력했을 때, 저장된 돈의 액수를 출력하고, “/수표 액수” 를 입력했을 때, 유저에게 특정 액수의 수표를 지급하려고한다.
특정 기능을 수행하는 명령어를 제작하고, DataAttatchment의 메서드들을 사용하는 것은 다음 글에 작성해 두었다.