怎样使一个IP手游的第一次CBT拖延3个月

最近,参与的IP手游项目终于第一次CBT结束了。

IP手游真是件累人的事儿,各种监修各种限制,光游戏UI资源就换了至少4个版本,各种比较创新的玩法也被一一砍掉。本来的CBT时间点是在3月份的,然而,现在是7月份了。不得不喷下这大几个月在拥有众多资源的公司中开发IP手游碰到的问题。

  • 前期规划不足

规划的问题,其实是在各种项目中多多少少都会碰到的问题。既然负责客户端这边的开发,就只说说客户端统一性规划。涉及人员当然是策划、美术、程序 缺一不可。

项目开发过程中,我们的策划的每一个案子中控件的设置方法、摆放位置(涉及美术),乃至配置表字段的设计都是乱来的,怎么舒服怎么做,导致美术出的效果也不能统一,然后美术的设计风格也是一个界面一风格。如此,直到游戏基本成型后还需要重新大幅调整资源,统一设计风格,然后程序加班加点修改,这上面浪费了大量的时间精力。如果在项目初期就注意这些统一性的把控,游戏开发应该是更顺畅的体验吧?

  • 开发迭代不规范

再者,项目开发过程中,美术、程序的迭代过程不是不断优化的过程,而是不断替换之前成果,更关键是这种替换不是推到重来,而是回到之前的之前版本模式。

糟糕的迭代导致项目资源的严重浪费,我看到程序、美术在不断加班加点做着重复的工作,感觉好痛心。我在考虑一个问题,制作人在这种情况下是不是应该停歇一下,思考下项目为什么会进入这样恶性循环中,而不是任由此类态势发展下去,最终导致项目不断延期。

当然,这种无奈的情况不能排除IP游戏中特有的情况,如若真是如此,是不是负责监修方面的人员应该做的更到位一点?

  • 产品阶段模糊

在我们游戏CBT版本功能还没有定下的时候,就开始接入QA了。然后QA各种测试后,提交了各种bug,程序再修复bug,如此反复一个多月,直到一个多月后,才发现QA测试的很多功能是要砍掉的或是程序、美术要重新修改的功能。

项目开发中也频现类似的情况:策划分配策划案到美术、程序,之后相关创作人员进行功能制作。在程序获得美术资源进行功能开发的时候,美术的第二版、第三版资源就接踵而至,导致程序浪费大量时间在前段ui修改上,而不是代码性能及功能优化上。

诸如以上产品把控的乱入,直接造成项目资源的严重浪费。所以,想拖延项目进程的,照着上面做就行了。

Lua 实现多层继承

在 Lua 中实现多层继承的方法与实现多重继承的方法类似。可以通过使用基类和派生类的方式来实现多层继承。以下是一个示例:

-- 基类
local BaseClass = {}

function BaseClass:new()
local newObj = {}
setmetatable(newObj, self)
self.__index = self
return newObj
end

function BaseClass:baseMethod()
print("BaseClass baseMethod")
end

-- 派生类1
local DerivedClass1 = BaseClass:new()

function DerivedClass1:derivedMethod1()
print("DerivedClass1 derivedMethod1")
end

-- 派生类2
local DerivedClass2 = DerivedClass1:new()

function DerivedClass2:derivedMethod2()
print("DerivedClass2 derivedMethod2")
end

在上面的示例中,`BaseClass` 是基类,`DerivedClass1` 是从 `BaseClass` 派生的第一层派生类,`DerivedClass2` 是从 `DerivedClass1` 派生的第二层派生类。

现在,你可以创建对象并调用相应的方法:

local obj = DerivedClass2:new()
obj:baseMethod() -- 调用基类方法
obj:derivedMethod1() -- 调用第一层派生类方法
obj:derivedMethod2() -- 调用第二层派生类方法

输出结果:

BaseClass baseMethod
DerivedClass1 derivedMethod1
DerivedClass2 derivedMethod2

通过创建派生类并在每一层中调用 `new()` 方法来实现多层继承。每一层的派生类都可以访问其上一层的方法和属性,从而形成了多层继承的效果。

需要注意的是,多层继承可能增加代码的复杂性,并且容易出现命名冲突。因此,在设计和使用多层继承时,要仔细考虑和管理类之间的关系和命名空间,以避免潜在的问题。

lua实现多重继承

在 Lua 中,没有直接支持多重继承的机制。然而,你可以通过一些技巧来实现类似多重继承的效果。以下是一种常见的方法,称为”混合继承”(Mixin):

1. 创建基础类和混合类:

-- 基础类
local BaseClass = {}

function BaseClass:new()
local newObj = {}
setmetatable(newObj, self)
self.__index = self
return newObj
end

-- 混合类
local MixinClass = {}

function MixinClass:doSomething()
-- 实现混合类的功能
end

2. 创建需要使用多重继承的类,并将基础类和混合类组合起来:

local MyClass = BaseClass:new()

