背景
Cocos 2d粒子相比于3d粒子来说,可供定制化的空间要小很多,如部分参数只能设置一个固定值和一个变化范围,不能使用3d粒子参数的Curve、TwoCurves等模式;部分参数(如Gravity)只能设置一个固定值,不同的粒子无法使用不同的参数,更不能在生命周期过程中发生变化;纹理只能使用一张,不能使用多张,使每个粒子使用不同的纹理,3d粒子可以通过TextureAnimationModule做到。因此很多粒子效果没法使用粒子系统来实现,只能转而使用序列帧动画。基于项目要求,本文对2d粒子系统进行了部分修改,使得支持多纹理,在js侧和c++测均有实现。
实现方案
2d粒子使用的材质和Sprite是一样的,都是builtin-2d-sprite,该材质有一个纹理参数。如果要支持多纹理,在不改变材质的情况下,就要通过图集的方式,将多张纹理放到一个texture中,通过不同的uv坐标来引用texture的不同部分。为了方便控制和兼容已有项目代码,给CParticalSystem增加以下参数:
textureType: 使用单纹理/多纹理,默认单纹理
atlas: 图集资源,每个粒子以一定规则从图集中取一份SpriteFrame
textureMode: 粒子以什么方式从图集中取SpriteFrame,支持顺序取和随机取
js侧实现详情
由于C++侧没有图集的概念,js侧无法把图集传递给C++,所以下面的实现中将图集中各个纹理的纹理坐标提取出来设置给_simulator.texsInfo属性,C++侧会注册texsInfo属性读写器,拿到这里设置的信息
class ParticleSystem {
// 设置atlas属性后触发的函数
_applyAtlas () {
if (this._atlas && this._assembler) {
this._onTextureLoaded();
if (CC_JSB && CC_NATIVERENDERER) {
// 图集信息传递给Native
const spFrames = this._atlas.getSpriteFrames();
const texsInfo = [];
spFrames.forEach(spFrame => {
texsInfo.push({
uv: spFrame.uv,
})
})
this._simulator.texsInfo = texsInfo;
}
}
},
}
Simulator.prototype.emitParticle = function (pos) {
……
// 下面部分为原有的emitParticle里新增的代码
const TextureType = cc.ParticleSystem.TextureType;
// 多纹理模式时,给每个粒子赋予一个spriteFrame
if (psys.textureType === TextureType.MULTI && psys.atlas) {
const spFrames = psys.atlas.getSpriteFrames();
if (spFrames.length > 0) {
const TextureMode = cc.ParticleSystem.TextureMode;
if (psys.textureMode === TextureMode.SEQUENCE) {
this._atlasIndex = (this._atlasIndex + 1) % spFrames.length;
particle.spriteFrame = spFrames[this._atlasIndex];
} else {
particle.spriteFrame = spFrames[Math.floor(Math.random() * spFrames.length)];
}
}
}
}
Simulator.prototype.updateUVs = function (force) {
let assembler = this.sys._assembler;
if (!assembler) {
return;
}
let buffer = assembler.getBuffer();
if (buffer) {
const TextureType = cc.ParticleSystem.TextureType;
const FLOAT_PER_PARTICLE = 4 * assembler._vfmt._bytes / 4;
let vbuf = buffer._vData;
let start = force ? 0 : this._uvFilled;
let particleCount = this.particles.length;
if (this.sys.textureType === TextureType.MULTI && this.sys.atlas) {
for (let i = start; i < particleCount; i++) {
// 使用每个粒子附带的spriteFrame的uv数据来填充vData
if (this.particles[i].spriteFrame) {
let uv = this.particles[i].spriteFrame.uv;
let offset = i * FLOAT_PER_PARTICLE;
vbuf[offset+2] = uv[0];
vbuf[offset+3] = uv[1];
vbuf[offset+7] = uv[2];
vbuf[offset+8] = uv[3];
vbuf[offset+12] = uv[4];
vbuf[offset+13] = uv[5];
vbuf[offset+17] = uv[6];
vbuf[offset+18] = uv[7];
}
}
} else if (this.sys.textureType === TextureType.SINGLE && this.sys._renderSpriteFrame) {
// 使用粒子系统的spriteFrame的uv数据来填充vData
let uv = this.sys._renderSpriteFrame.uv;
for (let i = start; i < particleCount; i++) {
let offset = i * FLOAT_PER_PARTICLE;
vbuf[offset+2] = uv[0];
vbuf[offset+3] = uv[1];
vbuf[offset+7] = uv[2];
vbuf[offset+8] = uv[3];
vbuf[offset+12] = uv[4];
vbuf[offset+13] = uv[5];
vbuf[offset+17] = uv[6];
vbuf[offset+18] = uv[7];
}
}
this._uvFilled = particleCount;
}
}
c++测实现详情
c++侧的实现整体逻辑上来说流程是类似的,jsb中增加几个属性的读写器,用于保存js侧设置的属性值。创建新的粒子结点时,从texsInfo中分配一个纹理坐标信息,填充buffer的时候uv坐标使用分配给粒子结点的。
// jsb_cocos2dx_particle_auto.cpp
bool js_register_cocos2dx_particle_ParticleSimulator(se::Object* obj) {
auto cls = se::Class::create("ParticleSimulator", obj, nullptr,
_SE(js_cocos2dx_particle_ParticleSimulator_constructor));
……
// jsb中增加3个字段的定义
cls->defineProperty("texsInfo", _SE(js_cocos2dx_particle_ParticleSimulator_get_texsInfo), _SE(js_cocos2dx_particle_ParticleSimulator_set_texsInfo));
cls->defineProperty("textureType", _SE(js_cocos2dx_particle_ParticleSimulator_get_textureType), _SE(js_cocos2dx_particle_ParticleSimulator_set_textureType));
cls->defineProperty("textureMode", _SE(js_cocos2dx_particle_ParticleSimulator_get_textureMode), _SE(js_cocos2dx_particle_ParticleSimulator_set_textureMode));
……
}
void ParticleSimulator::emitParticle(cocos2d::Vec3 &pos) {
……
// 下面部分为原有的emitParticle里新增的代码
if (textureType == TextureType::MULTI && !texsInfo.empty()) {
if (textureMode == TextureMode::SEQUENCE) {
particle.uv = texsInfo[atlasIndex].uv;
atlasIndex = (atlasIndex + 1) % texsInfo.size();
} else {
auto texIndex = rand() % texsInfo.size();
particle.uv = texsInfo[texIndex].uv;
}
} else {
particle.uv = _uv;
}
}
void ParticleSimulator::render(float dt) {
……
std::vector<float>& uv = textureType == TextureType::MULTI ? particle.uv : _uv;
// bl
vb.writeFloat32(x1 * cr - y1 * sr + x);
vb.writeFloat32(x1 * sr + y1 * cr + y);
vb.writeFloat32(uv[0]);
vb.writeFloat32(uv[1]);
vb.writeUint32(tempColor);
// br
vb.writeFloat32(x2 * cr - y1 * sr + x);
vb.writeFloat32(x2 * sr + y1 * cr + y);
vb.writeFloat32(uv[2]);
vb.writeFloat32(uv[3]);
vb.writeUint32(tempColor);
……
}
对于本文的实现有错误的地方,欢迎各位看官指正