Phaser3中使用纹理集创建动画

随便看看用户

前几天才开始学习phaser,看完教程后第一件事就是想做个动画效果,但是这才刚踏出第一步就踩了坑,就此记录一下。

创建纹理集

这里我使用TexturePacker把事先准备好的素材生成Phaser3支持的纹理集,TexturePacker使用非常简单,官网有教程,这里不多赘述

通过TexturePacker获得了火焰的纹理集和一个json文件

json文件中记录的都是关于纹理集中每一帧的位置、大小等信息,看不懂没关系,所幸Phaser会帮我们解析

绘制火焰

先在preload方法加载资源

// 读取assets/sprites/fire.json文件,并命名为fire,第三个参数则是图片的路径
this.load.multiatlas('fire', 'assets/sprites/fire.json', 'assets/sprites')

然后在create方法中把已经加载完成的纹理添加到场景中

// 三个参数分别是精灵(sprite)的x坐标、y坐标和纹理的key值,对应上方multiatlas()的第一个参数
const fire = this.add.sprite(400, 100, 'fire')


这样就把纹理集的第一帧绘制到屏幕上了,非常轻松。但是一团火焰不会燃烧可不行,接下来就得让它燃烧起来。

创建动画

创建动画也很简单,在create方法中加入以下代码

const frames = this.anims.generateFrameNames('fire', {
  start: 0,
  end: 14
})
console.log(frames)
this.anims.create({
  key: 'burn',
  frames,
  repeat: -1
})

回到浏览器发现火焰还是纹丝不动,不急,先看看这些个参数是什么

  • key: 作为这个动画的名称
  • frames: 生成动画帧的数据
  • repeat: 动画播放次数,-1为无限循环

这里可以看到frames参数比较特别,它是用this.anims的另一个方法生成的,这就是我踩坑的点。
根据API文档,这个方法的两个参数分别是纹理的key和动画帧名称的配置,于是点开继续查看第二个参数的配置属性,可以看到有7个属性。

  • start: 开始
  • end: 结束
  • prefix: 前缀
  • suffix: 后缀
  • zeroPad: 补0
  • outputArray: 输出数组,可以把生成的动画帧数据push到这个数组当中
  • frames: 帧编号数组

这是我刚看到的理解,根据文档,start、end跟frames是互斥的,如果传入frames,start和end就会失效。
由于文档中描述得比较模糊(英语渣),并且都是可选参数,我图省事儿只写了start和end参数,并从0写到14,于是浏览器给我无情地报了个错误
Cannot read property 'frame' of undefined
我代码中并没有引用frame属性的,看来是Phaser的运行时错误没有很好地处理,并且这时候打印frames是一个空数组。由于国内的Phaser资源较少,经过好一番搜索也没有发现相关的解决方法,那咋办呢,看源码呗。
我在phaser/src/animations/AnimationManager.js路径下找到了generateFrameNames方法

generateFrameNames: function (key, config)
{
    var prefix = GetValue(config, 'prefix', '');
    var start = GetValue(config, 'start', 0);
    var end = GetValue(config, 'end', 0);
    var suffix = GetValue(config, 'suffix', '');
    var zeroPad = GetValue(config, 'zeroPad', 0);
    var out = GetValue(config, 'outputArray', []);
    var frames = GetValue(config, 'frames', false);

    var texture = this.textureManager.get(key);

    if (!texture)
    {
        return out;
    }

    var diff = (start < end) ? 1 : -1;

    //  Adjust because we use i !== end in the for loop
    end += diff;

    var i;
    var frame;

    if (!config)
    {
        //  Use every frame in the atlas?
        frames = texture.getFrameNames();

        for (i = 0; i < frames.length; i++)
        {
            out.push({ key: key, frame: frames[i] });
        }
    }
    else if (Array.isArray(frames))
    {
        //  Have they provided their own custom frame sequence array?
        for (i = 0; i < frames.length; i++)
        {
            frame = prefix + Pad(frames[i], zeroPad, '0', 1) + suffix;

            if (texture.has(frame))
            {
                out.push({ key: key, frame: frame });
            }
        }
    }
    else
    {
        for (i = start; i !== end; i += diff)
        {
            frame = prefix + Pad(i, zeroPad, '0', 1) + suffix;

            if (texture.has(frame))
            {
                out.push({ key: key, frame: frame });
            }
        }
    }

    return out;
}

这段代码其实不难懂,仔细揣摩一下就能看懂,返回值out是一个数组,就是前边说的“生成动画帧的数据”,经过一系列的判断之后,可以看到out中的对象的frame属性总是prefix + xxx + suffix,原来参数中的start,end,prefix,suffix,frames,zeroPad都是用来生成拼接的字符串的,而我们需要通过这些参数让它生成纹理集数据中的帧名
fire.json
于是将代码修改

const frames = this.anims.generateFrameNames('fire', {
  start: 1,
  end: 15,
  prefix: 'fire',
  suffix: '.png'
})

这样一来生成的frame分别是fire1.png ~ fire15.png,这时候打印的frames就会是一个15项的数组
image.png
然后在代码最后面加一行代码就能让火焰烧起来了fire.play('burn')
效果还不错

完整代码

new Phaser.Game({
  type: Phaser.AUTO,
  width: 750,
  height: window.innerHeight,
  scene: [
    {
      preload() {
        this.load.multiatlas('fire', 'assets/sprites/fire.json', 'assets/sprites')
      },
      create() {
        const fire = this.add.sprite(400, 100, 'fire')
        const frames = this.anims.generateFrameNames('fire', {
          start: 1,
          end: 15,
          prefix: 'fire',
          suffix: '.png'
        })
        console.log(frames)
        this.anims.create({
          key: 'burn',
          frames,
          repeat: -1
        })
        fire.play('burn')
      }
    }
  ]
})

小结

此次踩坑是因为理解错generateFrameNames()方法中的配置,一开始以为start和end是每一帧对应的index,所以才会写成0和14,但这其实是跟每一帧的filename对应,通过结合配置参数的前缀、后缀、补0生成,所以根据filename的不同传入不同配置才是正解。

阅读 2.1k
18 声望
0 粉丝
0 条评论
18 声望
0 粉丝
文章目录
宣传栏