-- 混合继承
function MyClass:includeMixin(mixin)
for k, v in pairs(mixin) do
if self[k] == nil then
self[k] = v
end
end
end

-- 使用混合类
local mixin = MixinClass
MyClass:includeMixin(mixin)

现在,`MyClass` 就具有了基础类和混合类的功能。你可以像使用普通类一样使用它,同时还能够调用混合类中定义的方法。

注意,使用混合继承时需要小心避免命名冲突。如果多个混合类中存在同名的方法或属性,可能会导致不确定的行为。在混合类中最好使用特定的命名约定或命名空间来避免冲突。

也可以使用第三方的类库或框架来实现更灵活和强大的多重继承功能,例如使用 `middleclass`、`hump.class` 等库。这些库提供了更高级的面向对象编程功能,包括多重继承和接口实现等。

2014->2015

最近跟朋友聊天,总或多或少听到提及时间过得好快啊。。之类的词语,巧的是,本人也有同样的感觉。

即将过去的一年,对于我的关键词简约了好多:生娃、手游。

生娃

我的宝贝女儿终于在这一年健健康康地来了,开始了她的精彩人生,我也算正式加入奶爸大军了。现在每天回家,还是只能跟他大眼瞪小眼,对视地傻笑,不过这样就够了。生之前总有计划着,生完后到现在,执行一片空白,突然发现这真是一种病,得改。

手游

本来想把做手游当成职业爱好,不想竟然真的做了一年多了,而且还真的做出了像样的作品了,而且成绩还算可以(对于处女作来说)。

虫虫消消乐没推广的情况下,在苹果AppStore上卖出了近3万拷贝,算是达到了自己的预期。但是在随后的版本更新中,自己引入了一个悔到肠青的bug,导致用户量和活跃用户急剧下降,希望吃了这一堑得长点智,修改bug,还是得有个完整的测试过程。

在google play上,虫虫消消乐卖了近5000拷贝,成绩不好的主要原因,我就归结为Android版本出得较晚,还有google play在大陆众所周知的处境。因为游戏中有内购功能,国内安卓平台限制个人应用集成内购功能,因此,Android版本没在国内的安卓平台上线,这应该也算Android版本不佳的原因吧。

关于吹过的牛逼

写了几年总结,发现年年都会吹一些牛逼,然后下一年终总有对不起的牛逼。

吹过的牛逼1——业余时间做2-3个手游。我其实业余只做了两个简单手游,一个虫虫消消乐,另一个也是消除类游戏,当然不是虫虫消消乐的玩法。但是,没上线,由于种种原因。。好吧,这是一种病。

吹过的牛逼2——再多看四五本书。其实是很简单的一件事,但是竟然还是没达成。现在想想,我总以太专注写代码来搪塞“为什么没时间看书”这样的理由,这也是加剧拖延症的主要原因之一,得治。

各种年度评选

今年年度人物毫无疑问是我的乖乖女儿——水灵灵的眼睛眨呀眨。

年度产品就将就发给虫虫消消乐吧。

2015

  • 看10本书;
  • 写12篇文章;
  • 努力赚钱,养家糊口;
  • 对得起吹过的牛逼;
  • 与拖延症顽抗到底。

豪华经典三消游戏:Happy Bugs — 虫虫消消乐

Happy Bugs

Download_on_the_App_Store_Badge

en_generic_rgb_wo_45

androidDown45

经典的操作,独特的玩法,​超乎寻常的体验。

本年度最吸引眼球的昆虫消除游戏!

还在玩天天爱消除,天天联萌,开心消消乐,糖果粉碎,打飞机,猜图,猜歌,猜成语,植物大战僵尸?来点新鲜的吧!

游戏简介:
小伙伴们在各自的旅程中,碰到了多种多样的可爱虫,大家用智慧捕获各式各样的虫虫,并在过程中收集了各种水果蔬菜。
游戏中,你只要把三个或三个以上相同的虫虫连在同一直线上就可以消除,你可能要同时清除虫虫粘液,经受困住虫虫的各种障碍物的挑战。
为了完成任务,你需要克服重重障碍,你也可以使用各种道具来降低难度。
别忘了去收集金币哦,有了金币,你可以购买很多有意思的道具。

IMG_0776IMG_0777IMG_0788

游戏特色:

不一样的旅程,数百个关卡,从邻居家的小院子到郊区公园、湿地公园,再到杰克的蘑菇园!
魔法小虫:游戏中,一次消除尽量多的的小虫,将产生各种各样的魔法小虫,让你在游戏中更加畅快。
小虫粘液:游戏中清理各种小虫粘液。
小虫铁网:游戏中,打掉困住小虫的铁网。
绿色阻碍:通过消除打掉小虫旁的绿色锁链。
僵尸阻碍:通过消除打掉小虫旁边自动产生的僵尸障碍,把它们从你的视线中清除!
水果、蔬菜收集:在游戏中收集目标水果和蔬菜。
7种道具,多种可爱小虫等你来获取。
更有魔法能量助你一臂之力!
上百种游戏地图,让对对碰玩出更多花样,更多乐趣!
丰富形象的帮助以及新手引导,让你一分钟轻松上手,从菜鸟变身无敌专家!
还有更多有趣的玩法在等着您!

