統合版アドオンで「装備ごとの状態」を保存する方法
武器の強化レベルのように、アイテム1本ずつに違う値を持たせたい時の dynamic property の使い方をまとめます。
武器強化アドオンを作っていて最初に詰まったのが、「この剣は強化レベル3、あっちの剣は5」という状態をどこに持たせるかでした。ワールド全体に保存すると当然すべての剣が同じレベルになってしまいます。
答えは ItemStack の dynamic property です。
値はアイテムに直接くっつく
setDynamicProperty はアイテムスタック1つずつに値を保存します。プレイヤーがその剣をチェストに入れようが、別のワールドに持って行こうが値は付いて回ります。
import { ItemStack } from '@minecraft/server';
const KEY_LEVEL = 'aoki:weapon.level';
const KEY_XP = 'aoki:weapon.xp';
function getLevel(item: ItemStack): number {
// 未設定なら undefined が返るので既定値を用意しておく
return (item.getDynamicProperty(KEY_LEVEL) as number | undefined) ?? 0;
}
function setLevel(item: ItemStack, level: number): void {
item.setDynamicProperty(KEY_LEVEL, level);
}
保存できるのは number / string / boolean / Vector3 だけです。オブジェクトを入れたい時は JSON.stringify して文字列で持たせることになりますが、後述の容量制限があるので必要最小限にした方がいいです。
キーには名前空間を付ける
level のような素の名前を使うと、別のアドオンと同時に入れられた時に衝突します。自分は全部 aoki: で始めるようにしました。
| よくない | よい |
|---|---|
level | aoki:weapon.level |
xp | aoki:weapon.xp |
class | aoki:weapon.class |
命名を統一しておくと、後で別のアドオンに統合する時に置き換える箇所が減ります。
書き換えたら手に持たせ直す
ここが一番ハマった所です。getComponent で取り出した ItemStack に値を書いても、それだけではインベントリに反映されません。取り出したものはコピーなので、書き換えた後に元のスロットへ戻す必要があります。
import { EquipmentSlot, Player } from '@minecraft/server';
function addXp(player: Player, amount: number): void {
const equippable = player.getComponent('equippable');
if (!equippable) return;
const item = equippable.getEquipment(EquipmentSlot.Mainhand);
if (!item) return;
const xp = (item.getDynamicProperty(KEY_XP) as number | undefined) ?? 0;
item.setDynamicProperty(KEY_XP, xp + amount);
// これを忘れると値が消える
equippable.setEquipment(EquipmentSlot.Mainhand, item);
}
書いたはずの値が次のティックで消えている時は、だいたいこの戻し忘れです。
容量に上限がある
1つのアイテムに保存できる dynamic property の合計サイズには制限があります。数値をいくつか持たせる程度なら問題になりませんが、装備の履歴や長い JSON を詰め込もうとすると引っかかります。
自分は「レベル」「累計XP」「武器種」の3つに絞りました。表示用の名前や倍率は、レベルから毎回計算すれば済むので保存していません。保存するのは復元できない情報だけ、という切り分けが結果的に一番きれいでした。
まとめ
- アイテム個別の状態は
ItemStackの dynamic property に持たせる - キーは
aoki:のような名前空間付きにする - 書き換えたら
setEquipmentで戻す - 計算で出せる値は保存しない