教學:編寫腳本API/動態屬性

出自Minecraft基岩版开发Wiki
動態屬性
系列教學
所屬系列
難易度
初級
實踐裝置
WindowsAndroid
所需軟體

引言[編輯]

動態屬性(DynamicProperties) 是腳本系統儲存自訂資料的一種格式。目前動態屬性可以儲存在實體、物品和世界上。相較於資料驅動中屬性(或狀態)的「靜態性」,動態屬性可以實時增刪,且動態屬性的型別不固定,腳本系統以此可以實現資料的動態儲存。

動態屬性以NBT的形式儲存,其資料內容位於DynamicProperties複合標籤下。每個行為包的動態屬性儲存在以該行為包UUID命名的複合標籤內。

動態屬性NBT結構:
  •  DynamicProperties
    •  <UUID>
      •  <dynamic_property_id1>
      •  <dynamic_property_id2>
        •  X
        •  Y
        •  Z
      •  ……

取得和設定[編輯]

舊版動屬相對複雜,在新版,提供了以下5個方法來對動態屬性進行操作:

  • clearDynamicProperties:清除該物件上的所有動態屬性
  • getDynamicProperty:取得該物件上指定動態屬性的值
  • getDynamicPropertyIds:取得該物件上所有動態屬性的ID
  • getDynamicPropertyTotalByteCount:取得該物件上儲存的所有動態屬性的總字節數
  • setDynamicProperty:在該物件上設定一個動態屬性

這些方法可從World、​EntityItemStack三個類中呼叫。

  注意:ItemStack类上此ItemStack必须不可堆叠,这要求在本质上不能堆叠,同时利用nbt修改的堆叠也无效。

動態屬性可以儲存布林型、數值型、字串型和三維向量型的資料,單個動態屬性的資料大小限制在32KB以內。大量的動態屬性資料可能會導致某些裝置上載入緩慢。

設定[編輯]

對任意可使用動屬的物件,使用setDynamicProperty可設定其動屬。

import {
    ItemStack
} from "@minecraft/server";

// setDynamicProperty(identifier: string, value?: boolean | number | string | Vector3): void
const exampleItem = new ItemStack("minecraft:diamond_axe");

exampleItem.setDynamicProperty("example", "Test");
exampleItem.setDynamicProperty("示例", true);
// identifier 在此不是命名空间,可为任意字符串,但推荐使用命名空间

setDynamicProperty的資料可為一個字串,大小限制為32726個字元。

JSON是一種好的方法,JSON.stringify()可以把JSON轉為字串,JSON.parse()又可以把字串轉回JSON。所以可以像下面這麼做:

import {
    world
} from "@minecraft/server";

function setData(id, json, Class = world) {
    Class.setDynamicProperty(id, JSON.stringify(json));
}

除此外我們也可以清除動屬。

import {
    ItemStack
} from "@minecraft/server";

// clearDynamicProperties(): void
const exampleItem = new ItemStack("minecraft:diamond_axe");
exampleItem.setDynamicProperty("example", "Test");
exampleItem.clearDynamicProperties();

clearDynamicProperties()可以清除物件上的所有動屬。

取得[編輯]

對任意使用了動屬的物件,使用以下方法可取得其動屬。

getDynamicProperty()可取得目標動屬,範例如下:

import {
    ItemStack
} from "@minecraft/server";

// getDynamicProperty(identifier: string): boolean | number | string | Vector3 | undefined
// ...
// 省略定义和设置动属的过程。
let result = exampleItem.getDynamicProperty("example");
console.warn(result);

getDynamicPropertyIds()可取得物件上使用的可用動態屬性識別碼集,範例如下:

import {
    ItemStack
} from "@minecraft/server";

// getDynamicPropertyIds(): string[]
// ...
// 省略定义和设置动属的过程。

let result = exampleItem.getDynamicPropertyIds();
console.warn(result);