更多惊喜:
支持高清视网膜屏幕。
全设备支持,在你的ipad,iphone,ipod touch相同体验游戏乐趣。
更多玩法即将加入,更多惊喜等你来发掘。

这就是最新体验、最具挑战的消除类游戏,超越经典之神作 – 虫虫消消乐。
还在犹豫什么? 赶紧进入挑战吧。

如果小伙伴们对 虫虫爱消除 有什么问题,请留下评论哦,保证能愉快地玩耍哦。

Switch and match colorful bugs through hundreds of level in this puzzle adventure!! Join and enjoying Happy Bugs completely FREE!

*WHY YOU’LL LOVE HAPPY BUGS*
- Easy and fun to play, but hard to be master!
- Amazing music and sound effects!
- Colorful graphics!
- Hundreds of funny levels, every of them is unique !
- FREE updates with new puzzles.

Happy bugs is completely free to play but some in-game items will require payment. Just disable in app purchases on your device if you don’t want to use this feature.

Play Happy bugs today and join millions enjoying the latest touch craze!

Plz leave comments if you have any questions.

cocos2d-x 截图保存到本地

游戏推广的一个很重要组成就是玩家分享,所以游戏截图就起到很大作用了。截图功能通过CCRenderTexture实现。

1.CCRenderTexture

CCRenderTexture是一个通用渲染对象,可以通过构建一个CCRenderTexture对象,把要渲染的东西填充进去,在渲染开始前调用call函数,调用cocos的场景的visit函数对其进行渲染,渲染结束后调用end函数。

CCRenderTexture继承于CCNode,所以可以简单地把渲染纹理添加到你的场景中,就像处理其它cocos中的节点一样,当然它还提供了保存功能,可以把渲染纹理保存为PNG或JPG格式。

在实际应用中中,只是获取渲染的纹理还不够,还要考虑不同尺寸设备的截图范围,当然,这个是针对采用kResolutionNoBorder屏幕适配方案来说,下面的粒子就是针对kResolutionNoBorder。

2.API

//创建和初始化函数
   static CCRenderTexture * create(int w ,int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat);
   static CCRenderTexture * create(int w, int h, CCTexture2DPixelFormat eFormat);
   static CCRenderTexture * create(int w, int h);
   bool initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat);
   bool initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat);

   //开始获取
   void begin();

   //开始渲染时清除之前渲染的内容
   void beginWithClear(float r, float g, float b, float a);
   void beginWithClear(float r, float g, float b, float a, float depthValue);
   void beginWithClear(float r, float g, float b, float a, float depthValue, int stencilValue);

   //结束获取
   void end();

   //清除纹理
   void clear(float r, float g, float b, float a);
   void clearDepth(float depthValue);
   void clearStencil(int stencilValue);

   //保存纹理为图片文件,可以选择JPG/PNG格式,默认是JPEG格式,成功返回真
   bool saveToFile(const char *szFilePath);
   bool saveToFile(const char *name, tCCImageFormat format);

3.示例

修改HelloWorld中结束菜单的回调函数如下:


void CTestLayer::menuCloseCallback(CCObject* pSender)
{
    SaveScreenShot();
}

//截图功能
void CTestLayer::SaveScreenShot()
{
 //获取屏幕尺寸
//CCDirector::sharedDirector()->getVisibleSize();
CCSize size =  CCDirector::sharedDirector()->getWinSize();
CCPoint origin = VisibleRect::getVisibleRect().origin;
CCSize visableSize = VisibleRect::getVisibleRect().size;

//使用屏幕尺寸初始化一个空的渲染纹理对象
CCRenderTexture* texture = CCRenderTexture::create((int)size.width, (int)size.height);

//设置位置
texture->setPosition(ccp(size.width/2, size.height/2));
CCScene* curScene = CCDirector::sharedDirector()->getRunningScene();

//开始获取
texture->begin();

//遍历场景节点对象,填充纹理到texure中
curScene->visit();

//结束获取
texture->end();

//保存为PNG图
if (origin.x == 0 && origin.y == 0) {
texture->saveToFile("screenshot.png", kCCImageFormatPNG);
return;
}

// 适配不同尺寸设备,只截取屏幕大小的尺寸,没采用kResolutionNoBorder就不用下面的处理

CCRect finalRect = CCRectMake(origin.x, origin.y, visableSize.width, visableSize.height);
CCSprite *sprite = CCSprite::createWithTexture(texture->getSprite()->getTexture(), finalRect);
sprite->setAnchorPoint(CCPoint(0, 0));
sprite->setFlipY(true);
CCRenderTexture *finalRtx = CCRenderTexture::create(visableSize.width, visableSize.height);
finalRtx->begin();
sprite->visit();
finalRtx->end();
finalRtx->saveToFile("screenshot.png", kCCImageFormatPNG);
}

