진입 장벽 주의
이 내용은 초보자 게이들에게 난해한 설명이 많이 포함되어 있음. (아니면 내 글쓰기 실력 때문일 수도)
와우! 난 커맨드를 사랑해! I LOVE CMD!!!(혼인 신고서: 나&커맨드) 수준의 집착을 가지고 있지 않는다면,
그대로 Alt + 좌측 방향키를 눌러줘라. (Windows)
(맥을 사용 중이라면, Cmd + 좌측 방향키를 눌러줘라)
유의
1.21.3 이상 버전을 기준으로 작성하였음.
작성자는 사람임. 실수를 할 수 있음.
이해를 돕기 위해 정상적이지 않은 설명을 이용할 수 있음.
마인크래프트에는 nbt라는 커맨드를 하는 입장(이 글을 읽고 있는 마붕이)에서
아주 중요하고 중요한 요소가 있음.
마인크래프트에서 대부분의 행동 및 상태는 이 nbt를 통해 로드되며 게임 틱에 의해 유동적으로 바뀌기도 하며,
오브젝트(엔티티, 블록)의 값을 저장하는 역할을 수행함. (실제로 "월드" 파일에 저장되는 형식은 다르지만)
특정 nbt는 맵 제작이나, 시스템 개발에 요긴하게 쓰이기도 함.
떠돌이 상인의 wander_targetX/Y/Z나, 갑옷 거치대의 Invisible 등등.
(심지어 주로 사용하는 커맨드 블록도 고유의 nbt를 가지고 있음!)
모장은 이 nbt를 명령어로도 수정, 접근할 수 있도록 만들었는데,
대표적으로 data, execute store, execute if data, @x[nbt={}] 등이 그것임.
이것을 이용해 맵 제작 퀄리티를 상승시키거나, 여러 기믹을 제작할 수 있게 되었음.
또한, 커맨드를 하는 입장에서 엔티티나 블록의 nbt를 외우는 일은 당연시되며,
이를 어떠한 책을 읽고 이해하기 위해 한글(발음와 형태)과 한국어(단어와 문장 성분)를 먼저 배우는 것에 비유할 수 있음.
nbt를 많이 외웠다는 것(또는 알고 있는 것)은 이들(이 글을 읽고 있는 마붕이22) 사이에서
흔히 말하는 "초보자"와 "고수"를 나누는 지표가 되기도 함.
그런데, 한 가지 의문점이 듬.
이 nbt. 다시 말해 name binary tag는 정말 사용해도 괜찮을까?
장점만 존재하는 사기캐가 정말 마인크래프트 세계에 가득해도 되는 걸까?
다행히(가 아니지만), 신(모장 은신이야!)은 모든 것에 대해 공평함.
nbt는 맵 제작 퀄리티, 여러 기믹 제작 가능이라는 장점을 앞에 두고,
무지막지한 비용(여기서는 돈이 아니라 성능으로 정의)을 소모하는 검은 그림자같은 단점을 가지고 있음.
잘만 사용하면 맛있는 요리를 만들 수 있지만,
잘못 휘두르면 사람을 다치게 하는 칼처럼 작용할 수 있다는 의미임. (아마도 피해자는 해당 서버나 본인의 컴퓨터겠지)
작성자는 왜. 무엇 때문에, nbt 사용에 대한 큰 패널티가 "성능"이라고 말 한 걸까?
이는 한 가지 실험으로 증명할 수 있음.
우선 다음 명령어를 이용해서, 세계에 존재하는 모든 플레이어의 모든 레시피를 제공함.
(세계는 디버그 월드가 실험하기에 좋음)
/recipe give @a *
그리고 다음 명령어를 반복형 명령 블록에 삽입해서 모든 플레이어의 nbt 검사를 해보자.
(명령 출력은 꺼줘라. 그리고 nbt를 선택자 내에서 반복 사용하는 이유는 그래프에서 드라마틱한 변화를 확인 가능할 정도로 실행 횟수를 증가시키기 위함임)
execute if entity @a[nbt={},nbt={},nbt={},nbt={},nbt={},nbt={},nbt={},nbt={},nbt={}]
(절대로 플레이어가 5명 이상 넘어가는 곳에서 실행하지 마셈!)
반복형 명령 블록을 실행 했다면, 이제 F3+2 키를 눌러 세계의 MSPT 그래프를 보도록 하자.
이런! 누가 토마토 주스를 쏟았나 보노.
흠... 별반 다른 점을 찾기가 어려울다고?
그럼 방금 실행시킨 반복형 명령 블록의 실행을 중단해 보자!
그리고 다시 그래프를 관찰해 보면
누가 이렇게 재빨리 토마토 주스를 닦은 거지? 청소부로 고용해야겠음.
흠... 누군가는 이렇게 말할 수도 있겠노.
???: "nbt={}은 플레이어의 모든 nbt를 확인해서 느린 거 아니냐? 특정 값을 넣으면 달라지겠지!"
그럼 플레이어의 Health nbt가 20.0f인지 검사해 보자.
execute if entity @a[nbt={Health:20f},nbt={Health:20f},nbt={Health:20f},nbt={Health:20f},nbt={Health:20f},nbt={Health:20f},nbt={Health:20f},nbt={Health:20f},nbt={Health:20f}]
으악! 중력이 다시 한번 내 토마토 주스를!
오히려 랙이 더 증가한 것 같은데..?
???: "이런 간고등어 같은 게임!!!!"
하하. 금쪽이 마인크래프트에 대한 화풀이는 그쯤하고,
이제 진짜 이유에 대해 알아보자. (지금까지의 서론은 그저 추진력을 얻기 위함이었다)
nbt 검사가 랙이 걸리는 이유
정확한 원인 탐색을 위해, 마인크래프트의 진짜 모습을 파헤쳐 보겠음.
선택자의 모든 인수(tag, scores, name 등등)는 EntitySelectorOptions라는 클래스에서 정의되고, 동작함.
여기 그 중에서 선택자의 nbt={}를 구성하는 코드를 보여주지.
차근차근 알아가 보자.
유의
1.21.3 이상 버전을 기준으로 작성하였음.
작성자는 사람임. 실수를 할 수 있음.
이해를 돕기 위해 정상적이지 않은 설명을 이용할 수 있음.
여기서부터 조금 어지러우면, 이건 건너뛰셈.
1. 먼저 regis ter로 "nbt=" 인수를 등록함. (선택자 조건으로 사용할 수 있도록)
2. "!"(조건 뒤집기, not)를 사용했다면, flag6을 true로 설정함.
3. compoundtag에 작성한 nbt(문자열)를 CompoundTag로 캐스팅하여 저장함. ("{Health:20.0f}" -> {Health:20.0f})
4. nbt 검사 대상(엔티티)의 조건을 추가함. (선택자 필터링에 사용된다)
5. compoundtag2에 검사 대상(엔티티)의 기본 nbt(Pos, Rotation 등)와 특정 nbt(text_display의 text, text_opacity 등)를 saveWithoutId 메서드를 이용해 값들을 저장하고, CompoundTag로 캐스팅하여 저장함. (기본 nbt 중에서 무조건 저장되는 nbt와 선택적으로 저장되는 nbt는 후술할 "nbt 저장 목록"을 확인하셈)
6. 만약에 검사 대상이 플레이어라면, 현재 플레이어가 들고 있는 아이템을 itemstack에 저장함.
-- 6.1. 플레이어가 어떠한 아이템을 들고 있다면, compoundtag2에 SelectedItem을 삽입한 뒤, itemstack을 해당 nbt에 저장함.
-- 6.2. 여기서 알 수 있는 점: SelectedItem은 "월드" 파일에 저장되는 별도의 값이 아니다. (그때그때 필요할 때마다 생성하는 식으로 동작함)
7. 작성한 nbt와 검사 대상에 저장된 nbt가 서로 일치하는지 확인하고 boolean을 반환함. (일치한다면, true를 반환함)
8. 선택자 내에서 중복 사용 가능함. (@s[nbt={},nbt={}])
-- 8.1. 다만, 이렇게 사용하면 https://bugs.mojang.com/browse/MC-257155 버그로 인해, 비용을 더 많이 소모함.
여기서 5. 번이 중요한데, nbt를 검사하기 위해 검사 대상의 nbt를 모조리 저장하는 짓을 하고 있음.
이게 얼마나 치명적인 문제냐면, 내가 앞에서 보여준 레시피의 경우, 플레이어의 recipeBook.recipes nbt에
1,337개의 문자열이 ArraryList로 저장된다. 그러면 매 틱당 1,337개의 리스트를 변수에 삽입하는 간간간고등등등어 같은 짓을 하고 있다는 의미이니... 서버와 컴퓨터가 비명 소리(그 말대로 악! 소리 47번)를 외칠 수 밖에 없겠지.
nbt 저장 목록
무조건 저장: Pos, Motion, Rotation, FallDistance, Fire, Air, OnGround, Invulnerable, PortalCooldown, UUID
선택적 저장(값이 있거나 1 또는 1 이상이면): CustomName, CustomNameVisible, Silent, NoGravity, Glowing, TicksFrozen, HasVisualFire, Tags, Passengers
기본적으로 저장되는 nbt
정말 어지럽노.
그런데, 짜잔! 이게 끝이 아니네?(마붕이에게 쉴 틈을 주지 않겠어)
마인크래프트가 과연 선택자(@p, @a, @r, ...)에만 이런 짓을 해놨을까?
마음을 조심스럽게 가다듬고 다른 nbt에 접근하는 명령어에도 이런 끔찍한 일이 일어나는지 확인 해봤음.
엔티티의 nbt에 접근하기 위한 모든 명령어(execute store, if data, data get 등)는 모두 아래의 메서드를 꼭 거치게 된다.
이제 설명 안 해줘도 뻔하지?
엔티티 외에 블록도 같은지 확인해 보자.
이제 설명 안 해.
엔티티의 nbt 보다는 덜하겠지만, 기본 nbt(x, y, z) 또한 같이 저장하고 있음.
오, 마이 아이즈.
정말 신에게는 12척의 배 밖에 남아 있지 않다는 것이냐??
(1.17 버전 때, 추가된 마커도 기본 nbt를 저장함)
정말로 해결책은 없나?
마인크래프트 1.15 버전 때, 추가된 기가막힌 nbt 저장소가 하나 있음.
ㅇㅇ. 얘는 그냥 nbt 저장하라고 모장이 만들어줬음.
그 12척의 배는 바로, "스토리지"임.
스토리지에 접근하면, 다른 쓸 데 없는 모든 값들이 저장되지 않고, path에 지정된 해당 값만을 확인함. (사실은 이게 당연한 거 아닌가)
스토리지를 어떤식으로 이용함?
만약, 작성자의 인벤토리에서 돌이 hotbar에 있는지 검사하고 싶음. (지금은 그냥 execute if items 쓰셈)
그런데, 이런! 앞서 설명했던 if data entity를 사용하면 간간간고등등등어 같은 게임이 지멋대로 nbt를 복사해버리기 때문에,
플레이어가 많아지면 성능에 크나큰 상처를 주게 될 것임.
이때, 스토리지를 다음과 같이 사용할 수 있음. (아래 명령어는 무조건 함수에서만 실행함)
execute as @a run function <next>
#next.mcfunction
data modify storage items: Inventory set from entity @s Inventory
execute if data storage items: Inventory[{id:"minecraft:stone", Slot:0b}] run say 난, 돌을 핫바0에 지니고 있어!
execute if data storage items: Inventory[{id:"minecraft:stone", Slot:1b}] run say 난, 돌을 핫바1에 지니고 있어!
execute if data storage items: Inventory[{id:"minecraft:stone", Slot:2b}] run say 난, 돌을 핫바2에 지니고 있어!
execute if data storage items: Inventory[{id:"minecraft:stone", Slot:3b}] run say 난, 돌을 핫바3에 지니고 있어!
execute if data storage items: Inventory[{id:"minecraft:stone", Slot:4b}] run say 난, 돌을 핫바4에 지니고 있어!
execute if data storage items: Inventory[{id:"minecraft:stone", Slot:5b}] run say 난, 돌을 핫바5에 지니고 있어!
execute if data storage items: Inventory[{id:"minecraft:stone", Slot:6b}] run say 난, 돌을 핫바6에 지니고 있어!
execute if data storage items: Inventory[{id:"minecraft:stone", Slot:7b}] run say 난, 돌을 핫바7에 지니고 있어!
execute if data storage items: Inventory[{id:"minecraft:stone", Slot:8b}] run say 난, 돌을 핫바8에 지니고 있어!
물론 from entity로 엔티티의 nbt를 참조하는 데, 요소 전체를 저장하지만,
그 다음 스토리지에서만 검사를 하는 것으로 엔티티 nbt 접근 시, 발생하는 비용을 최소화 할 수 있음.
추가로
이건 별개의 이야기인데,
플레이어의 Rotation[0] 값만 받아오도록 하는 명령어를 만들었음(모드)...

data get 명령어에 비해 대략, 37배 정도 차이가 나노.

결론: 앞으로 nbt를 사용하는 사람이 있다면, 본인의 칼 솜씨로 멋진 요리를 만들어 주자. (헤치진 마셈)
그리고 기회가 된다면 이 글 링크를 주는 것도
진짜 끗
화살의 Motion을 1번 수정했는데 화살이 1초 느리게 알아채는 버그가 있더라고 화살의 Motion을 계속 수정하니 부드럽게 움직였음(?) 그냥 양날의 검이 아니라 좀만 잘못 잡아도 다치는 가시검임
https://bugs.mojang.com/browse/MC-139548 아주 유서 깊은 버그
1.21.2에선 안 그랬는데 1.21.3에서 갑자기 이상해지던데 추가적으로 경험치 바닥에 튕기는 것처럼 보이는 거랑 화살이 몹 맞고 튕겨나가는(것처럼 보이는) 버그도 발생했음
토마토 주스 레전드넼ㅋㅋㅋㅋㅋ
유익한 글인데 정병걸릴 것 같은 말투가 다 잡아먹네
말투좀