- 致编者:请牢记我们的域名wiki.mcbe-dev.net!
- 致编者:欢迎加入本Wiki的官方交流QQ群或Discord服务器!
- 基岩版1.19.31现已发布!(了解更多)
- Inner Core现已支持Xbox模组联机!(了解更多)
- 如果您是第一次来到本Wiki,欢迎注册一个账户
- 点击顶部的“编辑”或“编辑源代码”按钮即可编辑当前页面
- 请知悉:在不登录时也可以编辑和新建页面,但是您当前的IP地址会记录在编辑历史中
教程:编写脚本API/动态属性
引言[编辑]
动态属性(DynamicProperties) 是脚本系统存储自定义数据的一种格式。目前动态属性可以存储在实体、物品和世界上。相较于数据驱动中属性(或状态)的“静态性”,动态属性可以实时增删,且动态属性的类型不固定,脚本系统以此可以实现数据的动态存储。
动态属性以NBT的形式存储,其数据内容位于DynamicProperties复合标签下。每个行为包的动态属性存储在以该行为包UUID命名的复合标签内。
- 动态属性NBT结构:
- DynamicProperties
- <UUID>
- <dynamic_property_id1>
- <dynamic_property_id2>
- X
- Y
- Z
- ……
- <UUID>
获取和设置[编辑]
旧版动属相对复杂,在新版,提供了以下5个方法来对动态属性进行操作:
clearDynamicProperties
:清除该对象上的所有动态属性getDynamicProperty
:获取该对象上指定动态属性的值getDynamicPropertyIds
:获取该对象上所有动态属性的IDgetDynamicPropertyTotalByteCount
:获取该对象上存储的所有动态属性的总字节数setDynamicProperty
:在该对象上设置一个动态属性
这些方法可从World、Entity和ItemStack三个类中调用。
注意: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的开发环境的话,请使用上面的代码来安装它。否则,请在这个地方来复制它的单文件版本。
存储与读取[编辑]
上面的方法足以满足大部分需求,但压缩和保存都是消耗大量时间的操作,我们需要一个良好的方式来完成存储和读取:
下面只介绍思路,文件保留在实例中:
- 使用对象(
DataObject
)来控制系统。 - 使用方法来读取对象的引用,这样可以通过直接的赋值操作,而无需额外设置。
- 监听
worldInitialize
或worldLoad
以保证在世界加载完成的时候去读取动态属性。 - 同时注册一个循环调用器(
runInterval
),来保证自动储存,防止数据丢失。 - 监听
shutdown
来保证在退出游戏之前保存数据。 - 监听
playerSpawn
用来在玩家初次生成时读取动态属性,推荐在世界加载的时候也读取所有玩家的动态属性(防止/reload
命令引起错误)