写在201314

照旧,在2013的最后一天是该总结点东西。这个时候,整理那些思绪中混乱的时间碎片,都会感伤时间的飞快。先亮一下现在头壳中的那些标签云吧:游戏、微信、苹果、小米、3d打印、移动开发。这些是我今年在reader中关注比较多的词条。

可能因为有点过分专注于写代码了,这是沉溺在reader、微博、知乎比较少的一年了。简单地结个论,这是属于微信的一年,也是属于小米的一年,同样也是各种移动应用的爆发年,这行的国产企业的雄起,让我不用预测都知道2014它们会更加火热,加上易信、来往,各种支付以及各种手机厂商、各种云的乱入,2014将会上演更加精彩的业内诸企业争霸剧情,都忍不住想看了。

戏是大佬们演的好看,但还是得回到现实演好自己的屌丝泡沫剧。还是评评本屌丝泡沫剧的各种年度最佳抢镜:

年度抢镜关键词

  • 游戏:我有史以来专注在游戏行业上最多的一年,也更多地主动接触横跨端游、页游、手游各种游戏。工作上,负责away3d粒子编辑器编写,参与游戏编辑器的功能开发,这些都让我收获蛮多,业余上,写了个三消游戏,也算学习了手游开发的流程,弥补了自己移动端的不足。

年度抢镜电子产品

今年是最低碳的一年,只添置了Amazon的kindle paperwhite,虽然仍有很多很酷的产品上线,但是貌似没促动我消费神经的产品?好吧,我不会告诉你其实是我没钱。。

  • mbp:这是前年的东东了,但是其实人家还没过时呢。装上osx10.9,各种崭新如初,写代码什么的各种方便,伦家unix系的就是霸道就是逼格高。额低调下,其实挺卡有时候,有钱了咱也升级。
  • ipad2:这是去年的,但是人家也没过时呢。升级ios7,依然各种顺畅崭新如初,各种场合看片游戏利器,加之百度网盘乱入,妈妈再也不用担心我看片不方便了。
  • kindle Paperwhite:其实这是老婆建议买的,但是万万没想到,因为翻页的拖延及闪烁,用惯了ipad的年轻人用起来不习惯,买后几乎是我在用,真是不幸中的万幸。借助它,我算是多看了点书(但是貌似还没把本钱看回来),而且看网页文章各种爽,还是得赞下amazon的send to kindle插件。

年度抢镜互联网产品

本来想分开pc和移动的,但是界限太模糊,不好操作。随着移动互联网的大势入侵,pc互联网很多都加大了移动互联网的打通力度,静候2014各种大战。

  • 微信:这个应该没有争议,因为它和新浪微博在国内大有统治之势头。在得入口者得天下这句话的号召下,如今业内可谓群雄并起,今年的第四季度易信、来往的突袭可见一斑。
  • 支付宝:新推出的余额宝让大佬们看到了网上支付可以这样玩期货,余额宝的成功将挑起更多的网上期货大战,诸如度娘的百度理财,加之微信微支付的乱入。唉,一想到这些战争就开心。
  • 百度网盘:打通web、pc、移动端的类dropbox,提供了2T空间让社会各界宅男乐开了花,有了2T的种子,他们的妈再也不用担心他们了吗?
  • 小米:暂且将小米归到互联网产品吧,因为人家不是说了互联网思维做产品嘛。不说什么,就冲3年弄出个100亿美金的公司,雷牛了。

年度抢镜业余作品

  • 三消游戏:做的一个手游(其实还做了个消消零食,参考消除星星来着),程序、美工、音效等等都自己包了,好累啊。
  • 微洛克:腾讯微博粉丝管理工具,帮微博运营的同学写的一个小工具。
  • 微信自动回复公共账号:微信号:musictee,叫我爱查歌词,提供歌词查询功能。

年度抢镜旅游

说到旅游,得说今年是悲催的一年,也就省内溜溜。

  • 福鼎太姥山:跟老婆和她得小伙伴们去的,因做为拎包员得以跟随。其实挺漂亮的那景色,要说也不比武夷山差多少,就是开发没跟上。

年度抢镜户外活动

  • 全程马拉松:参加了厦门国际马拉松,具体跑了多少就不说了,反正咱是在7个小时内,拿了牌的呢。好吧,还是说说吧,6个多小时,但是我是有理由的,中途吃了顿肯德基来着,不然,不然,走慢点也是6个多小时。说实话吧,都走那么慢了,脚还是酸了一周多的。

年度抢镜人物

  • 我老婆,还有我们的宝宝,要好好健康成长。
  • 老爸老妈,他们在变老,依旧奋战一线,做为儿子很是惭愧。

展望2014

  • 业余时间做2-3个手游;
  • 再多看四五本书;
  • 用好时间规划软件;
  • 努力赚钱,养家糊口。