getDynamicPropertyTotalByteCount()可獲得物件儲存的所有動態屬性的總大小(以字節為單位)。這包括鍵和值的大小。這對於診斷效能警告標誌非常有用 - 例如,如果一個實體具有許多兆字節的關聯動態屬性,則在各種裝置上載入它可能會很慢,範例如下:

import {
    ItemStack
} from "@minecraft/server";

// getDynamicPropertyTotalByteCount(): number
// ...
// 省略定义和设置动属的过程。
let result = exampleItem.getDynamicPropertyTotalByteCount();
console.warn(result);

再結合上面的setData,我們可以使用它:

import {
    world
} from "@minecraft/server";

function getData(id, Class = world) {
    return JSON.parse(Class.getDynamicProperty(id));
}

動態屬性管理[編輯]

對於一些大型的專案,我們可能要進行比較複雜的動態屬性管理。下面有些思路供您參考:

分段[編輯]

32726個字元對於小專案已經足夠使用了,但對於大型專案來說遠遠不夠,可以嘗試將長的字串分割成多部分以供儲存:

// 限制最大分割长度
const BLOCK_LENGTH = 1000;

function readDataOnDynamicProperty(target) {
    // 读取分割块数
    const quantity = target.getDynamicProperty("data_space_quantity");
    // 没有正常读取成功直接返回
    if (typeof quantity !== "number") return {};
    if (quantity === 0) return {};
    // 准备储存量
    let value = "";
    // 拼接
    for (let i = 1; i <= quantity; i++) {
        const currentValue = target.getDynamicProperty(`data_space_${i}`);
        if (typeof currentValue !== "string" || currentValue === "") return {};
        value += currentValue;
    }
    try {
        // 转回对象
        return JSON.parse(value);
    } catch {
        return {};
    }
}

function writeDataOnDynamicProperty(target, source_data) {
    // 转为字符串
    const data = JSON.stringify(source_data);
    let dataBlocks = [];
    // 分割
    for (let i = 0; i < data.length; i += BLOCK_LENGTH) {
        dataBlocks.push(data.slice(i, i + BLOCK_LENGTH));
    }
    // 设置数量
    const quantity = dataBlocks.length;
    target.setDynamicProperty("data_space_quantity", quantity);
    // 保存
    for (let i = 1; i <= quantity; i++) {
        target.setDynamicProperty(`data_space_${i}`, dataBlocks[i - 1]);
    }
}

這裡我們預設將所有的資料儲存在同一個物件中,這樣可以更大程度的利用空間。

壓縮[編輯]

透過各種形式,我們可以壓縮字串來獲得更多的儲存空間,js有一個bota()方法可以將字串base64化,但我們還有另外的更好方法:

LZString是一個以MIT協定開源(該協定限制很小,在我們的專案中可以很好地使用)的字串壓縮專案(JS),它本來旨在滿足在localStorage中儲存大量資料的需求,但其原理就是使用壓縮的方式。其壓縮度很高,可以被我們使用:

npm i lz-string

如果您有NodeJs的開發環境的話,請使用上面的程式碼來安裝它。否則,請在這個地方來複製它的單檔案版本。

儲存與讀取[編輯]

上面的方法足以滿足大部分需求,但壓縮和儲存都是消耗大量時間的操作,我們需要一個良好的方式來完成儲存和讀取:

下面只介紹思路,檔案保留在實例中:

  1. 使用物件(DataObject)來控制系統。
  2. 使用方法來讀取物件的引用,這樣可以透過直接的賦值操作,而無需額外設定。
  3. 監聽worldInitializeworldLoad以保證在世界載入完成的時候去讀取動態屬性。
  4. 同時註冊一個循環呼叫器(runInterval),來保證自動儲存,防止資料丟失。
  5. 監聽shutdown來保證在退出遊戲之前儲存資料。
  6. 監聽playerSpawn用來在玩家初次生成時讀取動態屬性,推薦在世界載入的時候也讀取所有玩家的動態屬性(防止/reload指令引起錯誤)

引用[編輯]

  1. 官方文件: ItemStack Class