木匣子

Web/Game/Programming/Life etc.

细说精灵尺寸与 Unity3d GUI

Sprite & Texture

一般而言,精灵(Sprite)是由贴图(Texture)和矩形(Rect)构成的。矩形描述了精灵使用的是贴图中的哪个部分。

Sprite = Texture + Rect

通常一张贴图只对应一个精灵,于是矩形就和整个贴图大小相同。例如一张 512 * 512 的贴图,矩形则是 Rect(x=0, y=0, width=512, height=512);但从优化的角度来说,一张贴图可以由多个 Sprite 所共享,在加载的时候只需要一次 IO,且在渲染的时候,可以共用一个 Draw Call 。在这种情况下,在同一张贴图上使用不同的矩形框就能呈现出不同的 Sprite,例如:

  • Rect(x=0, y=256, width=256, height=256)
  • Rect(x=256, y=256, width=256, height=256)
  • Rect(x=0, y=0, width=256, height=256)
  • Rect(x=256, y=0, width=256, height=256)

以上 4 个矩形把一张 512 * 512 的贴图分割成 4 个精灵。

还有一种情况是,游戏引擎在优化资源的时候,可以把原来单张的贴图自动或手动合并成一张独立的大贴图来达到同样的优化目的。

Sprite in Scene

但是在 Unity3d 中贴图的大小是可变的,它可以针对对于不同的平台,在打包的时候导出不同尺寸和质量的贴图——那这是否会影响精灵在场景上显示的尺寸呢?

答案是否定的。Unity3d 为每个贴图提供了 PixelsPerUnit 属性,这个属性表示场景中的一个单位长度对应该贴图多少像素。例如,一个宽度为 512 的贴图,它的 PixelsPerUnit 为 100,显示到场景中即 5.12 单位长度。

sprite-inspector.png

Width_Scene = Width_Texture / PixelsPerUnit_Texture

要注意的是,检查器中 PixelsPerUnit 并非最终的 PixelsPerUnit,它会受到导出时的 Max Size 影响。一个使用 512 * 512 的贴图的精灵,但贴图的 Max Size 被设置到 256 时,它被缩小了 50%,为了保证精灵在场景里还是原来的大小,PixelsPerUnit 同样需要缩小 50% 。如果在游戏中读取上面那个在场景中为 5.12 单位长度的精灵的 PixelsPerUnit ,可以发现已经变成了 50,而不是在检查器中的 100 。

PixelsPerUnit_Texture = PixelsPerUnit_Inspector * Compressed_Factor

Sprite in Canvas

在 Unity3d 中场景的坐标系与 UI 的坐标系是完全不同的。UI 通常以像素为单位,方便设计和布局。那么是否直接在 UI 中使用贴图本身的大小来呈现图像就可以了呢?

答案还是否定的。但是从上文的介绍来看,由于贴图受优化的影响,大小是会变化的。我们最终只能知道贴图被优化后的尺寸和 PixelsPerUnit,这能保证精灵在场景中保持原来的显示尺寸,但却丢失了精灵本该有的像素尺寸信息。

有一种方案是给精灵附上原有的像素尺寸信息,这样的话就需要改精灵的组成,使得 精灵 = 贴图 + 矩形 + 尺寸信息 ,这样的话需要为大量的精灵储存额外的信息。但是从开发的习惯上来看,制作 UI 用的精灵通常都使用相同的 PixelsPerUnit ,所以其实只需要记录这个信息,应用到所有精灵上即可。于是 Unity3d 为 Canvas Scaler 提供了一个叫 ReferencePixelsPerUnit 的属性,表示 1 个场景单位长度在 Canvas 中显示为多少像素。这个属性用来将场景坐标中的精灵缩放到 UI 坐标系。这样即使精灵不知道原来的像素尺寸信息,也能按设计时的尺寸显示到 UI 上了。

Width_Canvas = Width_Texture / PixelsPerUnit_Texture * ReferencesPixelPerUnit

所以在开发 Unity3d gui 时最佳实践是为所有 UI 贴图使用相同的 PixelPerUnit,并为 Canvas Scaler 指定相同的 ReferencePixelsPerUnit 。