away3d传送门特效

away3d 4.0加入特效系统后,做比较炫的特效已经不是什么难事,away3d官方已经基于内置的particle system做了几个挺炫的粒子特效,需要的可以查看github中实例源码。这个传送门特效且作为away3d 粒子系统练手的的另一个实例。

简单说下,这个特效由三个独立的部分组成,转动的符文圆盘、向上发射的半透明光及不断从符文盘向上浮动的颗粒。效果图如下:

Snip201303_3

废话不说,直接上代码解释。

首先,必须准备三个部分的纹理和材质了。分别是符文圆盘、光辉、浮动粒子的纹理,在本教程最后提供的源码里有相应的纹理。

[Embed(source="../assets/pan.png")]
public  var panTexture:Class;

[Embed(source="../assets/light.png")]
public  var lightTexture:Class;

[Embed(source="../assets/blueball.png")]
public  var blueballTexture:Class;

材质拆分成五个部分,分别对应五种不同的粒子mesh。简单说为什么有五种,因为:1圆盘+3光辉+1浮动粒子串。粒子对应的五种material、mesh、animator等的声明如下,它们之间的关系我们将在后面代码解释。

//materials
private var matPan:TextureMaterial;
private var matLight1:TextureMaterial;
private var matLight2:TextureMaterial;
private var matLight3:TextureMaterial;
private var matBall:TextureMaterial;

//particle objects
private var particleAnimationSet1:ParticleAnimationSet;
private var particleAnimationSet2:ParticleAnimationSet;
private var particleAnimationSet3:ParticleAnimationSet;
private var particleAnimationSet4:ParticleAnimationSet;
private var particleAnimationSet5:ParticleAnimationSet;
private var particleGeometry1:ParticleGeometry;
private var particleGeometry2:ParticleGeometry;
private var particleGeometry3:ParticleGeometry;
private var particleGeometry4:ParticleGeometry;
private var particleGeometry5:ParticleGeometry;

// scene objects
private var particleMesh1:Mesh;
private var particleMesh2:Mesh;
private var particleMesh3:Mesh;
private var particleMesh4:Mesh;
private var particleMesh5:Mesh;
private var animator1:ParticleAnimator;
private var animator2:ParticleAnimator;
private var animator3:ParticleAnimator;
private var animator4:ParticleAnimator;
private var animator5:ParticleAnimator;

接着就是初始化引擎了,为了便于理解,采用跟官方一样的方法,顺便贴出代码。

//engine variables
private var scene:Scene3D;
private var camera:Camera3D;
private var view:View3D;
private var cameraController:HoverController;

//signature variables
private var Signature:Sprite;
private var SignatureBitmap:Bitmap;

//navigation variables
private var _move:Boolean = false;
private var _lastPanAngle:Number;
private var _lastTiltAngle:Number;
private var _lastMouseX:Number;
private var _lastMouseY:Number;

/**
 * Initialise the engine
 */
private function initEngine():void
{
	stage.scaleMode = StageScaleMode.NO_SCALE;
	stage.align = StageAlign.TOP_LEFT;

	scene = new Scene3D();
	camera = new Camera3D();

	view = new View3D();
	view.scene = scene;
	view.camera = camera;

	//setup controller to be used on the camera
	cameraController = new HoverController(camera, null, 255, 50, 300, 5);
	view.addSourceURL("srcview/index.html");
	addChild(view);

	//add signature
	Signature = Sprite(new SignatureSwf());
	SignatureBitmap = new Bitmap(new BitmapData(Signature.width, Signature.height, true, 0));
	stage.quality = StageQuality.HIGH;
	SignatureBitmap.bitmapData.draw(Signature);
	stage.quality = StageQuality.LOW;
	addChild(SignatureBitmap);

	addChild(new AwayStats(view));
}

引擎准备完后就是进入主题了,开始制作各种粒子。away3d中的生成粒子有几个步骤:

1、定义粒子需要的几何体集合,比如该教程中传送门的转动圆盘需要1个plane, 浮动的粒子需要50个plane来表现;

2、根据几何体几何,生成粒子需要的ParticleGeometry;

3、根据ParticleGeometry、Material,生成粒子mesh;

4、定义particleAnimationSet。粒子中的动作节点分为局部静态动作、局部动态动作还有全局动作,一个particleAnimationSet可以添加以上多种类型的不同动作节点;进一步了解动作细节,点击查看官方文档

5、根据particleAnimationSet定义ParticleAnimator,并把ParticleAnimator装到粒子mesh上。

6、设置粒子的start time,duration time, delay time及局部动作属性。

