前几天才开始学习phaser,看完教程后第一件事就是想做个动画效果,但是这才刚踏出第一步就踩了坑,就此记录一下。
创建纹理集
这里我使用TexturePacker把事先准备好的素材生成Phaser3支持的纹理集,TexturePacker使用非常简单,官网有教程,这里不多赘述
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,于是浏览器给我无情地报了个错误
我代码中并没有引用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都是用来生成拼接的字符串的,而我们需要通过这些参数让它生成纹理集数据中的帧名
于是将代码修改
const frames = this.anims.generateFrameNames('fire', {
start: 1,
end: 15,
prefix: 'fire',
suffix: '.png'
})
这样一来生成的frame分别是fire1.png ~ fire15.png,这时候打印的frames就会是一个15项的数组
完整代码
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的不同传入不同配置才是正解。