运行时骨架

Spine运行时可访问骨骼、插槽、附件等等。骨架可定制并以多种方式对环境做出响应。

世界变换

骨骼以层级方式排列,每个骨骼受父骨骼影响,一直到根骨骼。例如,当一个骨骼旋转时,所有子骨骼及其子骨骼也会旋转。要实现这点,每个骨骼都有一个本地变换,包含:

  • xy
  • rotation
  • scaleXscaleY
  • shearXshearY

从根开始,先是父骨骼,本地变换用于计算每个骨骼的世界变换。世界变换包含:

  • abcd 这是一个2x2矩阵,用于编码联合旋转、缩放和剪切骨骼及所有父骨骼一直到根。ac是X轴,bd是Y轴。
  • worldXworldY 这是骨骼的世界位置,世界坐标系统是放置根骨骼的坐标系统。

世界变换可将骨骼的本地坐标变换到世界坐标。附加到一个骨骼的网格顶点被该骨骼的世界变换所变换。生成的顶点受该骨骼及其所有父骨骼影响。此机制是Spine骨架动画系统的核心。

世界变换还可以反向变换,将世界坐标的任一点转换到该骨骼的本地坐标。

updateWorldTransform

通常不直接修改骨骼的世界变换,而是修改本地变换,再使用本地变换和父骨骼的世界变换计算此世界变换。无论何时修改了骨骼的本地变换,必须通过调用Bone updateWorldTransform来重新计算该骨骼的世界变换及其所有子节点。但是,骨骼必须按正确顺序更新,所以更常见的方法是调用Skeleton updateWorldTransform,它不仅按正确顺序更新所有骨骼,还应用所有的约束。

应用动画还总是会修改骨骼的本地变换,渲染骨架则使用的是骨骼的世界变换,所以在应用动画后渲染发生前常调用updateWorldTransform

state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
renderSkeleton(skeleton);

程序动画

可使用编程访问和调整骨骼来实现各种效果,例如,可设置骨骼的本地旋转使其指向鼠标指针进行瞄准。以编程方式放置IK目标骨骼也很方便,使IK约束可使用各种IK混合来调整各个骨骼。

通常在应用动画后再调整骨骼:

Bone torso = skeleton.findBone("torso");
...
state.update(delta);
state.apply(skeleton);
torso.rotation = ... // 计算躯干旋转
skeleton.updateWorldTransform();
renderSkeleton(skeleton);
}

如果需要用动画姿势的世界变换来调整骨骼,可在调整前调用updateWorldTransform,在本地变换变动后再次:

Bone torso = skeleton.findBone("torso");
...
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
torso.rotation = ... // 计算躯干旋转
skeleton.updateWorldTransform();
renderSkeleton(skeleton);

装配姿势

动画应用与装配姿势相对,这意味着如果调整BoneData,则会影响使用该BoneData的所有骨架的所有动画。在应用使用BoneData的动画时,应在应用动画前完成更改:

Bone torso = skeleton.findBone("torso");
...
torso.data.rotation = ... // 计算躯干旋转
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
renderSkeleton(skeleton);

骨骼位置

可使用骨骼的世界变换设置游戏元素的位置,如粒子或其他效果:

Bone rightHand = skeleton.findBone("right hand");
...
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
renderSkeleton(skeleton);
renderParticles(rightHand.worldX, rightHand.worldY);

本例应用了一个动画,计算世界变换并渲染骨架,然后使用“右手”骨骼的世界位置来绘制粒子效果。该骨骼的世界旋转和缩放也可用,例如,沿着骨骼方向射击粒子。同样地,骨骼世界变换也可用于实现UI动画,方法是使用骨骼来放置、旋转及缩放UI元素。

一般渲染

游戏工具包的运行时是一个完整的解决方案,什么都做,包括渲染。通用运行时对于游戏工具包而言功能强大,除了实际渲染外什么都做。如果使用通用运行时,只需考虑渲染。

要为通用运行时执行渲染,Skeleton类提供了一个drawOrder属性,这是一个按绘制顺序排列的插槽列表。渲染包括从各插槽获取附件,检测其类型并根据需要渲染。需要渲染的附件类型如下:

  • RegionAttachment 有4个顶点和四条边(不总是矩形)。
  • MeshAttachment 有任意数量的顶点和三角形,都由骨架数据提供,渲染器无需执行任何三角测量。

渲染伪码类似:

foreach (Slot slot in skeleton.drawOrder) {
   Attachment attachment = slot.attachment;
   AtlasRegion region;
   if (attachment is RegionAttachment) {
      attachment.computeWorldVertices(slot.bone, vertices);
      triangles = quadTriangles;
      region = attachment.rendererObject;
   } else if (attachment is MeshAttachment) {
      attachment.computeWorldVertices(slot.bone, vertices);
      triangles = attachment.triangles;
      region = attachment.rendererObject;
   }
   if (texture != null) {
      Texture texture = region.page.rendererObject;
      draw(texture, vertices, triangles, slot.data.blendMode);
   }
}

附件rendererObject属性在加载骨架数据时由AttachmentLoader设置。此伪码假设使用了Spine图集,所以rendererObject 是一个AtlasRegion。

AtlasRegion有一个AtlasPage,它有自己的rendererObject属性,在加载图集时由TextureLoader设置。Texture类代表一个具体游戏工具包类。

更换附件

插槽在任何时候可有一个附件或没有,该插槽附件可通过调用Slot attachment更换。Skeleton还有一个setAttachment方法方便按名称查找插槽及附件对象。该插槽附件将保留直至再次更换。

Skeleton skeleton = ...

// 按名称查找插槽。
Slot slot = skeleton.findSlot("slotName");
// 按名称从骨架皮肤或默认皮肤获取附件。
Attachment attachment = skeleton.getAttachment(slot.index, "attachmentName");
// 设置插槽的附件。
slot.attachment = attachment;

// 或者由骨架setAttachment方法来执行上述操作。
skeleton.setAttachment("slotName", "attachmentName");

更换附件还有其他方式。调用Skeleton setToSetupPosesetSlotsToSetupPose可更换插槽附件。动画可有更换附件的关键帧。调用Skeleton setSkin也可更换附件(见更换皮肤).

下一节: 运行时皮肤 上一节: 应用动画