文字太抽象,下面是直观的代码:


		/**
		 * 初始化粒子材质
		 */
		private function initMaterials():void
		{
			matPan = new TextureMaterial(Cast.bitmapTexture(panTexture));
			matBall = new TextureMaterial(Cast.bitmapTexture(blueballTexture));
			matLight1 = new TextureMaterial(Cast.bitmapTexture(lightTexture));
			matLight2 = new TextureMaterial(Cast.bitmapTexture(lightTexture));
			matLight3 = new TextureMaterial(Cast.bitmapTexture(lightTexture));

			matPan.bothSides = true;
			matPan.smooth = true;
			matPan.repeat = true;
			matPan.alphaBlending = true;
			matPan.specularColor = 0xFF1161D9;
			matPan.ambientColor = 0xFF1161D9;

			matLight1.bothSides = true;
			matLight1.smooth = true;
			matLight1.alphaBlending = true;

			matLight2.bothSides = true;
			matLight2.smooth = true;
			matLight2.alphaBlending = true;

			matLight3.bothSides = true;
			matLight3.smooth = true;
			matLight3.alphaBlending = true;

			matBall.bothSides = true;
			matBall.repeat = true;
			matBall.smooth = true;
			matBall.alphaBlending = true;
		}

		/**
		 * 初始化粒子
		 */
		private function initParticles():void
		{
			// 设置所有粒子的particleGeometry
			var ball:Geometry = new PlaneGeometry(3,3,1,1,false);
			var light1:Geometry = new CylinderGeometry(50,30,200,16,1,false,false,true);
			var light2:Geometry = new CylinderGeometry(55,30,180,16,1,false,false,true);
			var light3:Geometry = new CylinderGeometry(60,30,160,16,1,false,false,true);
			var pan:Geometry = new PlaneGeometry(65,65,1,1);

			var ballGeoSet:Vector.<Geometry> = new Vector.<Geometry>();
			var light1GeoSet:Vector.<Geometry> = new Vector.<Geometry>();
			var light2GeoSet:Vector.<Geometry> = new Vector.<Geometry>();
			var light3GeoSet:Vector.<Geometry> = new Vector.<Geometry>();
			var panGeoSet:Vector.<Geometry> = new Vector.<Geometry>();

			// particle ball
			for (var i:int = 0; i < 40; i++) {
				ballGeoSet.push(ball);
			}
			particleGeometry1 = ParticleGeometryHelper.generateGeometry(ballGeoSet);

			particleAnimationSet1 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet1.addAnimation(new ParticleBillboardNode());
			particleAnimationSet1.addAnimation(new ParticleVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,30,0)));
			particleAnimationSet1.addAnimation(new ParticleColorNode(ParticlePropertiesMode.GLOBAL,true,true,false,false,new ColorTransform(), new ColorTransform(1,1,1,0)));
			particleAnimationSet1.addAnimation(new ParticlePositionNode(ParticlePropertiesMode.LOCAL_STATIC));
			particleAnimationSet1.addAnimation(new ParticleOscillatorNode(ParticlePropertiesMode.LOCAL_STATIC));
			particleAnimationSet1.addAnimation(new ParticleVelocityNode(ParticlePropertiesMode.LOCAL_STATIC));
			particleAnimationSet1.initParticleFunc = initBallParticleProperties;

			// light 1
			light1GeoSet.push(light1);
			particleGeometry2 = ParticleGeometryHelper.generateGeometry(light1GeoSet);
			particleAnimationSet2 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet2.addAnimation(new ParticlePositionNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,100,0)));
			particleAnimationSet2.addAnimation(new ParticleRotationalVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,1,0,25)));
			particleAnimationSet2.addAnimation(new ParticleColorNode(ParticlePropertiesMode.GLOBAL,true,true,false, false,new ColorTransform(1,1,1,0.2), new ColorTransform(1,1,1,0.2)));
			particleAnimationSet2.initParticleFunc = initLightParticleProperties;

			// light 2
			light2GeoSet.push(light2);
			particleGeometry3 = ParticleGeometryHelper.generateGeometry(light2GeoSet);
			particleAnimationSet3 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet3.addAnimation(new ParticlePositionNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,90,0)));
			particleAnimationSet3.addAnimation(new ParticleRotationalVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,1,0,25)));
			particleAnimationSet3.addAnimation(new ParticleColorNode(ParticlePropertiesMode.GLOBAL,true,true,false, false,new ColorTransform(1,1,1,0.2), new ColorTransform(1,1,1,0.2)));
			particleAnimationSet3.initParticleFunc = initLightParticleProperties;

			// light 3
			light3GeoSet.push(light3);
			particleGeometry4 = ParticleGeometryHelper.generateGeometry(light3GeoSet);
			particleAnimationSet4 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet4.addAnimation(new ParticlePositionNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,90,0)));
			particleAnimationSet4.addAnimation(new ParticleRotationalVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,1,0,25)));
			particleAnimationSet4.addAnimation(new ParticleColorNode(ParticlePropertiesMode.GLOBAL,true,true,false, false,new ColorTransform(1,1,1,0.2), new ColorTransform(1,1,1,0.2)));
			particleAnimationSet4.initParticleFunc = initLightParticleProperties;

			// pan
			panGeoSet.push(pan);
			particleGeometry5 = ParticleGeometryHelper.generateGeometry(panGeoSet);
			particleAnimationSet5 = new ParticleAnimationSet(true, true, false);
			particleAnimationSet5.addAnimation(new ParticleRotationalVelocityNode(ParticlePropertiesMode.GLOBAL, new Vector3D(0,1,0,8)));
			particleAnimationSet5.initParticleFunc = initPanParticleProperties;

		}

		/**
		 * 初始化场景
		 */
		private function initObjects():void
		{
			particleMesh1 = new Mesh(particleGeometry1, matBall);
			particleMesh2 = new Mesh(particleGeometry2, matLight1);
			particleMesh3 = new Mesh(particleGeometry3, matLight2);
			particleMesh4 = new Mesh(particleGeometry4, matLight3);
			particleMesh5 = new Mesh(particleGeometry5, matPan);
			scene.addChild(particleMesh1);
			scene.addChild(particleMesh2);
			scene.addChild(particleMesh3);
			scene.addChild(particleMesh4);
			scene.addChild(particleMesh5);

			animator1 = new ParticleAnimator(particleAnimationSet1);
			animator2 = new ParticleAnimator(particleAnimationSet2);
			animator3 = new ParticleAnimator(particleAnimationSet3);
			animator4 = new ParticleAnimator(particleAnimationSet4);
			animator5 = new ParticleAnimator(particleAnimationSet5);

			particleMesh1.animator = animator1;
			particleMesh2.animator = animator2;
			particleMesh3.animator = animator3;
			particleMesh4.animator = animator4;
			particleMesh5.animator = animator5;

			animator1.start();
			animator2.start();
			animator3.start();
			animator4.start();
			animator5.start();
		}

		/**
		 * 初始化浮动粒子属性
		 */
		private function initBallParticleProperties(properties:ParticleProperties):void
		{
			properties.startTime = Math.random()*4.1;
			properties.duration = 3;
			var degree1:Number = Math.random() * Math.PI ;
			var degree2:Number = Math.random() * Math.PI;
			var r1:Number = Math.random()*15;
			var r2:Number = Math.random()*20 +10;
			properties[ParticlePositionNode.POSITION_VECTOR3D] = new Vector3D(r1*Math.sin(degree1), 0 , r1*Math.cos(degree1));
			properties[ParticleOscillatorNode.OSCILLATOR_VECTOR3D] = new Vector3D(r2*Math.sin(degree2), 0 , r2*Math.cos(degree2), Math.random()*4+2);
			properties[ParticleVelocityNode.VELOCITY_VECTOR3D] =  new Vector3D(0,Math.random()*100,0);
		}

		/**
		 * 初始化光辉属性
		 */
		private function initLightParticleProperties(properties:ParticleProperties):void
		{
			properties.startTime = 1;
			properties.duration = 10;
		}

		/**
		 * 初始化符文转盘属性
		 */
		private function initPanParticleProperties(properties:ParticleProperties):void
		{
			properties.startTime = 1;
			properties.duration = 1;
		}

