数据的完整生命周期
VVE 中最关键的不是某个公式,而是四种数据位置之间何时复制。混淆 _proj 与 _get,或忘记 _store,都会让“看起来执行成功”的命令没有持久效果。
四种数据位置
| 位置 | 典型路径 | 生命周期 | 用途 |
|---|---|---|---|
| 预设数据模板 | storage <ns>:class <module>_plate | 初始化后长期存在 | 创建同类实例的默认值 |
| I/O 输入输出 | storage <ns>:io input/result | 一次接口调用 | 在模块边界交换结构化数据 |
| 共享临时对象 | x int, vx int, quat_x int… | 一次连续计算 | 高性能数学工作区 |
| 实例对象 | 当前执行实体的 scores/NBT | 实例存活期 | 保存每个物体自己的状态 |
路径 A:构造一个实例
text
plate → io.input → _proj → 临时对象
↓ 修改 / anchor / impulse
_model → io.result → io.input → _new → 实例以可实例化模块 example:block 为例:
mcfunction
# 1. 复制默认 plate
data modify storage example:io input set from storage example:class block_plate
# 2. 投射到共享临时对象
function example:block/_proj
# 3. 在临时对象上设置位置、姿态或初速度
execute as @e[tag=math_marker,limit=1] positioned 0 100 0 rotated 30 0 run function vve:object/_anchor_to
# 4. 重新建模成结构化数据
function example:block/_model
data modify storage example:io input set from storage example:io result
# 5. 在当前执行位置创建实例
execute positioned 0 100 0 run function example:block/_new_proj 不创建实体;_model 不写回任何现有实例;_new 才消耗 io input 创建实例。
路径 B:更新一个实例
text
实例 → _get → 临时对象 → 计算 → _store → 同一实例mcfunction
execute as @e[tag=example_block,limit=1] run function example:block/_get
scoreboard players add vy int 500
execute as @e[tag=example_block,limit=1] run function example:block/_store这里 _store 必须仍以原实例为执行者。不要依赖 @e[tag=...,limit=1] 在复杂场景里两次都选中相同对象;更稳妥的写法是在同一个 execute as 调用链中进入一个封装函数:
mcfunction
execute as @e[tag=example_block] run function example:block/_acceleratemcfunction
# example:block/_accelerate
function example:block/_get
scoreboard players add vy int 500
function example:block/_store_class 只定义未来实例
_class 通常执行 _zero,设置默认字段,再 _model 写入 plate:
mcfunction
function example:block/_zero
scoreboard players set a int 2500
scoreboard players set mass int 17
scoreboard players set inertia int 500
function example:block/_model
data modify storage example:class block_plate set from storage example:io result重新运行 _class 会改变以后创建的实例,不会自动迁移已经存在的实体。需要升级存量实例时,应编写显式 migration:逐实例 _get、补字段、校验、_store。
精度缩放是数据契约的一部分
scoreboard 只保存整数。字段名旁常用 MOT 标注:
text
<x,int,1w> # 10000 表示 1
<angular_x,int,100w> # 1000000 表示 1
<inertia,int,100> # 100 表示 1storage 与 scoreboard 之间的 execute store ... <scale> 必须与字段契约一致。错误的 scale 不一定报错,只会制造速度爆炸、几乎不动或惯量异常。
共享状态的安全边界
以下写法危险:
mcfunction
function a:object/_get
function b:object/main # 覆盖了同一批临时字段
function a:object/_store # 把 b 的状态写进 a正确做法是让每个对象完整完成事务:
text
a: _get → all calculations → _sync → _store
b: _get → all calculations → _sync → _storeVVE 本身按命令顺序串行,但“串行”不等于“可以任意嵌套”。模拟器回调、材质回调和自定义工具尤其要明确会改写哪些共享字段。
数据调试清单
当实例没有变化:
_get的执行者是不是目标实例?- 修改发生在临时对象还是另一个 storage?
- 是否调用对应模块的
_store? _sync_motion与_store是否都需要?前者更新世界表现,后者保存计算状态。- scale 是否一致?
- 是否在中间调用了另一个覆盖临时对象的函数?