15. 精灵图-让2D动图显示在3D场景中

本教程翻译自官方教程Sprites,各位也可以进入本站镜像站点查看

本文目录

  1. 精灵图Sprites
    1. 一致性精灵表管理Sprite Manager
    2. 选中精灵图
    3. 压缩精灵表管理Sprite Packed Manager
      1. 压缩格式
    4. 创建精灵图实例
    5. 精灵图动画
    6. 压缩精灵表的Playground实例
    7. 精灵贴图Sprite Map
    8. 精灵贴图的图块
      1. 保存图块
    9. 精灵贴图动画
    10. 精灵贴图的Playground实例
    11. 下一步
  2. 延伸阅读
  3. 贡献者

精灵图Sprites

    在本章我们将学习如果使用精灵图Sprites,精灵图其实就是一张2D的图片,可以有透明色背景(例如png,gif),也叫做雪碧图,大家比较熟悉的可能就是css sprite。在3D场景中,无论从什么角度看精灵图总是面对相机的,我们总是看到2D图片的全貌。

    现在精灵图常常被用来展示一些动画角色、作为粒子发射器的粒子以及模拟一个复杂的3D对象,例如树木等。可以把精灵图认为是一个简化的“实体”,它在每一次的场景绘制调用中都被呈现。

    在2d或2.5d游戏中,通常要求几千个精灵图同时作为动画被渲染,这个场景对精灵图的管理要求非常高,所以我们之后将会介绍一个特殊的系统,叫精灵贴图,来管理这些精灵图。在Babylon v4.1版本后可以使用。

    单个精灵图都被整合在一张图片中,这个图片一般被称作精灵表spritesheet或者纹理图谱texture atlas。

  • 一致性精灵表uniform spritesheet 是一张特殊的图片,在图片中精灵图有完全一样的大小,并顺序的进行排列。当你在阅读文档时,看到里面提到精灵表spritesheet这个术语,通常你可以假设这是一个 一致性精灵表。一致性精灵表由SpriteManager对象进行监听和管理。

  • 压缩精灵表packed spritesheet 可以理解为精灵图被压缩了,因为精灵图在 压缩精灵表 中会拥有不同的大小,会以最优的方式进行排列,最终结果就是压缩了精灵表的总体积。可以会有各种各样的方法来压缩精灵表,但是原理都是一样的,所以我们统称为 压缩精灵表packed spritesheet,它由SpritePackedManager对象进行管理,在Babylon v4.1版本后可以使用。

    如果要在Babylon中使用精灵图Sprite,必须使用SpriteManager和SpritePackedManager两个管理器来进行新建和管理,即使只有一个小小的精灵图,这个步骤也逃不掉。他们通过将一个精灵图Sprite的多个实例集中在一个位置,来优化GPU资源。

1-08
使用精灵图的最终效果

一致性精灵表管理Sprite Manager

    对于同样体积的精灵图,可以使用如下代码创建:

// Create a sprite manager
var spriteManagerTrees = new BABYLON.SpriteManager("treesManager", "Assets/Palm-arecaceae.png", 2000, 800, scene);

    SpriteManager的参数如下所示:

  • 名称:管理器的名称
  • 图片URL链接:大多数时候会使用背景透明的png图片。
  • 管理器的负载:相当于在管理器中可允许的最大精灵图示例数量,在上例中,我们创建了一棵树的2000个实例。
  • 单元格尺寸,就像图片大小一样,指定精灵图的大小,刚才我们也说到过, 一致性精灵表 的精灵图有完全一样的大小,这里就是指定这个大小,可以设置一个数字代表宽高一样,也可以设置一个对象单独指定宽高。(没有在这里指定也不要紧,可以通过 spriteManager.cellWidthspriteManager.cellHeight 来单独指定)
  • 场景Scene实例,决定管理器添加到哪里。

    再举一个例子,请看下面的代码片段:

var spriteManagerPlayer = new BABYLON.SpriteManager("playerManager","Assets/Player.png", 2, {width: 64, height: 64}, scene);

    上面的代码中我们只包含了2个实例,我们声明单个精灵图的大小是64x64。下面是一个精灵图的示例:

2-08-1

    每个精灵图都必须包含在一个64x64像素的正方形中,不能多也不能少。

选中精灵图

    在下面的Playground例子中,精灵图能够被选中:

Playground 选中精灵图示例

    要做到上例的效果,我们需要进行如下2步:

  • 开启精灵图的选中属性:sprite.isPickable = true;
  • 再启用spriteManager的选中属性:spriteManager.isPickable = true;

    然后我们就能通过 scene.pickSprite 方法来选中他们了:

var pickResult = scene.pickSprite(this.pointerX, this.pointerY);
if (pickResult.hit) {
    pickResult.pickedSprite.angle += 0.5;
}

    也可以使用multiPickSprite方法获取鼠标下的所有精灵图:

var pickResult = scene.multiPickSprite(this.pointerX, this.pointerY);
for (var i = 0; i < pickResult.length; i++) {
        pickResult[i].pickedSprite.angle += Math.PI / 4;
}

    默认情况下,选中操作将使用精灵图的边界矩形 bounding rectangle(由于性能原因),也就是靠近精灵图的大概一个区域就能够选中,不够精确。时候我们需要精确的选中操作怎么办?那就是设置使用精灵图的alpha值,也就是透明度,来进行选中(值来自于精灵图的纹理)。但是要注意的是,这个方法只在alpha大于0.5的情况下才能选中精灵。

Playground 使用alpha模式选中精灵图

压缩精灵表管理Sprite Packed Manager

此功能仅在Babylonv4.1版本以上生效

    就之前讨论的,压缩精灵表中的精灵图大小各不相同,所以只有一张图片还不够,我们还需要一个包含定义精灵图位置的Json文件。通常情况下,图片和Json文件应该有相同的名称,并且位于同一个文件目录下,例如:pack1.png 和 pack1.json。使用如下代码进行创建:

var spm = new BABYLON.SpritePackedManager("spm", "pack1.png", 4, scene);

    参数包括:

  • 名称:管理器的名称
  • 图片URL链接:大多数时候会使用背景透明的png图片。
  • 管理器的负载:相当于在管理器中可允许的最大精灵图示例数量,上例中最多包含4个实例
  • 场景Scene实例,决定管理器添加到哪里。

    初始化的时候,也可以直接使用现有Json对象,让它作为参数传递到管理器中。 例如:

var spm = new BABYLON.SpritePackedManager("spm", "pack1.png", 4, scene, JSONObject);

打包格式

3-pack1

    对应上述图片的Json文件,是基于TexturePacker软件生成的,对于软件中的配置我们是这样设置的:输出格式为Json(软件支持多种格式,目前Babylon只认Json),trim设置为none(自动裁剪单个精灵图多余的边缘,none为不裁剪),rotation设置为关闭(是否字段旋转精灵图,以达到更大的压缩,这里选择不旋转)。对于以上关于 压缩精灵表 的配置, TexturePacker软件输出的Json文件内容如下:

{   "frames": {

        "eye.png": {
            "frame": {"x":0,"y":148,"w":400,"h":400},
            "rotated": false,
            "trimmed": false,
            "spriteSourceSize": {"x":0,"y":0,"w":400,"h":400},
            "sourceSize": {"w":400,"h":400}
        },
        "redman.png": {
            "frame": {"x":0,"y":0,"w":55,"h":97},
            "rotated": false,
            "trimmed": false,
            "spriteSourceSize": {"x":0,"y":0,"w":55,"h":97},
            "sourceSize": {"w":55,"h":97}
            },
        "spot.png": {
            "frame": {"x":199,"y":0,"w":148,"h":148},
            "rotated": false,
            "trimmed": false,
            "spriteSourceSize": {"x":0,"y":0,"w":148,"h":148},
            "sourceSize": {"w":148,"h":148}
        },
        "triangle.png": {
            "frame": {"x":55,"y":0,"w":144,"h":72},
            "rotated": false,
            "trimmed": false,
            "spriteSourceSize": {"x":0,"y":0,"w":144,"h":72},
            "sourceSize": {"w":144,"h":72}
        }
    },
    "meta": {
        "app": "https://www.codeandweb.com/texturepacker",
        "version": "1.0",
        "image": "pack1.png",
        "format": "RGBA8888",
        "size": {"w":400,"h":548},
        "scale": "1",
        "smartupdate": "$TexturePacker:SmartUpdate:c5944b8d86d99a167f95924d4a62d5c3:3ed0ae95f00621580b477fcf2f6edb75:5d0ff2351eb79b7bb8a91bc3358bcff4$"
    }
}

     上述Json文件看起很臃肿,但其实SpritePackedManager仅仅用到了frame属性对应的数据,所以以上的Json数据我们可以简化为:

{   "frames": {
        "eye.png": {
            "frame": {"x":0,"y":148,"w":400,"h":400}
        },
        "redman.png": {
            "frame": {"x":0,"y":0,"w":55,"h":97}
            },
        "spot.png": {
            "frame": {"x":199,"y":0,"w":148,"h":148}
        },
        "triangle.png": {
            "frame": {"x":55,"y":0,"w":144,"h":72}
        }
    }
}

     但是,如果我们打算在项目中使用SpriteMap精灵贴图,那么建议使用完整格式的Json,不要对其做人为的删减操作。

创建精灵图实例

    只创建SpriteManager和SpritePackedManager两个管理器还不够,我们还需要创建一个精灵图的实例来链接到管理器。创建一个实例的代码如下所示:

var sprite = new BABYLON.Sprite("sprite", manager);

    以上代码指定了精灵表上的第一个精灵图。
    使用一致性精灵表spritesheet和SpriteManager实例的情况下,可以通过指定cellIndex索引来决定引用哪一个精灵图,cellIndex在精灵表中的计数是从左到右,自上而下的。代码示例如下:

var sprite = new BABYLON.Sprite("sprite", manager);
sprite.cellIndex = 2; //指定第二个精灵图

    使用压缩精灵表packed spritesheet和SpritePackedManager的情况下,我们能使用上面提到的cellIndex或者精灵图sprite的名称cellRef,来指定使用哪个精灵图。代码示例如下:

var sprite = new BABYLON.Sprite("sprite", manager);
sprite.cellRef = "spot.png";

    也可以更改精灵图的大小,方向或水平翻转:

sprite.size = 0.3;
sprite.angle = Math.PI/4;
sprite.invertU = -1;

    自从Babylon.js v2.1版本开始,还可以对精灵图的宽和高进行设置:

sprite.width = 0.3;
sprite.height = 0.4;

    还可以像操作其他3D物体一样操作精灵图:

sprite.position.y = -0.3;

精灵图动画

    动画是精灵图的优点之一,最简单直接的方式是使用一致性精灵表和SpriteManager。只需载入一张大图片,里面包含了动画的所有帧,这些帧就是一张接一张的精灵图。请小心的指定精灵图的大小,精确到1个像素,避免动画失真。下面的例子展示了一个动态游戏角色的精灵表:

4-08-2

    我们能够把这40个位置的精灵图串联起来播放,然后就会得到一个可以动的游戏角色,依据不同的播放起止位置,他可以实现走路、跳跃等动作。

player.playAnimation(0, 43, true, 100);

    按照以上代码,代表游戏角色的精灵图,将从0到43帧的顺序播放动画。第三个参数是指动画是否循环播放,true为循环。最后一个参数表示播放速度,值越小,动画播放速度越快。

Playground 一致性精灵表形成的动画示例

    在压缩精灵表中也可以使用playAnimation方法来播放动画,前提是:精灵图在Json文件中存储的信息正确,并且在文件中是按照动画顺序放置的。

压缩精灵表的Playground实例

精灵贴图Sprite Map

Babylon v4.1版本以后可用

    在某些特定场景下,传统的精灵表很难解决所有问题,例如我们需要同时显示几千甚至几十万个精灵图在屏幕上时,精灵贴图在制作2d游戏时很常见,在Babylon的3D场景中,有时也会用得到。接下载我们将专注于2d和2.5的游戏关卡设计。这也存在一些限制:精灵贴图SpriteMap的初始化参数指定了特定的网格,所以精灵图位置将总是静态的。

    精灵贴图SpriteMap是展示在3d平面Plane上的,能够在3d空间中进行装换。每个SpriteMap都执行一个绘制调用,并在内存中至少保留3个纹理缓冲区,具体取决于SpriteMap初始化时设置的层数。

    SpriteMap使用的Json格式与压缩精灵表相同,但是支持旋转、挤出和填充,不就之后的版本也将支持删除空白区域的功能。

    创建SpriteMap很简单:

var spriteMap = new BABYLON.SpriteMap(name, atlasJSON, spriteTexture, options, scene);

    具体参数如下所示:

  • Name: 精灵贴图SpriteMap的名称。

  • atlasJSON: 精灵贴图使用的Json图集。

  • spriteTexture: 精灵贴图使用的纹理图集。

  • options: 初始化选项。

  • scene: 加入映射的场景scene。
        上述options对象中可以传入多个参数,每一个参数都是帮助 精灵贴图SpriteMap 在内存中做好数据缓冲,然后进行更好的显示。这些选项如下所示:

  • stageSize: 让精灵贴图进行平铺显示,二维向量表示横向平铺和纵向平铺,默认:Vector2(1,1)

  • outputSize: 在世界单元中控制精灵贴图的大小,二维向量表示宽和高。默认 : Vector2(1,1)

  • outputPosition: 在世界单元中控制精灵贴图的输出位置,相当于css中的background-position,决定显示纹理的哪一个部分。 默认 : Vector3.Zero

  • outputRotation: 在世界单元中控制精灵贴图的输出角度。默认 : Vector3.Zero

  • layerCount: 精灵贴图中的分层数量,使2d游戏有层次感. 默认 : 1

  • maxAnimationFrames: 在精灵表中,动画的最大帧数。默认 : 0

  • baseTile: 要添加到精灵贴图中的初始图块,所对应的帧ID。 默认 : 0

  • flipU: 用于在帧计算后,垂直翻转精灵图sprite的显示,布尔类型。默认 : false

  • colorMultiply: 一个三维向量Vector3,它将与精灵贴图的最终颜色值相乘,从而改变精灵图的颜色。. default : Vector3(1,1,1)

    初始化后,我们能像普通的物体那样,通过引用spriteMaps.position 或 spriteMaps.rotation 来改变精灵贴图的位置和旋转。而其他选项的修改,例如stageSize、layerCount等,我们推荐先dispose释放精灵贴图SpriteMap之后,再重新创建一个新的图谱。