猛击下载源码

Away3d粒子系统学习与深入I

最近在做基于away3D引擎的粒子编辑器,因此,有了这部分文章作为mark。

Away3D从4.1alpha之后开始加入了粒子系统功能,再次感谢国内牛人LiaoCheng,是他向away3d贡献的这部分功能,大家鼓掌。

简介

所谓粒子系统是表示三维计算机图形学中模拟一些特定的模糊现象的技术,游戏开发中经常使用粒子系统模拟的现象有火、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者像发光轨迹这样的抽象视觉效果等等。

Away3D中的粒子系统实现思路

跟大部分游戏的引擎的粒子系统实现思路类似,Away3D的粒子系统遵循以下基本原则:

  1. 每个粒子由带纹理的多边形或几何体构成;
  2. 粒子按一定的规则运动;
  3. 每个粒子有自己的属性,如出生时间、生命周期、位置、速度、加速度、尺寸、颜色、运动轨迹等等;

粒子生成步骤

在Away3D中,由n个粒子组成的粒子特效(为了与广泛的粒子系统区分,我们下面都称为粒子特效)主体,被抽象为一个mesh,而mesh的材质就是给粒子添加的材质,再往mesh中添加animator就组成了一个完整的粒子特效。下面我们就如何构建一个粒子mesh,以及如何为mesh添加动作器展开分析。

构建ParticleGeometry

在上面的的粒子系统基本思路说过:每个粒子由带纹理的多边形或几何体构成,而组成一个完整的粒子特效,则需要n个这样的多边形或几何体,这边我们且称为Geometry Set,我们可以往这个geometry set添加各种Away3D内置的几何体或者外部定义的几何体。出于性能的考虑,建议粒子特效里边最好不要添加太复杂的几何体。

举一个简单的栗子,我们要构建一个向上喷发的小球,就可以如下构建该粒子特效:

var ball:Geometry = new PlaneGeometry(3,3,1,1,false);
var ballGeoSet:Vector.<Geometry> = new Vector.<Geometry>();
// particle ball
for (var i:int = 0; i < 50; i++) {
     ballGeoSet.push(ball);
}

上面的代码,我们添加了包含50个Plane的geometry set,填充这个set可以视情况添加,如果有需要,你也可以同时添加plane、cube、sphere等几何体到这个set里边。准备好geometry set后,我们将使用Away3D提供的particle geometry helper将geometry set转化为最终的ParticleGeometry:

particleGeometry = ParticleGeometryHelper.generateGeometry(ballGeoSet);

这样,我们就完成的一个尚未添加动作的ParticleGeometry实例。

构建ParticleAnimationSet

接下来就是创建粒子动作集合了。类似传统的粒子系统,一个粒子是有其生命周期的,在这个周期中,粒子控制着它所有的属性,位置、轨迹等等。

在Away3D中,一个粒子有关联系统时间的startTime属性,代表粒子出生时间;有可选的duration属性(若没设置duration属性,粒子将无限延续),代表粒子的持续存活时间,还有可选的delay属性(若没设置,将无间隔时间),代表两个重复的粒子周期之间的间隔时间。这些属性基于GPU代码的时间计算,我们必须对要使用的属性进行预定义。

Away3D粒子系统中为粒子动作提供了动作集合ParticleAnimationSet类,构造ParticleAnimationSet需要三个bool参数,第一个参数表示是否使用粒子的duration属性,第二个表示是否不断循环粒子的生命周期,第三个表示是否使用粒子的delay属性,使用则当一个粒子周期执行完成,粒子会消失delay的时间后再进行新的周期。

还是上面的栗子,我们把三个属性都设置为true。

var animationSet:ParticleAnimationSet = new ParticleAnimationSet(true, true, true);
animationSet.initParticleFunc = initParticleParam;

值得注意的是,startTime、duration、delay并不是ParticleAnimationSet的属性,粒子特效中的每个粒子都有自己特有的以上三个属性,在Away3D中,他们被设定为local static属性,我们使用一个函数来定义以上的属性,然后赋给ParticleAnimationSet的initParticleFunc,这样,每个粒子都会调用这个函数一遍。通常,使用ParticleProperties::index这个值设置startTime、duration、delay三个属性,达到每个粒子有不同的以上属性的目的。栗子如下:

private function initParticleParam(prop:ParticleProperties):void {
    prop.startTime = prop.index * 0.005;
    prop.duration = 10;
    prop.delay = 5;
}

以上代码中,我们实现了每5毫秒产生一个粒子,每个粒子延续10秒,然后休眠5秒。

在之后的local static动作属性的设置中,我们也将所用动作的属性设置添加到这个独立的函数中。

Flash和JavaScript互相调用

1.Flash调用JavaScript中的方法
ExternalInterface.call(“js方法名”,“方法入参”);

2.JavaScript调用Flash中的方法
Flash中注册ExternalInterface.addCallback(“供js调用的方法”, flash方法);

例子:

exp.html

<html>
<head>
<title>swfobject demo</title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
function jsAlert(txt)
{
alert(txt);
}
function flashAlert()
{
var obj = document.getElementById("demo");
obj.flashAlert();
}
</script>
</head>
<body>
<div id="content">
NO Flash Player or NO Support This Flash Player!
</div>
<script type="text/javascript">
var so = new SWFObject("Testjs.swf", "demo", "640", "480", "10", "#000000");
so.addParam("wmode", "transparent");
so.addParam("allowFullScreen", "true");
so.write("content");
</script>
<input type="Button" value="警告" onclick="flashAlert()"/>
</body>
</html>

exp.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="<a href="http://www.adobe.com/2006/mxml" rel="nofollow">http://www.adobe.com/2006/mxml</a>" layout="absolute" creationComplete="init()">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import flash.external.ExternalInterface;
private function init():void
{
ExternalInterface.addCallback("flashAlert", flashAlertTest);
}
private function doAlert(evt:Event):void
{
ExternalInterface.call("jsAlert", "js Alert");
}
private function flashAlertTest():void
{
Alert.show("flash Alert");
}
]]>
</mx:Script>
<mx:Button x="36" y="97" label="提示" click="doAlert(event)"/>
<mx:Label x="36" y="64" text="Flash调用js" width="135" height="25"/>
</mx:Application>

注意:
1. exp.html, exp.mxml, swfobject.js必须放到web服务器中,直接在本地打开,会出错:SecurityError:Error #2060:安全沙箱冲突:ExternalInterface调用者…

2. ExternalInterface.addCallback(“flashAlert”, flashAlertTest); 必须包含在方法中,然后触发它;否则,会出错:1120:访问的属性flashAlertTest未定义。
如:

private function init():void
{
ExternalInterface.addCallback("flashAlert", flashAlertTest);
}

init();