有哪些网站是关于cocos2dx 加载plist游戏中plist粒子特效的

使用 Cocos2d-x 和粒子编辑器实现“天天爱消除”场景特效
转载自:/gamedev/gameengine/61.html
Cocos2d-x引擎提供了强大的粒子系统,它在模仿自然现象、物理现象及空间扭曲上具备得天独厚的优势,为我们实现一些真实自然而又带有随机性的特效(如爆炸、烟花、水流)提供了方便。尽管如此,它众多的粒子属性还是着实让人头疼。因为如果要想自己编码写出炫丽的粒子效果,这里有太多的属性需要手动设置和调节。不管是对新手还是资深的老油条程序员来说,都存在不同程度的不便性。
所以,本文将结合 “天天爱消除” 游戏场景中的粒子特效,讲解如何使用粒子编辑器编辑实现飘飘扬扬往下飞落的的粒子效果。效果如下:
粒子编辑器的使用
现在网络中普遍使用的是 Particle Designer编辑器,不过它只支持在Mac系统下使用。对于像我这样的穷屌丝来说,怎么用的起!所以本文将介绍另一个粒子编辑器particle-editor的使用方法,它的功能同样强大,而且还支持在windows系统下使用。两种编辑器的用法大同小异,都可以把编辑出的粒子效果直接导出为plist文件。
particle-editor的下载地址:
particle-editor编辑器无需安装,下载解压后即可使用,它的编辑界面如下:
菜单栏的 Samples 下为用户提供了一些常用的粒子特效,如烟花、火、流星等,你可根据需要创建不同的粒子群。
编辑器的属性:
1.IsBackgroundMove 编辑器背景图片是否滚动,如果你觉得它动来动去的很烦人,你可以把它设置为False
2.IsSaveTextureImageData 是否把图片数据编码到文件里导出,这里我建议把它设置为False
3.Scale 编辑器画布缩放比例大小
编辑器的属性只影响编辑器内的预览效果,不对保存的.plist文件产生影响。而下面的所有属性都对应Cocos2dx中粒子系统的各属性,会直接影响生成的.plist文件。
主要属性:
1.Duration 发射器生存时间,即它可以发射粒子的时间,注意这个时间和粒子生存时间不同。单位秒,-1表示永远;粒子发射结束后可点击工具栏的播放按钮再次发射
2.EmissionRate 每秒喷发的粒子数目
3.IsAutoRemoveOnFinish 粒子结束时是否自动删除
4.Mode 喷发器模式,有重力模式(Gravity)和半径模式(Radius,也叫放射模式)两种
5.TotalParticles 场景中存在的最大粒子数目,往往与EmissionRate配合起来使用
半径模式:半径模式可以使粒子以圆圈方式旋转,它也可以创造螺旋效果让粒子急速前进或后退。下列各属性只在半径模式下起作用。
1.EndRadius 结束半径
2.EndRadiusVar 结束半径变化范围,即结束半径值的范围在 (EndRadius – EndRadiusVar) 和 (EndRadius + EndRadiusVar )之间,下面类似。
3.RotatePerSecond 粒子每秒围绕起始点的旋转角度
4.RotatePerSecondVar 粒子每秒围绕起始点的旋转角度变化范围
5.StartRadius 初始半径
6.StartRadius 初始半径变化范围
重力模式:顾名思义,重力模式模拟重力,可让粒子围绕一个中心点移近或移远,它的优点是非常动态,而且移动有规则。下列各属性只在重力模式下起作用。
1.GravityX 重力X
2.GravityY 重力Y
3.RadiaAccel 粒子径向加速度,即平行于重力方向的加速度
4.RadiaAccelVar 粒子径向加速度变化范围
5.Speed 速度
6.SpeedVar 速度变化范围
7.TangentialAccel 粒子切向加速度,即垂直于重力方向的加速度
8.TangentialAccelVar 粒子切向加速度变化范围
生命属性:
1.Life 粒子生命,即粒子的生存时间
2.LifeVar 粒子生命变化范围
大小属性:
1.EndSize 粒子结束时的大小,-1表示和初始大小一致
2.EndSize 粒子结束大小的变化范围
3.StartSize 粒子的初始大小
4.StartSize 粒子初始大小的变化范围
角度属性:
1.Angle 粒子角度
2.AngleVar 粒子角度变化范围
颜色属性:
1.EndColor 粒子结束颜色
2.EndColorVar 粒子结束颜色变化范围
3.StartColor 粒子初始颜色
4.StartColorVar 粒子初始颜色变化范围
如果你不想编辑出五颜六色的粒子效果,那应该把EndColorVar,StartColorVar尽量设置为(0, 0, 0, 0)。
1.PositionType 粒子位置类型,有自由模式(Free)、相对模式(Relative)和打组模式(Grouped)三种
2.PosVarX 发射器位置的横向变化范围,烟花出现在位置相对中心位置的像素偏移
3.PosVarY 发射器位置的纵向变化范围
4.SourcePositionX 发射器原始X坐标位置
5.SourcePositionY 发射器原始Y坐标位置
Free自由模式,相对于屏幕自由,不会随粒子节点移动而移动(可产生火焰、蒸汽等效果);Relative相对模式,相对于被绑定的Node静止,粒子发射器会随Node移动而移动,可用于制作移动角色身上的特效等等;Grouped打组模式是相对于发射点的,粒子随发射器移动而移动。
自旋属性:
1.EndSpin 粒子结束自旋角度
2.EndSpinVar 粒子结束自旋角度变化范围
3.StartSpin 粒子开始自旋角度
4.StartSpinVar 粒子开始自旋角度变化范围
纹理渲染属性:
1.DestBlendFunc 目的纹理的混合模式
2.SrcBlendFunc 源纹理的混合模式
3.TextureImageData 纹理数据
4.TexturePath 纹理路径,目前只可为Debug.win32目录下文件名
Cocos2dx使用OpenGL混合原理对图形进行渲染绘制。混合就是指把两种颜色混在一起,具体一点就是把某一像素位置原来的颜色和将要画上去的颜色,通过某种方式混在一起,从而实现特殊的效果。它是一种常用的技巧,通常可以用来实现半透明,你也可以通过不同的设置得到不同的混合结果,产生一些有趣或者奇怪的图象。
这里的源纹理和目标纹理跟绘制的顺序有关,先绘制的纹理是目标纹理,后绘制的纹理理所应当的就成为了源纹理。下面具体来看看混合模式的属性值。
DestBlendFunc和SrcBlendFunc的常用值为以下几种情况:
1.GL_ZERO: 表示使用0.0作为因子,实际上相当于不使用这种颜色参与混合运算。
2.GL_ONE: 表示使用1.0作为因子,实际上相当于完全的使用了这种颜色参与混合运算。
3.GL_SRC_ALPHA:表示使用源颜色的alpha值来作为因子。
4.GL_DST_ALPHA:表示使用目标颜色的alpha值来作为因子。
5.GL_ONE_MINUS_SRC_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。
6.GL_ONE_MINUS_DST_ALPHA:表示用1.0减去目标颜色的alpha值来作为因子。
最常用的组合模式有两种:GL_ONE和GL_ONE_MINUS_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA和GL_SRC_ALPHA。
介绍完编辑器的各属性项以后,我们就可以根据需要编辑出不同效果的粒子效果了。下图是类似天天爱消除游戏中的“雪花”粒子特效。
在使用粒子编辑器编辑粒子时,应放在粒子编辑器的ParticleEditor目录下;导出plist文件时,应把资源图片和plist文件放一起。你可以点这里下载该plist
注意:这里目的纹理的混合模式设置为GL_ONE,源纹理的混合模式设置为GL_ONE_MINUS_SRC_ALPHA。
cocos2dx中加载使用plist文件
只要编辑出粒子效果,那在cocos2d-x引擎中加载plist文件就非常简单了。使用以下代码来读取粒子效果:
这里ParticleBatchNode可以引用且只可以引用1个texture(一个图片文件,一个texture图集),增加到SpriteBatchNode中的ParticleSystem都是在OpenGL ES调用绘图函数时绘制的。
如果ParticleSystem没有增加到ParticleBatchNode中,OpenGL ES会调用每个粒子系统的绘图函数,这样做效率会比较低。
好了,基本上就这些,比想象中简单多了吧。
工程下载地址:
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?主题 : 利用cocos2dx 3.2开发消灭星星(九)为游戏添加一些特效
级别: 侠客
UID: 352460
可可豆: 386 CB
威望: 329 点
在线时间: 54(时)
发自: Web Page
来源于&&分类
利用cocos2dx 3.2开发消灭星星(九)为游戏添加一些特效&&&
其实整个消灭星星开发也差不多了,为了使游戏好看些,不如为游戏添加一些特效1.星星消除有没有人发现,如果仅仅用简单的removeFromParentAndCleanUp方法来消除星星,会一下子消失,如果我们想一颗颗星星挨个地消失,不妨可以采用一下方法在StarMatrix的update函数里面加入一个参数delta,这与cocos2dx的update函数的一样表示上一帧到这一帧的间隔时间,当间隔时间达到一定时间后,消除星星。
void StarMatrix::updateStar(float delta){
&&&&for(int i = 0;i & ROW_NUM;i++){
&&&&&&&&for(int j = 0;j& COL_NUM;j++){
&&&&&&&&&&&&if(stars[j]!=nullptr){
&&&&&&&&&&&&&&&&stars[j]-&updatePosition();
&&&&&&&&&&&&}
&&&&if(needClear){
&&&&&&&&clearSumTime +=
&&&&&&&&if(clearSumTime & ONE_CLEAR_TIME){
&&&&&&&&&&&&clearMatrixOneByOne();
&&&&&&&&&&&&clearSumTime = 0;
needClear是一个flag,当游戏判断不能再继续后(看上一节),这个flag变为true,开始消除剩下的星星clearSumTime是一个累加器ONE_CLEAR_TIME就是每颗星星消除的时间2.连击加分信息一般消除一次星星都会有连击信息和加多少分的信息。我是采用一个固定在GameLayer的Label,通过设置其内容以及是否可见(visible)来实现3.连击combo效果当达到5连击,7连击,10连击或以上的时候,会弹出一个combo效果。其实这些combo标签就是一张图片,也是通过控制其属性或者runAction来实现。源码ComboEffect.h
#ifndef _COMBO_EFFECT_H_
#define _COMBO_EFFECT_H_
#include &cocos2d.h&
using namespace cocos2d;
void showComboEffect(int size,Node* node);
ComboEffect.cpp
#include &ComboEffect.h&
void showComboEffect(int size,Node* node){
&&&&if(size&5)
&&&&Size visibleSize = Director::getInstance()-&getVisibleSize();
&&&&Sprite* comboS
&&&&if(size&=10){
&&&&&&&&comboSprite = Sprite::create(&combo_3.png&);
&&&&}else if(size&=7){
&&&&&&&&comboSprite = Sprite::create(&combo_2.png&);
&&&&}else{
&&&&&&&&comboSprite = Sprite::create(&combo_1.png&);
&&&&comboSprite-&setPosition(Point(visibleSize.width/2,visibleSize.height/2));
&&&&node-&addChild(comboSprite);
&&&&Blink* blink = Blink::create(1.0f,5);
&&&&CallFunc* remove = CallFunc::create([=](){comboSprite-&removeFromParentAndCleanup(true);});
&&&&Sequence* action = Sequence::create(blink,remove,nullptr);
&&&&comboSprite-&runAction(action);
4.消除星星粒子效果消除星星时,为了实现星星爆裂散落的效果,使用了cocos2d提供的粒子特效引擎对于粒子特效不了解得可以先去cocos2dx中文社区里面找几篇文章看看。这里直接贴出代码。StarParticle.h
#ifndef _STAR_PARTICLE_H_
#define _STAR_PARTICLE_H_
#include &cocos2d.h&
using namespace cocos2d;
void showStarParticleEffect(int color,Point position,Node* node);
Color4F getColor4F(int color);
StarParticle.cpp
#include &StarParticle.h&
#include &Star.h&
void showStarParticleEffect(int color,Point position,Node* node){
&&&&ParticleExplosion* effect = ParticleExplosion::create();
&&&&effect-&setTexture(Director::getInstance()-&getTextureCache()-&addImage(&star.png&));
&&&&effect-&setTotalParticles(15);
&&&&effect-&setStartColor(getColor4F(color));
&&&&effect-&setStartColorVar(Color4F(0,0,0,1));
&&&&effect-&setEndColor(getColor4F(color));
&&&&effect-&setEndColorVar(Color4F(0,0,0,1));
&&&&effect-&setStartSize(25.0f);
&&&&effect-&setGravity(Point(0,-300));
&&&&effect-&setLife(1.0f);
&&&&effect-&setSpeed(200);
&&&&effect-&setSpeedVar(10);
&&&&effect-&setPosition(position);
&&&&node-&addChild(effect);
Color4F getColor4F(int color){
&&&&switch(color){
&&&&case Star::color::PURPLE:
&&&&&&&&return Color4F(0.74,0.30,0.99,1);
&&&&&&&&//return Color4F(189,78,253,1);
&&&&case Star::color::BLUE:
&&&&&&&&return Color4F(84/255.0f,203/255.0f,254/255.0f,1);
&&&&case Star::color::RED:
&&&&&&&&return Color4F(253/255.0f,80/255.0f,126/255.0f,1);
&&&&case Star::color::YELLOW:
&&&&&&&&return Color4F(253/255.0f,234/255.0f,84/255.0f,1);
&&&&case Star::color::GREEN:
&&&&&&&&return Color4F(132/255.0f,226/255.0f,111/255.0f,1);
&&&&return Color4F(1,1,1,0);
只要在每次消除星星后(即removeFromParentAndCleanUp后),调用这个showStarParticleEffect方法,就可以给人一种好像是星星碎成小星星散落的效果。
级别: 新手上路
可可豆: 56 CB
威望: 36 点
在线时间: 41(时)
发自: Web Page
谢谢楼主分享。
技术源于分享。
级别: 骑士
UID: 295073
可可豆: 2833 CB
威望: 1703 点
在线时间: 444(时)
发自: Web Page
我的站点:/
级别: 侠客
可可豆: 209 CB
威望: 209 点
在线时间: 114(时)
发自: Web Page
谢谢,但是连击不是消除的数量,概念错了
关注本帖(如果有新回复会站内信通知您)
发帖、回帖都会得到可观的积分奖励。
按"Ctrl+Enter"直接提交
关注CocoaChina
关注微信 每日推荐
扫一扫 关注CVP公众号
扫一扫 浏览移动版Cocos2d-x中自定义粒子系统
除了使用Cocos2d-x的11种内置粒子外,我们还可以通过创建ParticleSystemQuad对象,并设置属性实现自定义粒子系统,通过这种方式完全可以实现我们说需要的各种效果的粒子系统。使用ParticleSystemQuad自定义粒子系统至少有两种方式可以实现:代码创建和plist文件创建。代码创建所谓代码创建就是完全通过代码方式实现,其中所有的属性全部是通过程序代码设置。这要求开发人员对于这些属性值非常熟悉,而且这种方式无法预览,只能通过程序运行看效果,调整,再运行看效果,再调整,因此比较麻烦。要想实现如下图所示的下雪粒子系统,我们当然可以通过前面介绍的方式实现,但本节我们先介绍通过自定义粒子系统实现。 代码创建的下雪粒子系统,主要代码如下:bool HelloWorld::init()
if ( !Layer::init() )
Size visibleSize = Director::getInstance()->getVisibleSize();
auto bg = Sprite::create("background-1.png");
bg->setPosition(Point(visibleSize.width/2, visibleSize.height /2));
this->addChild(bg);
auto particleSystem = ParticleSystemQuad::createWithTotalParticles(200); ①
//设置雪花粒子纹理图片
particleSystem->setTexture(TextureCache::getInstance()->addImage("snow.png")); ②
//设置发射粒子的持续时间-1表示永远持续
particleSystem->setDuration(-1);
//设置粒子的重力方向
particleSystem->setGravity(Point(0,-240));
//设置角度以及偏差
particleSystem->setAngle(90);
particleSystem->setAngleVar(360);
//设置径向加速度以及偏差
particleSystem->setRadialAccel(50);
particleSystem->setRadialAccelVar(0);
//设置粒子的切向加速度以及偏差
particleSystem->setTangentialAccel(30);
particleSystem->setTangentialAccelVar(0);
// 设置粒子初始化位置偏差
particleSystem->setPosVar(Point(400,0));
//设置粒子生命期以及偏差
particleSystem->setLife(4);
particleSystem->setLifeVar(2);
//设置粒子开始时候旋转角度以及偏差
particleSystem->setStartSpin(30);
particleSystem->setStartSpinVar(60);
//设置结束时候的旋转角度以及偏差
particleSystem->setEndSpin(60);
particleSystem->setEndSpinVar(60);
//设置开始时候的颜色以及偏差
particleSystem->setStartColor(Color4F(1,1,1,1));
//设置结束时候的颜色以及偏差
particleSystem->setEndColor(Color4F(1,1,1,1));
//设置开始时候粒子大小以及偏差
particleSystem->setStartSize(30);
particleSystem->setStartSizeVar(0);
//设置粒子结束时候大小以及偏差
particleSystem->setEndSize(20.0f);
particleSystem->setEndSizeVar(0);
//设置每秒钟产生粒子的数量
particleSystem->setEmissionRate(100);
particleSystem->setPosition(Point(visibleSize.width/2, visibleSize.height + 50));
this->addChild(particleSystem);
}上述第①行代码ParticleSystemQuad::createWithTotalParticles(200)是创建ParticleSystemQuad对象,静态createWithTotalParticles函数是通过指定初始粒子数来创建粒子对象。第②行代码是指定粒子的纹理,TextureCache::getInstance()->addImage("snow.png")语句可以通过指定的纹理图片创建纹理对象Texture2D,贴图的纹理图片宽高必须是2的n次幂,大小不要超过64x64像素,在美工设计纹理图片时候,不用关注太多细节,例如:设计雪花纹理图片时候,按照雪花是有6个角的,很多人会设计为图10-7所示的样式,而事实上我们需要的图10-8所示的渐变效果的圆点。雪花图片
雪花粒子纹理图片vcD48cD4gPC9wPjxoMz5wbGlzdM7EvP60tL2oPC9oMz48cD60+sLrtLS9qLe9yr3Sqs6su6S63LbgyvTQ1KOs0qrP68rWuaS199X71eLQqcr00NTEx8rHt8ezo8CnxNG1xMrCx+mjrM7Sw8fNxrz2yrnTw1BhcnRpY2xlIERlc2lnbmVytcjBo9fTyei8xrmkvt+9+NDQy/m8+7y0y/m1w7XEyei8xqOs1eLQqbmkvt/Su7Dju+HJ+rPJ0ru49sPoyvbBo9fTtcTK9NDUwOCx7c7EvP5wbGlzdKOsyLu6882ouf3A4MvGz8LD5rXE0+++5LzT1NijujwvcD48cD5hdXRvIHBhcnRpY2xlU3lzdGVtID1QYXJ0aWNsZVN5c3RlbVF1YWQ6OmNyZWF0ZSg="snow.plist");snow.plist是描述运动的属性文件,plist文件是一种XML文件,参考代码如下:7611人阅读
cocos2d-x(46)
大规模运动的物体通常有两种方法实现
1,使用帧动画来模拟
2,粒子效果
粒子系统有CCParticleSystem类实现,CCParticleSystem实现了对粒子的控制与调度,对粒子的操作包括:
1,产生粒子
2,更新粒子状态
3,回收无效粒子
粒子系统继承自CCNode ,可以添加到其他节点之中
cocos2d-x内置了一些粒子效果如:
- CCParticleSystem(所有粒子系统的父类)
-- CCParticleSystemPoint、CCParticleSystemQuad (点粒子和方形粒子系统,都继承了CCParticleSystem的所有属性)
-- CCParticleExplosion &:爆炸粒子效果
-- CCParticleFireworks &:烟花粒子效果
-- CCParticleFire & & & & & & :火焰粒子效果
-- CCParticleFlower & & & :花束粒子效果
-- CCParticleGalaxy & & & &:星系粒子效果
-- CCParticleMeteor & & & :流星粒子效果
-- CCParticleSpiral & & & & :漩涡粒子效果
-- CCParticleSnow & & & & :雪粒子效果)
-- CCParticleSmoke & & &:烟粒子效果
-- CCParticleSun & & & & & &:太阳粒子效果
-- CCParticleRain & & & & &:雨粒子效果
ParticleSystem *particle = ParticleSnow::create();
particle-&setTexture(CCTextureCache::sharedTextureCache()-&addImage(&snow.png&));
this-&addChild(particle);
上面这段代码添加到init里面就可以看到雪花的粒子效果
首先需要知道的是粒子的发射器模式,不同发射器模式下有不同的可用参数,也因此会产生不同的效果,这个就有点像是模具,用什么模具就生成出什么样的模型产品,如果随便引用参数那是有可能会报错的:
1.重力模式-&setEmitterMode(kCCParticleModeGravity)
重力模式下可以按照设定的重力点setGravity,根据附加参数如速度、径向加速度、切向加速度……来获得对应的粒子效果。
2.半径模式-&-&setEmitterMode(kCCParticleModeRadius)
这个模式下,粒子的活动范围、动作是按圆形、半径来划分的,像上面的爆炸中心效果我就设定粒子初始产生的活动半径范围和粒子生命周期结束时活动的半径范围。
粒子还有三种关于运动位置坐标的影响模式-&setPositionType(),这里引用一段解释:
假设粒子发射器A被加入了SpriteB, &( B-&addChild(A) )
1.kCCPositionTypeFree
当B运动时,若设置Free,A发出的粒子则会出现拖尾现象。若设置其他的,则不会出现拖尾。
2.kCCPositionTypeRelative
当A相对B中坐标变动的时,若设置Relative,A发出的粒子则会出现拖尾现象。
3.kCCPositionTypeGrouped
而设置Grouped,不管什么坐标改变都不会发生拖尾现象。
总结,Free是相对于世界,Relative相对于父节点,Grouped是相对于发射器坐标。
@1——根据 plist 文件创建粒子系统:
不使用plist文件创建粒子效果
&auto size = Director::getInstance()-&getWinSize();
& & auto m_emitter = ParticleSystemQuad::createWithTotalParticles(900);
& & m_emitter-&setTexture(Director::getInstance()-&getTextureCache()-&addImage(&snow.png&));
& & //The code below we can use both in 2.x and 3.x&
& & m_emitter-&setDuration(-1);
& & m_emitter-&setGravity(Point(0, -240)); &// in Cocos2d-x-2.x CCPoint(0, -240) is used
& & m_emitter-&setAngle(90);
& & m_emitter-&setAngleVar(360);
& & m_emitter-&setRadialAccel(50);
& & m_emitter-&setRadialAccelVar(0);
& & m_emitter-&setTangentialAccel(30);
& & m_emitter-&setTangentialAccelVar(0);
& & m_emitter-&setPosition(Point(size.width / 2, size.height));
& & m_emitter-&setPosVar(Point(400, 0));
& & m_emitter-&setLife(4);
& & m_emitter-&setLifeVar(2);
& & m_emitter-&setStartSpin(30);
& & m_emitter-&setStartSpinVar(60);
& & m_emitter-&setEndSpin(60);
& & m_emitter-&setEndSpinVar(60);
& & m_emitter-&setStartColor(Color4F(255,255,255,1));
& & m_emitter-&setStartColorVar(Color4F(0,0,0,0));
& & m_emitter-&setEndColor(Color4F(255, 255, 255, 1));
& & m_emitter-&setEndColorVar(Color4F(0, 0, 0, 0));
& & m_emitter-&setStartSize(30);
& & m_emitter-&setStartSizeVar(0);
& & m_emitter-&setEndSize(20.0f);
& & m_emitter-&setEndSizeVar(0);
& & m_emitter-&setEmissionRate(100);
& & addChild(m_emitter,10);
在重力模式中,以下属性是合法的:
& &gravity&(Point)。粒子系统的重力。
& &speed&(float)。粒子们射出的速度。
& &speedVar(float)速度变量
& &tangencialAccel(float)粒子们的正切加速度
& &tangencialAccelVar(float)粒子们正切加速度的变量
& &radialAccel(float)粒子们的半径加速度。
& &radialAccelVar(float)粒子们半径加速度的变量
半径模式有下列合法的属性:
& &startRadius (a float).粒子的开始半径
& &startRadiusVar (a float). 开始半径变量
& &endRadius&(a float). 粒子的结束半径
& &endRadiusVar (a float).结束半径变量
& &rotatePerSecond (a float).围绕一个来源点的每秒旋转角度
& &rotatePerSecondVar (a float).每秒旋转角度变量
粒子们的常规属性:
startSize:粒子的初始尺寸(像素值)
startSizeVar
endSize:粒子的结束尺寸(如果你想让粒子的开始大小同结束大笑相同,就用一下kCCParticleStartSizeEqualToEndSize
endSizeVar
startColor :粒子开始颜色(ccColor4F)
startColorVar
endColor :粒子结束颜色
endColorVar
startSpin:仅用在CCParticleSystemQuad中,起始螺旋度
startSpinVar
endSpin:仅用在CCParticleSystemQuad中,结束螺旋度
endSpinVar
life粒子的生命生存时间,单位是秒
angle:粒子开始角度,float
positon:CCPoint粒子位置
positonVar
centerOfGravity :CGPoint
系统通用属性:
emissionRate duration (a float):每秒有多少粒子被发射
duration (a float):粒子系统生存时间
blendFunc (a ccBlendFunc):用于系统渲染的OpenGL的渲染函数
positionType (a tCCPositionType).:使用kCCPositionTypeFree (默认值)属性可以自由的移动粒子们。或者使用kCCPositionTypeGrouped 让粒子们一组一组的移动。
texture (a CCTexture2D).粒子的纹理
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:504570次
积分:5964
积分:5964
排名:第4399名
原创:160篇
转载:114篇
评论:24条
文章:11篇
阅读:4881
阅读:2935
文章:16篇
阅读:37233
(1)(1)(1)(1)(1)(1)(12)(9)(2)(2)(2)(3)(2)(2)(6)(4)(2)(8)(9)(21)(2)(1)(1)(3)(2)(2)(1)(3)(6)(11)(6)(4)(1)(1)(4)(3)(10)(9)(3)(2)(16)(14)(1)(3)(9)(16)(14)(10)(27)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'cocos2dx的精灵缓存在创建一组精灵帧,加载瓦片地图,普通动画的创建、骨骼动画等等都会通过plist(parameter list)文件获得需要的信息,建立器游戏中需要的类对象。本文从CCSpriteFrameCache读取plist创建精灵帧研究起。其中代码使用到了tinyXML2第三方库,以及SAX(simple api xml)。然后在从精灵帧创建精灵反向研究,plist文件数据的含义。
在分析前先介绍点东西
& & & 1、XML简介
下面是一个用于创建一组精灵帧的plist文件,里面描述了每个精灵帧的信息。
&?xml version=&1.0& encoding=&UTF-8&?&
&!DOCTYPE plist PUBLIC &-//Apple Computer//DTD PLIST 1.0//EN& &/DTDs/PropertyList-1.0.dtd&&
&plist version=&1.0&&
&key&frames&/key&
&key&1.png&/key&
&key&frame&/key&
&string&{{2,868},{110,102}}&/string&
&key&offset&/key&
&string&{1,-15}&/string&
&key&rotated&/key&
&key&sourceColorRect&/key&
&string&{{24,39},{110,102}}&/string&
&key&sourceSize&/key&
&string&{156,150}&/string&上面只贴出了部分内容,其中全两行是XML的描述信息,下面是XML的实体部分。&e attribute=value&text&/e&每个节点像这样的语法,e是一个元素,text是该元素的内容,元素可以包含属性,其中attribute是e的属性,值为value。每个元素可以嵌套的包含其它元素,上面的&dict&&/dict&就嵌套包含了&key&&/key&和&dict&&/dict&。每个元素必需又关闭的标签,通常一个元素有开与关两个标签标示,但是一个关标签也可以表示元素,上面的&false/&就标示一个元素开关在一起了。需要注意的是XML是文本可读的,所有的都是字符,解析前,那些数字、字符串、布尔值都是以字符形式存在的。我们的朴素解析就是把这些全部解析为一个个节点,这些节点的值都是字符串,然后利用其它方式转化为需要的类型,这个一根据就上下文,这里上下文是key这个元素的文本,它们的描述隐含了类型,在代码里根据这些key的字符串值就知道下面节点string中的文本具体是什么类型了。XML里只能一个根节点,用于生成一颗树。XML参考网站:/xml/xml-tutorial.html
& & & 2、tinyXML2
tinyXML2是一个XML解析库,这是tinyXML2作者的官网:/tinyxml2docs/index.html,这是tinyXML2作者的git库:/leethomason/tinyxml2 。tinyXML2只把节点解析为一颗树,每个节点存储的仍旧是原来的文本,没有提供把这些文本转化为其它类型值的接口,不过我们可以通过atoi等接口自己转换,如果你持久化了一个类对象,那么你需要自己实现转换方法根据存储的数据反向生成一个对象。游戏里的二进制也可以存在xml中的,不过没什么可阅读性,cocos用到的创建粒子节点的plist文件可以把图片与粒子描述信息一起存在plist中。
下面是一个对于上面XML文件的部分解析,cocos自带了tinyXML2,可以直接使用它,它们定义在名称空间tinyxml2。
void PlistTest::btnClick(CCObject* pSender, CCControlEvent event){
string filename = CCFileUtils::sharedFileUtils()-&fullPathForFilename(&shoe.plist&);
doc.LoadFile(filename.c_str());
XMLElement *rootElement = doc.FirstChildElement();//root plist element
XMLElement *dicts = rootElement-&FirstChildElement(&dict&)-&FirstChildElement(&dict&);
XMLElement *child = dicts-&FirstChildElement(&dict&)-&FirstChildElement();
while (child) {
printf(&%s\n&, child-&GetText());
child = child-&NextSiblingElement();
}输出如下:
{{2,868},{110,102}}
sourceColorRect
{{24,39},{110,102}}
sourceSize
{156,150}其中(null)因为&false/&这个节点没有文本值,所以为空。上面XMLDocument、XMLElement都是XMLNode的子类,FirstChildElement可以获得第一个孩子,doc.LoadFile后,doc变成了树根,所以doc.FirstChildElement()就是树根节点下的第一个孩子,这里是dict元素。FirstChildElement可以指定一个节点名,返回一个该节点名的节点。我们看到上面GetText获得的是const char*,这里没有进行更深入的解析,比如把{{2,868},{110,102}}解析为一个CCRect,把{1,-15}解析为CCPoint等等。
& & & 3、SAX(simple api xml)
上面tingXML2把plist解析为树结构T,可以通过T访问存在的任意节点。假如现在需要解析为需要的类型数据,可以遍历T,然后根据之前的key元素进行解析,对于树形结构,可以递归的编写方法解决,上面的XML遇到dict元素就递归解析,遇到非dict就进行解析,然后把遍历下个兄弟节点。cocos用到了SAX进行解析,SAX是一个高效的对xml解析的方式,它对xml进行一次扫描。上面的解析,第一次tingyXML扫描建立树结构包含字符串数据,第二次遍历XML生成实际的类型。显然这种操作要遍历两次XML了,一次XML文件,一次XML树结构,第二次应该比第一次快。SAX要解决的就是解析XML的速度问题,只进行一次解析,就得到实际类型。可能你觉得挺简单,就是扫描XML遇到某个元素做个判断,写个分支语句对所有的不同元素进行不同解析,可是XML是一个可以使用任意字符串命名元素的一种文件,怎么可能对所有元素进行解析呢。光cocos提供的标准库就用到好几种plist文件,里面就一堆不同命名的元素。有一种方法可以解决这个问题,解析方式不变,具体解析为什么交给用户自定义的处理程序去做,只要这个自定义程序实现了必须的接口就OK。常用的接口是文档进入处理、文档结束处理、元素进入处理、元素结束处理、元素文本处理、错误处理。上面这些方法作为一个接口,由客户实现,然后遍历XML的时候遇到元素的开始标签调用元素进入方法、遇到文本调用元素文本处理方法等等,这样只需要访问一次XML文件。SAX正是做这个的一个XML库。不幸的是,cocos里面的SAX具有解析时调用客户自定义的处理方法,但是没有XML解析能力,它借助tingXML2进行解析,等解析好了之后,再调用tinyXML的Accept方法,该方法遍历所有节点,然后调用SAX的处理方法,SAX的处理方法再调用代理的处理方法。cocos的SAX还是进行了2次访问操作,并没有如前一样的SAX的功能,可以自己尝试做一下这个东西,一次扫描就建立客户需要的数据结构。
4、CCSpriteFrameCache的addSpriteFramesWithFile(constchar&*pszPlist)是如何如何读取plist以及创建CCSpriteFrame的
下面是创建精灵帧以及用精灵帧创建精灵的代码:
void PlistTest::btnClickCreateSpriteBySpriteFrame(CCObject* pSender, CCControlEvent event){
CCSpriteFrameCache::sharedSpriteFrameCache()-&addSpriteFramesWithFile(&shoe.plist&);
CCSprite *sp = CCSprite::createWithSpriteFrameName(&1.png&);
sp-&setPosition(ccp(320, 500));
this-&addChild(sp);
}最终创建了一双鞋子。
addSpriteFramesWithFile的代码如下:
void CCSpriteFrameCache::addSpriteFramesWithFile(const char *pszPlist)
CCAssert(pszPlist, &plist filename should not be NULL&);
//not find pszPlist, so read plist
if (m_pLoadedFileNames-&find(pszPlist) == m_pLoadedFileNames-&end())
std::string fullPath = CCFileUtils::sharedFileUtils()-&fullPathForFilename(pszPlist);
CCDictionary *dict = CCDictionary::createWithContentsOfFileThreadSafe(fullPath.c_str());
string texturePath(&&);
CCDictionary* metadataDict = (CCDictionary*)dict-&objectForKey(&metadata&);
if (metadataDict)
// try to read
texture file name from meta data
texturePath = metadataDict-&valueForKey(&textureFileName&)-&getCString();
if (! texturePath.empty())
// build texture path relative to plist file
texturePath = CCFileUtils::sharedFileUtils()-&fullPathFromRelativeFile(texturePath.c_str(), pszPlist);
else//plist没有metadata key
// build texture path by replacing file extension
texturePath = pszP
// remove .xxx remove .plst
size_t startPos = texturePath.find_last_of(&.&);
texturePath = texturePath.erase(startPos);
// append .png
texturePath = texturePath.append(&.png&);
CCLOG(&cocos2d: CCSpriteFrameCache: Trying to use file %s as texture&, texturePath.c_str());
CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()-&addImage(texturePath.c_str());
if (pTexture)
addSpriteFramesWithDictionary(dict, pTexture);
m_pLoadedFileNames-&insert(pszPlist);
CCLOG(&cocos2d: CCSpriteFrameCache: Couldn't load texture&);
dict-&release();
}上面CCDictionary *dict = CCDictionary::createWithContentsOfFileThreadSafe(fullPath.c_str());是创建字典,代码如下:
CCDictionary* CCDictionary::createWithContentsOfFileThreadSafe(const char *pFileName)
return CCFileUtils::sharedFileUtils()-&createCCDictionaryWithContentsOfFile(pFileName);
}CCDictionary* CCFileUtils::createCCDictionaryWithContentsOfFile(const std::string& filename)
std::string fullPath = fullPathForFilename(filename.c_str());
CCDictMaker tM
return tMaker.dictionaryWithContentsOfFile(fullPath.c_str());
CCDictionary* dictionaryWithContentsOfFile(const char *pFileName)//&span style=&font-family: Arial, Helvetica, sans-&&CCDictMaker方法&/span&
m_eResultType = SAX_RESULT_DICT;
if (false == parser.init(&UTF-8&))
return NULL;
parser.setDelegator(this);
parser.parse(pFileName);
return m_pRootD
}CCDictMaker是正真用来创建CCDictionary的类。上面循序是CCDictionary创建字典的操作交给CCDictMaker,CCDictMaker在它的dictionaryWithContentsOfFile方法中创建
CCSAXParser,并把它自己设置为CCSAXParser的代理。CCSAXParser就是所谓的SAX,是服务端,CCDictMaker是客户它实现了SAX需要的处理程序,代码如下:
class CCDictMaker : public CCSAXDelegatorclass CC_DLL CCSAXDelegator
virtual void startElement(void *ctx, const char *name, const char **atts) = 0;
virtual void endElement(void *ctx, const char *name) = 0;
virtual void textHandler(void *ctx, const char *s, int len) = 0;
};上面的接口CCDictMaker已经实现了。继续追踪parser.parse(pFileName):
bool CCSAXParser::parse(const char *pszFile)
bool bRet =
unsigned long size = 0;
char* pBuffer = (char*)CCFileUtils::sharedFileUtils()-&getFileData(pszFile, &rt&, &size);
if (pBuffer != NULL && size & 0)
bRet = parse(pBuffer, size);
CC_SAFE_DELETE_ARRAY(pBuffer);
}parse(pBuffer, size);代码如下:
bool CCSAXParser::parse(const char* pXMLData, unsigned int uDataLength)
tinyxml2::XMLDocument tinyD
tinyDoc.Parse(pXMLData, uDataLength);
printer.setCCSAXParserImp(this);
return tinyDoc.Accept( &printer );
}上面代码tinyDoc.Parse(pXMLData, uDataLength);使用tingyXML解析了XML,然后把XmlSaxHander printer是SAX的处理程序,printer.setCCSAXParserImp(this);设置CCSAXParser为SAX解析处理的实现。tinyDoc.Accept( &printer )将递归的处理每个几点,并调用printer的SAX操作。printer又会调用printer.setCCSAXParserImp(this);指定的代理的接口,这个代理就是CCSAXParser,上面讲了CCDictMaker是CCSAXParser的代理,它实现了SAX需要的处理程序,所以CCSAXParser又会调用CCDictMaker的处理程序。顺序这样子tinyxml2::XMLDocument-》(XmlSaxHander-》CCSAXParser)-》CCDictMaker-》CCDictionary。XmlSaxHander与CCSAXParser其实可以合起来,这里分开来了,XmlSaxHander做了一些处理,然后调用CCDictMaker方法。tinyDoc.Accept(
&printer )代码如下:
文档节点的:
bool XMLDocument::Accept( XMLVisitor* visitor ) const
if ( visitor-&VisitEnter( *this ) ) {
for ( const XMLNode* node=FirstChild(); node=node-&NextSibling() ) {
if ( !node-&Accept( visitor ) ) {
return visitor-&VisitExit( *this );
元素节点的:
bool XMLElement::Accept( XMLVisitor* visitor ) const
if ( visitor-&VisitEnter( *this, _rootAttribute ) ) {
for ( const XMLNode* node=FirstChild(); node=node-&NextSibling() ) {
if ( !node-&Accept( visitor ) ) {
return visitor-&VisitExit( *this );
}文本节点:
bool XMLText::Accept( XMLVisitor* visitor ) const
return visitor-&Visit( *this );
}Accept是XMLNode的一个虚函数,代码如下
virtual bool Accept( XMLVisitor* visitor ) const = 0;所以tinyDoc.Accept( &printer );会根据节点类型,调用相应的Accept方法。不管哪种方法只会调用XMLVisitor中的VisitEnter Visit VisitExit。XmlSaxHander真好实现了
XMLVisitor里面的这几种方法,代码如下:
class XmlSaxHander : public tinyxml2::XMLVisitor
XmlSaxHander():m_ccsaxParserImp(0){};
virtual bool VisitEnter( const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute );
virtual bool VisitExit( const tinyxml2::XMLElement& element );
virtual bool Visit( const tinyxml2::XMLText& text );
virtual bool Visit( const tinyxml2::XMLUnknown&){ }
void setCCSAXParserImp(CCSAXParser* parser)
m_ccsaxParserImp =
CCSAXParser *m_ccsaxParserI
};bool XmlSaxHander::VisitEnter( const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute )
//CCLog(& VisitEnter %s&,element.Value());
std::vector&const char*& attsV
for( const tinyxml2::XMLAttribute* attrib = firstA attrib = attrib-&Next() )
//CCLog(&%s&, attrib-&Name());
attsVector.push_back(attrib-&Name());
//CCLog(&%s&,attrib-&Value());
attsVector.push_back(attrib-&Value());
// nullptr is used in c++11
//attsVector.push_back(nullptr);
attsVector.push_back(NULL);
CCSAXParser::startElement(m_ccsaxParserImp, (const CC_XML_CHAR *)element.Value(), (const CC_XML_CHAR **)(&attsVector[0]));
bool XmlSaxHander::VisitExit( const tinyxml2::XMLElement& element )
//CCLog(&VisitExit %s&,element.Value());
CCSAXParser::endElement(m_ccsaxParserImp, (const CC_XML_CHAR *)element.Value());
bool XmlSaxHander::Visit( const tinyxml2::XMLText& text )
//CCLog(&Visit %s&,text.Value());
CCSAXParser::textHandler(m_ccsaxParserImp, (const CC_XML_CHAR *)text.Value(), strlen(text.Value()));
}其中它又会调用CCSAXParser的startElement、endElement、textHandler三种方法。这三个代码如下:
void CCSAXParser::startElement(void *ctx, const CC_XML_CHAR *name, const CC_XML_CHAR **atts)
((CCSAXParser*)(ctx))-&m_pDelegator-&startElement(ctx, (char*)name, (const char**)atts);
void CCSAXParser::endElement(void *ctx, const CC_XML_CHAR *name)
((CCSAXParser*)(ctx))-&m_pDelegator-&endElement(ctx, (char*)name);
void CCSAXParser::textHandler(void *ctx, const CC_XML_CHAR *name, int len)
((CCSAXParser*)(ctx))-&m_pDelegator-&textHandler(ctx, (char*)name, len);
}它们也不做什么解析工作,而是交给代理去做,代理是CCDictMaker,它的三个方法代码分析如下:
void startElement(void *ctx, const char *name, const char **atts)
CC_UNUSED_PARAM(ctx);
CC_UNUSED_PARAM(atts);
std::string sName((char*)name);
if( sName == &dict& )
m_pCurDict = new CCDictionary();
if(m_eResultType == SAX_RESULT_DICT && m_pRootDict == NULL)
// Because it will call m_pCurDict-&release() later, so retain here.
m_pRootDict = m_pCurD
m_pRootDict-&retain();
m_tState = SAX_DICT;
CCSAXState preState = SAX_NONE;
if (! m_tStateStack.empty())
preState = m_tStateStack.top();
if (SAX_ARRAY == preState)
// add the dictionary into the array
m_pArray-&addObject(m_pCurDict);
else if (SAX_DICT == preState)
// add the dictionary into the pre dictionary
CCAssert(! m_tDictStack.empty(), &The state is wrong!&);
CCDictionary* pPreDict = m_tDictStack.top();
pPreDict-&setObject(m_pCurDict, m_sCurKey.c_str());
m_pCurDict-&release();
// record the dict state
m_tStateStack.push(m_tState);
m_tDictStack.push(m_pCurDict);
else if(sName == &key&)
m_tState = SAX_KEY;
else if(sName == &integer&)
m_tState = SAX_INT;
else if(sName == &real&)
m_tState = SAX_REAL;
else if(sName == &string&)
m_tState = SAX_STRING;
else if (sName == &array&)
m_tState = SAX_ARRAY;
m_pArray = new CCArray();
if (m_eResultType == SAX_RESULT_ARRAY && m_pRootArray == NULL)
m_pRootArray = m_pA
m_pRootArray-&retain();
CCSAXState preState = SAX_NONE;
if (! m_tStateStack.empty())
preState = m_tStateStack.top();
if (preState == SAX_DICT)
m_pCurDict-&setObject(m_pArray, m_sCurKey.c_str());
else if (preState == SAX_ARRAY)
CCAssert(! m_tArrayStack.empty(), &The state is wrong!&);
CCArray* pPreArray = m_tArrayStack.top();
pPreArray-&addObject(m_pArray);
m_pArray-&release();
// record the array state
m_tStateStack.push(m_tState);
m_tArrayStack.push(m_pArray);
}&pre name=&code& class=&cpp&&void textHandler(void *ctx, const char *ch, int len)
CC_UNUSED_PARAM(ctx);
if (m_tState == SAX_NONE)
CCSAXState curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();
CCString *pText = new CCString(std::string((char*)ch,0,len));
switch(m_tState)
case SAX_KEY:
m_sCurKey = pText-&getCString();
case SAX_INT:
case SAX_REAL:
case SAX_STRING:
if (curState == SAX_DICT)
CCAssert(!m_sCurKey.empty(), &key not found : &integer/real&&);
m_sCurValue.append(pText-&getCString());
pText-&release();
else { m_tState = SAX_NONE; } }
上面是当遇到一个开始标签,调用处理程序进行解析工作。SAX_ARRAY先不分析,遇到这样的plist在分析。现在分析一般类型以及字典。m_tStateStack是一个状态栈,它读到一个字典把字典状态压栈,读到基本类型它们的状态由m_tState保存,但不压栈。sName == &dict&它会先创建一个字典m_pCurDict = new CCDictionary();。if(m_eResultType == SAX_RESULT_DICT && m_pRootDict == NULL)表示当前还没有建立根字典,把m_pCurDict给m_pRootDict。preState表示之前状态,建好字典后如果之前有个字典else
if (SAX_DICT == preState),CCDictionary* pPreDict = m_tDictStack.top();pPreDict-&setObject(m_pCurDict, m_sCurKey.c_str());这几句代码意思是取出之前的字典(不出栈),然后设置为字典的一个键值对,键是怎么得到的,等下分析。其它的开始标签只改变状态m_tState。开始标签的状态决定了怎么读取它的文本,代码如下:
void textHandler(void *ctx, const char *ch, int len)
CC_UNUSED_PARAM(ctx);
if (m_tState == SAX_NONE)
CCSAXState curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();
CCString *pText = new CCString(std::string((char*)ch,0,len));
switch(m_tState)
case SAX_KEY:
m_sCurKey = pText-&getCString();
case SAX_INT:
case SAX_REAL:
case SAX_STRING:
if (curState == SAX_DICT)
CCAssert(!m_sCurKey.empty(), &key not found : &integer/real&&);
m_sCurValue.append(pText-&getCString());
pText-&release();
一开始遇到dict开始标签&执行了m_tStateStack.push(m_tState);m_tDictStack.push(m_pCurDict);,这是栈中有一个字典状态与一个字典。上面CCSAXState
curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();当状态栈空的时候,认为当前是字典,不空就获取栈顶元素这里没考虑数组,取出来还是字典。然后根据m_tState,如果m_tState为SAX_KEY,也就是开始标签是key,那么解析出来的是字符串m_sCurKey = pText-&getCString();如果其它标签,那么m_sCurValue.append(pText-&getCString());设置m_sCurValue的值,它与key是成双成对的,所以CCAssert(!m_sCurKey.empty(),
&key not found : &integer/real&&);断言做了相应的检查,后面key/value组合会被放入字典。再看一下关闭标签做的处理:
void endElement(void *ctx, const char *name)
CC_UNUSED_PARAM(ctx);
CCSAXState curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();
std::string sName((char*)name);
if( sName == &dict& )
m_tStateStack.pop();
m_tDictStack.pop();
if ( !m_tDictStack.empty())
m_pCurDict = m_tDictStack.top();
else if (sName == &array&)
m_tStateStack.pop();
m_tArrayStack.pop();
if (! m_tArrayStack.empty())
m_pArray = m_tArrayStack.top();
else if (sName == &true&)
CCString *str = new CCString(&1&);
if (SAX_ARRAY == curState)
m_pArray-&addObject(str);
else if (SAX_DICT == curState)
m_pCurDict-&setObject(str, m_sCurKey.c_str());
str-&release();
else if (sName == &false&)
CCString *str = new CCString(&0&);
if (SAX_ARRAY == curState)
m_pArray-&addObject(str);
else if (SAX_DICT == curState)
m_pCurDict-&setObject(str, m_sCurKey.c_str());
str-&release();
else if (sName == &string& || sName == &integer& || sName == &real&)
CCString* pStrValue = new CCString(m_sCurValue);
if (SAX_ARRAY == curState)
m_pArray-&addObject(pStrValue);
else if (SAX_DICT == curState)
m_pCurDict-&setObject(pStrValue, m_sCurKey.c_str());
pStrValue-&release();
m_sCurValue.clear();
m_tState = SAX_NONE;
上面也不考虑数组,当前只考虑字典。sName ==
&dict&&表示一个字典结束了,那么栈中的的字典与字典状态会被弹出。sName ==
&true&将生成一个1字符串作为键值对的值与key以前放入字典,key怎么来的呢?开始标签处理中遇到key标签就把m_tState =
SAX_KEY,然后文本处理中m_tState&=&SAX_KEY时,m_sCurKey = pText-&getCString();
m_sCurKey此时等于key标签的文本。上面遇到结束标签的处理就是把前面得到的m_sCurKey与m_sCurValue放入字典。m_sCurValue也是文本处理得到的。
上面解析的过程就是遇到字典就创建字典并压栈,然后作为栈顶元素,后面的解析出的key/value就加在栈顶元素字典上,遇到字典结束就弹出字典。利用栈这个结构完成了解析,栈的后进先出的与字典的创建与添加键值对有相同之点。
最后CCDictionary *dict = CCDictionary::createWithContentsOfFileThreadSafe(fullPath.c_str());建立好了字典。
5、CCDictionary
后面就是利用解析获得的CCDictionary获得各种必须的参数,加载纹理,创建精灵帧。下面先分析下CCDictionary。
CCDictionary是一个字典结构,它底层使用hash表建立的,正常情况可以在O(1)得到要找的关键字对象,差的情况可能像个链表,要花费O(n)的时间完成查找。不过设计良好的
hash表查找都挺快。下面分析CCDictionary的几个函数,代码如下:
const CCString* CCDictionary::valueForKey(const std::string& key)
CCString* pStr = dynamic_cast&CCString*&(objectForKey(key));
if (pStr == NULL)
pStr = CCString::create(&&);
}CCObject* CCDictionary::objectForKey(const std::string& key)
// if dictionary wasn't initialized, return NULL directly.
if (m_eDictType == kCCDictUnknown) return NULL;
// CCDictionary only supports one kind of key, string or integer.
//This method uses string as key, therefore we should make sure that the key type of this CCDictionary is string.
CCAssert(m_eDictType == kCCDictStr, &this dictionary does not use string as key.&);
CCObject* pRetObject = NULL;
CCDictElement *pElement = NULL;
HASH_FIND_STR(m_pElements, key.c_str(), pElement);
if (pElement != NULL)
pRetObject = pElement-&m_pO
return pRetO
CCAssert(m_eDictType ==
kCCDictStr, &this dictionary does not use string as key.&);表示字典的键是字符串,它的setObject方法决定m_eDictType为kCCDictStr还是kCCDictInt,可以看下代码,这里不贴了。CCDictElement是CCDictionary使用的类。它的数据成员如下:
// The max length of string key.
MAX_KEY_LEN
// char array is needed for HASH_ADD_STR in UT_HASH.
// So it's a pain that all elements will allocate 256 bytes for this array.
m_szKey[MAX_KEY_LEN];
// hash key of string type
// hash key of integer type
CCObject* m_pO
// hash value
// makes this class hashable
friend class CCD // declare CCDictionary as friend classm_szKey保存的是字符串key,UT_hash_handle是uthash库中的结构,cocos使用到了uthash库,git地址:/troydhanson/uthash,纯c写的,用到了大量宏操作,可以研究下。提供的外部接口如下:
/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */
#define HASH_FIND_STR(head,findstr,out)
HASH_FIND(hh,head,findstr,strlen(findstr),out)
#define HASH_ADD_STR(head,strfield,add)
HASH_ADD(hh,head,strfield,strlen(add-&strfield),add)
#define HASH_REPLACE_STR(head,strfield,add,replaced)
HASH_REPLACE(hh,head,strfield,strlen(add-&strfield),add,replaced)
#define HASH_FIND_INT(head,findint,out)
HASH_FIND(hh,head,findint,sizeof(int),out)
#define HASH_ADD_INT(head,intfield,add)
HASH_ADD(hh,head,intfield,sizeof(int),add)
#define HASH_REPLACE_INT(head,intfield,add,replaced)
HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)
#define HASH_FIND_PTR(head,findptr,out)
HASH_FIND(hh,head,findptr,sizeof(void *),out)
#define HASH_ADD_PTR(head,ptrfield,add)
HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
#define HASH_REPLACE_PTR(head,ptrfield,add)
HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)
#define HASH_DEL(head,delptr)
HASH_DELETE(hh,head,delptr)HASH_FIND_STR(head,findstr,out) HASH_ADD_STR(head,strfield,add) HASH_REPLACE_STR(head,strfield,add,replaced)这些操作key为字符串的对象,head为hash表头结点,findstr、strfield为对象的key,out是查找输出的对象,add与replaced是分别要添加的对象。这些操作的都是对象指针。
CCDictElement* m_pE是CCDictionary的hash表的头结点。下面是CCDictionary添加相应key的对象的方法:
void CCDictionary::setObject(CCObject* pObject, const std::string& key)
CCAssert(key.length() & 0 && pObject != NULL, &Invalid Argument!&);
if (m_eDictType == kCCDictUnknown)
m_eDictType = kCCDictS
CCAssert(m_eDictType == kCCDictStr, &this dictionary doesn't use string as key.&);
CCDictElement *pElement = NULL;
HASH_FIND_STR(m_pElements, key.c_str(), pElement);
if (pElement == NULL)
setObjectUnSafe(pObject, key);
else if (pElement-&m_pObject != pObject)
CCObject* pTmpObj = pElement-&m_pO
pTmpObj-&retain();
removeObjectForElememt(pElement);
setObjectUnSafe(pObject, key);
pTmpObj-&release();
}上面HASH_FIND_STR(m_pElements, key.c_str(), pElement);先查找key.c_str()关键字对象存在否。存在但值跟现在对象的值(指针)不等就移除然后再添加,不存在直接添加。pTmpObj-&retain();与pTmpObj-&release();感觉没有必要,它怕赋值的对象跟现在的hash中对象一样,这个pElement-&m_pObject != pObject已经判断过了,不会出现了。如果出现的相等的,先从hash中移除对象就可能被释放了。removeObjectForElememt(pElement);代码如下:
void CCDictionary::removeObjectForElememt(CCDictElement* pElement)
if (pElement != NULL)
HASH_DEL(m_pElements, pElement);
pElement-&m_pObject-&release();
CC_SAFE_DELETE(pElement);
}用到了uthash的HASH_DEL,从hash中删除一个元素
setObjectUnSafe(pObject, key);代码如下:
void CCDictionary::setObjectUnSafe(CCObject* pObject, const std::string& key)
pObject-&retain();
CCDictElement* pElement = new CCDictElement(key.c_str(), pObject);
HASH_ADD_STR(m_pElements, m_szKey, pElement);
}用到了HASH_ADD_STR,向hash中添加一个key为m_szKey的对象pElement。
下面是CCDictionary已删除相应key对象的方法:
void CCDictionary::removeObjectForKey(const std::string& key)
if (m_eDictType == kCCDictUnknown)
CCAssert(m_eDictType == kCCDictStr, &this dictionary doesn't use string as its key&);
CCAssert(key.length() & 0, &Invalid Argument!&);
CCDictElement *pElement = NULL;
HASH_FIND_STR(m_pElements, key.c_str(), pElement);
removeObjectForElememt(pElement);
}removeObjectForElememt(pElement);}上面介绍过了。
CCDictionary的功能主要如下:
CCDictionary* CCDictionary::create() 创建一个字典
CCDictionary* CCDictionary::createWithContentsOfFile(const
char *pFileName)由plist创建CCDictionary。(用到了tinyXML2、SAX以及uthash)
bool CCDictionary::writeToFile(const
char *fullPath)把CCDictionary输出到磁盘。(借助tinyxml2逆向生成XML文档)
key为字符串的接口
void CCDictionary::setObject(CCObject* pObject,
const std::string& key)添加键值对
const CCString*
CCDictionary::valueForKey(const
std::string& key)获得值
void CCDictionary::removeObjectForKey(const
std::string& key)移除键值对
key为整数的接口
void CCDictionary::setObject(CCObject* pObject,
intptr_t key)
const CCString*
CCDictionary::valueForKey(intptr_t key)
void CCDictionary::removeObjectForKey(intptr_t key)
所有cocos中可以可以直接用XML来存储数据,它是cocos原生支持的。用XML保存一些配置信息,小游戏可以保存物品等等信息,然后可以通过CCDictionary直接解析,这个比CCUserdata好多了,CCUserdata的路径不是我们指定的,而且只能写、修改,不能删除一个键值,用的底层的接口。
6、解析CCDictionary存储的数据
plist解析为CCDictionary后,是如何提取出实际类型数据的。继续查看代码如下:
CCDictionary* metadataDict = (CCDictionary*)dict-&objectForKey(&metadata&);
if (metadataDict)
// try to read
texture file name from meta data
texturePath = metadataDict-&valueForKey(&textureFileName&)-&getCString();
}如果存在metadata元素那么从里面提取textureFileName的值,它是纹理的路径,例如1.png
下面代码是获得纹理最终路径
texturePath = CCFileUtils::sharedFileUtils()-&fullPathFromRelativeFile(texturePath.c_str(), pszPlist);
const char*
CCFileUtils::fullPathFromRelativeFile(const
char *pszFilename, const
char *pszRelativeFile)
& & std::string relativeFile = pszRelativeF
& & CCString *pRet =
CCString::create(&&);
& & pRet-&m_sString = relativeFile.substr(0, relativeFile.rfind('/')+1);
& & pRet-&m_sString +=
getNewFilename(pszFilename);
& & return pRet-&getCString();
先找出pszRelativeFile的相对路径,也就是0到/之间的路径,然后跟pszFilename拼接成一个新的路径。继续追踪如下
CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()-&addImage(texturePath.c_str());上面函数的分析在之前写的文章里有分析,这里不再分析。如果没在字典里发现metadata,那么它就用plist的路径去除.plist与.png进行拼接,得到纹理路径。
纹理创建好后会调用addSpriteFramesWithDictionary(dict, pTexture);代码如下:
else if(format == 1 || format == 2)
CCRect frame = CCRectFromString(frameDict-&valueForKey(&frame&)-&getCString());
bool rotated =
// rotation
if (format == 2)
rotated = frameDict-&valueForKey(&rotated&)-&boolValue();
CCPoint offset = CCPointFromString(frameDict-&valueForKey(&offset&)-&getCString());
CCSize sourceSize = CCSizeFromString(frameDict-&valueForKey(&sourceSize&)-&getCString());
// create frame
spriteFrame = new CCSpriteFrame();
spriteFrame-&initWithTexture(pobTexture,
sourceSize
} 由于我用的plist的的format等于2,所以就分析format等于2的情况,其它解析方式都一样。
&CCRect frame =
CCRectFromString(frameDict-&valueForKey(&frame&)-&getCString());代码如下:
CCRect CCRectFromString(const char* pszContent)
CCRect result = CCRectZ
CC_BREAK_IF(!pszContent);
std::string content = pszC
// find the first '{' and the third '}'
int nPosLeft
= content.find('{');
int nPosRight = content.find('}');
for (int i = 1; i & 3; ++i)
if (nPosRight == (int)std::string::npos)
nPosRight = content.find('}', nPosRight + 1);
CC_BREAK_IF(nPosLeft == (int)std::string::npos || nPosRight == (int)std::string::npos);
content = content.substr(nPosLeft + 1, nPosRight - nPosLeft - 1);
int nPointEnd = content.find('}');
CC_BREAK_IF(nPointEnd == (int)std::string::npos);
nPointEnd = content.find(',', nPointEnd);
CC_BREAK_IF(nPointEnd == (int)std::string::npos);
// get the point string and size string
std::string pointStr = content.substr(0, nPointEnd);
std::string sizeStr
= content.substr(nPointEnd + 1, content.length() - nPointEnd);
// split the string with ','
strArray pointI
CC_BREAK_IF(!splitWithForm(pointStr.c_str(), pointInfo));
strArray sizeI
CC_BREAK_IF(!splitWithForm(sizeStr.c_str(), sizeInfo));
float x = (float) atof(pointInfo[0].c_str());
float y = (float) atof(pointInfo[1].c_str());
float width
= (float) atof(sizeInfo[0].c_str());
float height = (float) atof(sizeInfo[1].c_str());
result = CCRectMake(x, y, width, height);
} while (0);
}上面利用c++的标准库的string对{{2,868},{110,102}}这样的字符串进行解析,最后得到一个CCRect对象。
其它几个解析的函数就不贴了,本人准备另起一篇文章专门讲解析相关的内容,并且介绍一些解析文件的方法。
本文只讲了解析plist,没讲从plist解析出数据后怎么创建对象的,这个下篇文章讲。
本文已收录于以下专栏:
相关文章推荐
void GameLayer::loadMapInfo(TMXTiledMap *map)
TMXObjectGroup *objG
objGroup = map-&objectGr...
plist文件的内容格式和xml文件的内容格式是一样的,测试的data.plist文件内容如下:
Hello World
#ifndef __HELLOW...
人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..
http://xyoung.go.nease.net/sc.cur)&&
因为服务器要用.net开发,与ipad的程序通信的文件格式选用了binary plist所以花了一下午找到了这个好用的库——iphone-plist-net。用法还是挺简单的://写入
PLIST的全称为Property List,是扩展名为.plist的属性文件,其内容为XML格式,并且以键值对的形式来标记内容。
读取plist文件的具体代码如下:
1.创建plist文件,Fi...
之前我贴过一段很简单的C#生成plist文件的代码,但是反过来如果要读取plist文件呢?有没有实现这样功能的类库呢?答案是肯定的,下午在网上找了一个iphone-plist-net库试用了一下感觉很...
在做iOS开发时,经常用到到plist文件,  那plist文件是什么呢? 它全名是:Property List,属性列表文件,它是一种用来存储串行化后的对象的文件。属性列表文件的扩展名为.plist...
分两篇博文来记录文件的上传与下载,首先来说IOS文件的上传,下篇博文说文件的下载
上传文件我们需要处理服务端与客户端,在服务端使用asp.net实现,在客户端使用Objective - C实现
  ...
他的最新文章
讲师: 许鹏
讲师:董付国
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)

我要回帖

更多关于 cocos2dx游戏开发之旅 的文章

 

随机推荐