精灵贴图的图块

    每个精灵贴图都由许多大小相等的图块tiles组成,图谱输出的平面会被分成一个网格。
    要更改为某一个位置的图块,我们只需要使用以下方法:

spriteMap.changeTiles(layerID, tileID, frameID)

    参数说明如下:

  • layerID: 精灵贴图的目标分层索引,从0开始
  • tileID: 一个分层下的目标图块位置,是一个二维向量组Vector2
  • frameID: 更换到另一个精灵对应的帧ID
        如果要一次进行多个更改,请将所有具有相似帧ID的tileID 二维向量Vector2都添加到数组中,并将其传递给tileID参数。 否则,必须为每个图块更新缓冲区,而不是分批更新(这不是最好的方法)。
        如果需要做一个透明的单元格时,我们可以有两种方式做到:在纹理素材中找一个单像素的透明图片,或者在Json文件中创建一个空白的图块。
        以下是一个创建透明单元格的实例:
{
    "filename": "blank.png",
    "frame": {"x":221,"y":221,"w":1,"h":1},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
    "sourceSize": {"w":32,"h":32}
},

    上述的Json对象,指向到了一个纹理文件的单个像素。在着色器shader中,这个像素被放大到了32x32的正确体积,然后作为一个透明数据,展示到了图块当中。

保存图块

    当创建了一个具有正确图块tile位置的精灵贴图SpriteMap后,我们可以导出并保存这个配置,以便于将来的复用。加载“ .tilemaps”文件时,必须确保将其加载到的SpriteMap具有正确的layerCount,否则将抛出webGL错误。
保存的代码如下:

spriteMap.saveTileMaps()

载入的代码如下:

spriteMap.saveTileMaps(url)
  • url: .tilemaps文件的url地址。

精灵贴图动画

    是时候为精灵贴图总的图块添加动画了。这里有一个限制,每帧只能分配一个动画序列。不过这并没有限制我们的发挥,因为可以在Json文件中创建重复的帧引用,然后仅分配一个不同的动画给被添加的帧。与创建图块贴图相似,我们还需要“合成”这个动画。目前针对这些设置,还没有导出选项。

    这些动画具有特殊的胶卷风格,这与其他精灵系统是一样的。可以分配任何的帧作为序列中的下一帧,并且每个动画帧都有它自己的独立计时。关于术语:帧和动画帧,请不要相混淆。帧是对精灵在图集上的位置的引用,而动画帧是此时显示的特定帧,由原始帧(为其分配了动画的帧)决定。尽管所有动画在全局和每个动画帧可能都具有不同的计时,但假定它们都是在循环播放。

    要创建动画,请分配每个单元格。

spriteMap.addAnimationToTile(frameID, animationFrame, nextFrameID, animationFrameDisplayTiming, globalSpeed)
  • frameID:为精灵图的哪一个帧分配动画,是一个整数id。
  • animationFrame:分配到哪一个动画帧,为整数ID。
  • nextFrameID:动画中的下一个精灵帧,为整数ID。
  • animationFrameDisplayTiming:动画帧的播放计时,浮点数,值域在0-1。 这将在下面详细描述。
  • globalSpeed:动画播放的速度,浮点数。

    每一个精灵贴图都有一个计时器,它被输入到着色器shader中,然后显示出精灵贴图。在着色器中,它的调整时间值基本在0-1之间,在设置的帧率下循环。每个图块都会查找是否已分配了动画数据,然后检查该段时间是否在正确的动画帧上。这是通过检查动画帧显示时间值是否低于调整时间来确定的。这个时间值由一个0-1的浮点数控制,它能够加速或减慢所有动画帧的播放速度。

    幸运的是,如果多个精灵贴图SpriteMaps使用相同的数据,那么则只需要构建一次动画。 我们只要把animationMap赋值到另一个精灵贴图spriteMap就可以了,它将自动复制所有的动画。

spriteMap1.animationMap = spriteMap0.animationMap

    这听起来很复杂,所以接下来请看实例来加深一下理解。

精灵贴图的Playground实例

Playground 精灵贴图全配置案例

下一步

    学习了精灵图之后,我们就要开始在经典的3d效果中使用它了,请阅读下一章16. 发射粒子-产生梦幻的效果

延伸阅读

物体概述

贡献者

翻译/校对: Cow&Sheep
Playground: 天才
技术支持: Dushusir github

参与贡献? 联系alexads@foxmail.com