一个控件、一次绘制、许多副本 — 每个都有自己的变换,而对
InstancedModel 来说,还有自己的动画片段、速度和相位。参考应用中的
机器人、鸟、岩石和沙滩瓦片,每一种都只是一个实例化控件。
你从
MeshInstanceTransform 派生自己的类型,把你自己的字段放上去,再把你的
对象加进 Instances。列表中的对象 就是 实例,
所以编辑它在下一帧就生效,没有复制步骤,也没有需要保持同步的句柄。
// Your own fields ride along with the engine's.
internal class Person : MeshInstanceTransform
{
internal float Yaw { get; set; }
internal string? Animation { get; set; }
}
robotField = new InstancedModel { ModelName = "Assets/3DGodotRobot.glb" };
AddControl(robotField);
for (var i = 0; i < 10; i++)
{
robotField.Instances.Add(new Person
{
PosX = -2f, PosY = 1f, PosZ = 6f + i * 3f,
Width = 1f, Height = 1f, Depth = 0.5f,
Animation = "Idle-loop"
});
}
在运行时添加和移除(包括在列表中间)会保持顺序。 没有重建步骤,也没有需要事先声明的实例数上限。
| 字段 | 说明 |
|---|---|
PosX、PosY、PosZ | 实例锚点的世界位置,即共享模板盒的中心。 |
Width、Height、Depth | 世界单位的目标尺寸。这里是普通 float,不是 float? — 0 意味着未设置,会在宿主更新期间收敛为模板尺寸。 |
Rotation | 一个 Quaternion,以锚点为枢轴。注意与 Model.Rotation 的区别,后者是绕 Y 轴的 float。 |
Enable | 跳过该副本而不把它从列表中移除。默认 true。 |
AnimationClip | 片段 索引,不是名字。0 是默认片段。 |
AnimationSpeed | 播放速率,1 为正常。每个副本稍微不同,是让人群看起来不机械的方法。 |
AnimationTimeOffset | 以秒计的相位偏移,让副本们不齐步走。 |
Selected、Highlight | 逐实例的拾取状态,所以两百个副本中的一个可以被描边。 |
ID、Name | 身份标识,当列表从数据重建时很有用。 |
动画字段由 InstancedModel 读取,并被
InstancedMesh3D 忽略,因为程序化几何没有片段。
实例用索引寻址片段。你的代码通常想用名字思考,所以 参考应用在它的实例类型上保留一个名字,每帧转换一次。这 就是整个转换,值得照抄而不是重新发明:
int ResolveAnimationClip(string? name)
{
if (string.IsNullOrWhiteSpace(name))
return 0;
var names = robotField.AnimationNames;
for (var i = 0; i < names.Count; i++)
if (names[i] == name)
return i;
return 0; // unmatched falls back to the default clip
}
// Per frame, per instance:
person.AnimationClip = ResolveAnimationClip(person.Animation);
InstancedModel.AnimationNames 在 GLB 加载后才填充,
所以在那之前循环找不到任何东西,每个副本都播放片段 0。null、空或不匹配的
名字会回退到默认片段而不是抛出,这正是资产还在加载时你
想要的行为。
几何是共享的,所以边界也是。Mesh3DBase 上的
一切在这里都有模板作用域的对偶。
| 成员 | 含义 |
|---|---|
TemplateLocalBounds | 对动画模型会被 AnimatedBoundsScale 膨胀。剔除测试的对象。 |
TemplateLocalBoundsRaw | 不膨胀。锚点和缩放计算使用的对象。 |
TemplateLocalSize | 原始模板盒的完整尺寸。 |
TemplateAnchorLocal | 原始模板盒的中心 — 与单个模型相同的中心锚点约定。 |
BuildInstanceMatrix(instance) | 一个副本的世界矩阵,构建方式与单个模型完全相同。 |
InstanceAnchorWorldOffset(instance) | 当你想钉住模板的 原点 而不是它的中心时使用的转换。 |
GetInstanceWorldBoundsRaw(instance) | 一个副本的世界边界,与实际渲染的身体匹配。 |
TryPickInstance(...)、TryPickInstanceSurface(...) | 盒级和表面级的射线测试,都会报告击中了哪个 MeshInstanceTransform。 |
Highlight 会级联
把一个全新的 Highlight 对象赋给实例化
控件,会把它复制进那一刻存在的每一个实例。嵌套赋值
— Highlight = { Style = ... } — 不会
级联,因为没有对属性发生赋值。后来加入的实例
保留它们自己的默认值,并被 ObjectPicker
解析为“继承宿主”,这正是让一个逐帧增长实例列表的面板
仍然能正确高亮的原因。
每个实例都会做一次包围球视锥测试,只有每个副本都不可见时,
整批才被跳过。阴影通道完全不做视锥剔除;它改由
CastShadows 把关。
ITransparentSortable 只存在于
Mesh3DBase 上,所以实例化批次不会与
透明的单个对象做深度排序。