视频封面

上传视频封面

UE4 UI粒子插件

这是一个能让Cascaded Particle Editor编辑的粒子放在UI上跑的插件。
核心特性
1.直接在UI上向前渲染,摆脱了Render Target。
2.作为一个控件,可以在UMG Editor里编辑和控制。这意味着可以在UI的动画编辑器里和其他控件配合做出一些特殊效果。
3.支持Sprite和Ribbon。就算你不太需要在UI上显示Sprite,你也可以冲着Ribbon这一功能购买这一插件,为了能给你的UI控件加上花里胡哨的小尾巴。就像题图里最右侧的粒子一样花里胡哨——虽然这个粒子看上去像一块血迹。

技术细节
1.该插件本质上是直接把场景里的粒子数据拿出来,以一个SWidget显示在UI上。所以用法基本上就是创建一个粒子系统——把一个User Interface的材质设在它的发射器上——然后把这个粒子系统扔到UI上完事。具体用法可以看视频。
2.支持4.18~4.24。呃~如果你想在低版本上用的话改uplugin里面的引擎版本号就行了。时间原因我没有编译更多版本,但我保证4.18是支持的。
3.因为Slate本身的instance功能受限过多,该功能并没有依赖instance。这意味着FeaturelevelES2下该插件也可以使用。这也意味着想要在Slate上渲染Mesh类型的粒子就不太现实了(理论仍可行)——所以该插件目前只支持Sprite和Ribbon。
4.关于Ribbon,我只实现了Camera-Facing相关的渲染,只因为我觉得这对UI来说足够了。但如果你觉得对其他朝向有强烈的需求,可以跟我提。
5.受限于Slate本身的功能,不支持GPUParticle、PostEffect。没有后处理意味着没有bloom。其实众所周知的一点是,游戏里很多粒子之所以能够达到闪瞎狗眼的效果是因为有bloom的存在。(其实不一定是粒子)但是Slate基本是没有后处理的,除了那个BackgroundBlur控件(虽然我觉得可以强行尝试一下BackgroundBlur做bloom毕竟大家本质都是blur)。所以你在UI上想通过后效来达到bloom粒子的效果基本没可能,你只能把这种效果硬怼到UI材质里。
6.性能问题估计很多人问。但其实这代码写了两年多了(当然最近才想起来要上线),一直没认真做过压力测试的我,来无责任胡说一番。如果我说的跟实际测试结果不符也请不要打我。这玩意的CPU开销大概是原有场景粒子的2~3倍。对十年前的中端PC来说,UE3当时的粒子系统撑死跑个全屏四五万个。按这个数给到移动平台限制一下,四五千,然后给UI再除以10,四五百。(其实根本不能这么算,我估计UE4的Cascaded粒子系统在移动平台跟PC平台差距绝不止10倍)总之,这段胡话请无视。结论是,它的CPU开销跟场景CPU粒子相比的话,绝不可能相差超过一个数量级。
7.Particle Color、Dynamic Parameters不可能支持。我把前者用Vertex Color代替(会被clamp到0.0f~1.0f),后者的前两个参数被我传给了Texcoord[1]。这方面视频里也有演示。
商城地址
https://www.unrealengine.com/marketplace/en-US/product/10b64fc937ac49edba22761bb0b9eaeb​www.unrealengine.com

我的优势:
UE商城有另一款UI粒子插件,售价99.9美刀。那个我没有仔细研究,看预览界面,它似乎是自己实现了一套粒子系统,还带有编辑器。
那我这个跟他那个相比,优势是:
  • 便宜!只要19.9!买不了吃亏买不了上当!
  • 使用原生粒子系统,无额外学习成本!
其他资料
这里有一篇英文文档,里面详细列举了注意事项。此处不详述。
http://www.snafloda.com/blog/uiparticlesystem-documentation/​www.snafloda.com

这是一个范例项目。里面有一些根据引擎StarterContent改出来放到UI上的资源,还有一个Ribbon示例。
抱歉我作为程序猿实在做不出什么特别漂亮的粒子出来。
https://github.com/adolfans/uiparticlesamples​github.com

后续开发
目前暂无后续开发计划。如果有啥bug的话请及时提,我会持续关注大家的反馈。以及上面说的Ribbon相关如果有需求可以做。但是受限于UI渲染机制的我就无能为力了。以及,如果你要实现一个Cascaded Particle System里没有的Module,那这也超过了这个粒子插件所能及的范围了。
请各位老板多兹磁!在下多年没涨过工资了也不容易orz。

虚幻4UI优化和扩展指南

写这篇文章本质工作过于疲劳,快干不下去了。怀疑自己精神状态有问题,在我觉得我自己很正常和我觉得我自己很不正常之间反复横跳。
也许是对UE4产生了心理阴影,也许是对写代码产生了心理阴影。

以下是提纲:
1.InvalidationBox和林林总总的坑。
2.Prepass和LayoutCaching。
3.慎用UMG的相关接口。
4.如果你想要尽可能降UI的DC的话,我没啥好建议。
5.扩展UI的功能,譬如显示模型?

我印象中我两年来在Slate里面修的bug数起来有几十个了……关于怎样修的本文不详述,都是一个个心理阴影。

InvalidationBox

所有的移动端优化建议里都提到了InvalidationBox,但如果你两年前就激进地用了,然后你开心地发现UI的CPU开销降低了一半,然后第二天你哭着发现有几十个bug等你修。
a.checkbox状态不刷新。
b.scrollbox滚到一半再也滚不动了。
c.……
我没有一一确认这些bug在最新的版本里是否存在,不过总归是两类问题。
a.invalidationBox没在数据更新时触发刷新。
b.有些刷新(譬如播放动画)会导致整个控件处于volatile状态。如果这个状态在最终没有刷新回来,那将是很麻烦的结果。以下详述。
Volatile控件的使用
这是一个标记控件为不可cache的flag。但是如果你遇上控件不能刷新的bug,你还是考虑优先修复bug,而不是用这个flag避过bug。
Volatile控件的鼠标消息会在最上层,优先盖过invalidationbox内的所有其他控件。本质问题在于为了优先保证整个控件其他元素可以cache,volatile会在invalidationbox最后绘制。而引擎的点击信息(HittestGrid)的生成是顺序的,是没有层级关系的。
不只在编辑器中勾选了Volatile会使控件处于Volatile状态。动画播放的时候,对应的SObjectWidget是Volatile的;ScrollBox在滚动的过程中,它自己也是Volatile的;对嵌套的InvalidationBox来说,内部的box对父级的box来说也是volatile的。
播放动画导致的层级问题
这是一个无解的问题。如果一个invalidationbox内有两个UserWidget在同时播放动画,那这两个控件的内容就很可能穿插。

PREPASS
这个在移动端中低端机上能占1.6ms以上。当然,视你们UI的复杂度而定。
这个东西意义在于计算布局。譬如你用了SizeToContent,AutoSize之类,需要每帧算大小。
现在引擎有一个consolevariable叫Slate.EnableLayoutCaching可以去掉它。编辑器别急着开,一堆控件的显示会炸裂。
这玩意主要有两个问题:
1.有东西的刷新依赖Prepass。
这个问题方面,文字控件是重灾区。
你开了EnableLayoutCaching,逻辑里设置文字的时候,它可能不刷新——你肉眼可见你设置的文字属性都没刷新上去,而不是文字布局等等没刷新。主要STextBlock写得有问题,它内部数据的更新(ComputeDesizedSize,很多参数靠这个函数传递)实际是由Prepass进去的。
我曾经尝试过把Text的更新放到Tick里,把控件设成Tickable,但实测发现这么干会吃掉一部分时间(没记错的话0.3ms左右),最终作罢,将数据更新分散在每个Setter里写了一份。
2.使用了Binding的东西不刷新布局。
编辑器如果全局开了EnableLayoutCaching,很多不刷新的问题都是文字控件,很多都是Binding导致,例如UI编辑器里显示隐藏控件的眼睛图标(没错它是个字符- -|!)。

UMG接口频繁调用
关于UMG相关接口,我想说的不是说这些函数的设计有性能问题,是不要频繁调用。
我曾经在优化人物名字板的时候发现我们游戏的逻辑在每帧的tick里去频繁通过UUserWidget的接口去给50个左右的名字板SetVisibility。这个逻辑在移动平台空跑占了0.3ms。我看了一下SetVisibility的逻辑,发现最费的地方在IsValid。这里不确定是TWeakPtr的解引用(里面有个原子变量)导致的性能问题,还是Cache miss。
解决方案很简单:不要写类似的逻辑。如果要更新屏幕中控件的可见性,加一个数组用来缓存控件已有的可见性,用它来拦调频繁的调用。

DC合并
合并DC的前提是你用Paper2D解决了贴图合并的问题。
如果追求低DC的话,可以考虑在FSlateBatchData::Merge里面尝试让相邻层级中前一个层级最后一个Element和后一个层级中第一个Element的batchkey相同的合在一起。但这种方法在CPU方面是有开销的……毕竟所有层级遍历了一遍。
这是最flexible的方法。该方法并不能一蹴而就地将DC优化直最优,但能保证结果正确。
至于展平LayerId这件事,我没有好的方案。因为展平LayerId是一件违背了设计原则的事。UE4为了贴图合并引入了TextureAtlas,然后为了保证合并贴图后层级的正确性引入了ZOrder。ZOrder就是LayerId不一致的罪魁祸首之一,但如果不是层级不对,谁会想起调整ZOrder呢?
绘制顺序能保证同一层级上Element的组织顺序是对的。但一旦不同控件共享了同一张贴图,则另当别论。展LayerId的问题就在于此。开发过程中若你无法控制又无法保证不同的控件对应的Sprite具体位于哪一张贴图中,那强行展LayerId永远会有未知的隐患。

UI上显示模型
理论上是可以显示模型的——在不用rendertarget的前提下。
使用SMeshWidget。
这个东西貌似是他们在开发Paragon的时候为了解决头顶血条的效率问题引入的。有一些instance的功能,但限制也挺大。
关于这东西可以做到什么效果。Spine的UE4插件就是基于这个写的(这玩意在移动端巨坑,Index硬编码成了32位unsigned int,都上天了)。理论上你可以在UE4的UI这块屏幕空间里画任意网格。
一般在示例代码里,会使用一个类型为FSlateResourceHandle的对象和一个FSlateBrush指针来管理材质。但这些东西析构的时候是有问题的,不知最新版引擎改进了没有,这个问题会导致崩溃。
如果你在使用Spine的过程中遇到了疑似Slate相关渲染的崩溃,请避免使单个UMaterialInterface对应多个FSlateMaterialBrush,并使用DeferredCleanup来析构FSlateMaterialBrush。也就是说不能有同名的FSlateMaterialBrush。Slate内部使用FName作为key,一个FSlateMaterialBrush析构的时候会析构掉内部的东西,这样另一个渲染的时候会崩溃。

亚服的修改终于上体验服了

妖刀姬转火多加两刀、酒吞的鬼王状态、花鸟卷四只鸟变成三只鸟、青行灯变成了打火机、兵俑一刀砍废了等等。

妖刀姬的话,真不知道有多少人能吃到这次加强的好处。我这种咸鱼玩家当然乐于看加强了。今天还试了一把鸟+36级六星刀刷阴界之门,刀刀的表现马马虎虎,真不如同一套御魂的五星荒……

酒吞的鬼王状态是很厉害的。

花鸟卷削弱应该是针对针女花鸟这种有毒的御魂配置。它确实很有毒。一个奶妈打输出。不能说特别恶心。但,可能脱离策划本来的目的了。

青行灯也算是大加强了,对我这种没有辉夜姬的人来说是福音。虽然我……大部分情况下都靠惠比寿打火……大招升到满级比起辉夜姬来回火能力还是差点的,但好在遇上木魅可以强行克制——没火我也能放大。能吸火,能省火,其实挺好……就看怎么往队伍里放了。
早期阴阳师数值还没这么爆炸,不像现在说出个4S的SSR就出,青灯的数值比较均衡,换言之没有S的属性……其实只能走控制路线……
至于破势爆伤灯……这个我真算了。同样御魂的情况下比玉藻前少至少1000点伤害……

纪念天国被一刀砍废的兵俑。有人说,有拉条啊!
有毒吧……降低40%的速度,可不是降低10点,降低20点,降低30点,可是直接跑不动了……
你镰鼬拉得动试试啊!
其实不仅是拉得动拉不动的问题,是行动顺序打乱了,套路使不出来的问题。
啊,你说妖琴?
等于是用一个妖琴加一个兵俑实现了群控的效果?
??
如果减速是个可被驱散的buff的话,那用雨女哭一下也许就能搞定。
总之,怎么想都是原先一个式神搞的定的群控,现在需要至少两个人的配合。而且这群控还是能一下就被驱散的群控……
简直神了。
好了,拿转换券来,我不会犹豫。

御馔津也被加强了。网易终于发现没什么人用了……其实我是欣喜的,毕竟加强之前就带着四星的她过了几个秘闻本了。
网上有人说御馔津带破势一箭能多少伤害……这……有毒吧……………………
看技能就是打后手的啊……你以为你射箭的时候还能触发破势么……

阴阳师所有攻击频率异常高的式神都可以带针女走输出路线。譬如体验服被削弱的数珠,譬如早就被削弱的彼岸花,譬如花鸟卷……
所以我选择给御馔津带针女(X
开玩笑的,我练度这么低,肯定是带地藏保命。

怎样吃狗粮

阴阳师玩到后期会发现招福达摩这玩意很尴尬。吃还是不吃,这是一个问题。
去年这玩意还有人在评论区吐槽:
“提供经验是普通N卡的1.5倍,花费金币是1.5倍,意义何在?”
今年不知啥时候再一看,似乎改了。现在红蛋怎么喂都是500金币了。
可喜可贺。至少省钱方面有优势了。
但是仔细一看,提供经验:6435,花费金币:500.
再见。我困难25开着经验加成刷3把,两个狗粮都得到了至少6435经验。更何况我还有金币掉落。我只花了9点体力。
所以这个在商店花费勋章是普通破碎符咒1.5倍的玩意到底意义何在?破符还能有几率出R卡,换5骨灰。
我觉得能喂大吉达摩就喂了吧。但是大吉入手难度还是比这玩意高的。仔细一看,保有量完全不是一个数量级。
关键现在刷高等级副本不断地掉落这玩意,掉了就屯在库里了。库里东西多了,选择式神的界面会卡。唉,糟心。
什么时候能给我换成白票就好了。
关于把招福达摩喂大吉达摩。
其实是这样的:
四个招福达摩喂给大吉达摩,然后把该大吉达摩喂给一级2星奉为达摩,会发现它满级了。然后给它升3星,会发现它已经3星满级了。甚至还有经验溢出。
这件事说明了什么道理呢……说明一个大吉(胧车稳定出货,还算有一点点稀有)加上一个奉为达摩的入手难度和一个三星满级奉为的入手难度是持平的。所以没有必要倾家荡产去搞三星奉为来降低肝度……万一入手了也不要不舍得吃……
所以,怎样清库里的招福达摩,这又是一个问题了。

沉迷了一年阴阳师

去年十月份AFK,今年三月份回坑。
果然还是喜欢会动能说话可以360度观察的小哥哥小姐姐。仅此而已。
打算写关于阴阳师的东西了。
这博客里之前到处都是关于单相思的碎碎念。唉。反正事到如今无所谓了。

上海之行:ZH,LJ以及TY

12月24日我连滚带爬来到了上海。
出了徐家汇地铁站我就见到了那仨哥儿俩:ZH,LJ还有TY。其中TY是头一次见本人,虽然Q群里见过这人的名字。
山东的小伙子果然都挺高的啊,这三人都有一米八以上吧。
LJ我毕业之后就没再见过他了,估计有将近四年没见面了。最后一次跟ZH见面是一年半之前来着。但是看上去他们真的一点都没变。
老实说我之前在线上跟他们聊天比实际见面时多,而跟他们最熟的那段时间我大概还是一种线上能说话线下就是悶葫芦的状态。但现在我也不同了,毕竟跟一帮男同事朝夕相处了三年,也在线下有了面对面交流的好朋友。
现在看来LJ就是一非常常见的游戏策划。俗话说得好,策划全凭一张嘴。于是我看到他一直跟ZH还有TY聊各种游戏。回想一下的话,我之前的策划同事也差不多都是这样。
ZH的话,虽然他是一个程序猿,但他并不是一个话少闷头做技术的人。他话真心不少,而且并不都是技术相关,和LJ两个人也挺能聊的。这人你稍一不注意他就自顾自地快速说了很长一段话然而我还没有弄明白他一开始在说什么……
TY的话,这人只让我想起了之前的上司。一样的爱说瞎话,爱口胡。十句话有八句口胡。遇上这种我一般不知道该如何反应就只能干笑。
跟他们来到一个棋牌室玩桌游。
我上一次玩桌游的记录是在趣游和那帮人玩杀人游戏,应该是两年前的事了。这让我确确实实想起了刚进趣游时和大家打成一片的日子,我猛然间有点伤感,然而旋即又意识到那种日子已经离去很久了——也许是人变了,也许大家都没变,只是环境变了。
老实说我并不喜欢玩这种游戏。也不知道从哪年起,我就对有竞技内容的活动有了抵触情绪,因为得失心太重,讨厌失败,所以总是喜欢直接放弃。
两年前我跟趣游那帮人玩杀人游戏的时候就是一随时随地狂笑的傻子。我的乐趣甚至是出局之后作为旁观者看他们互相怼。
这次我其实挺开心的,无论是看TY口胡还是看他们互怼。
有朋友一起玩挺好的。
12月25日我跟LJ还有ZH去了外滩。
看了蒙蒙细雨中的黄浦江和被雾蒙了一半的东方明珠。
难为两位小伙子陪我雨中漫步了。虽然我心里有些过意不去但是我啥都没说。果然是因为觉得实际上不太熟所以也没有跟之前公司的朋友那么坦率吗?他们还请了我日料。
也见识了旁边那条步行街的繁华,在街上看到了一些之前想找没有找到的品牌的店铺。
总觉得即使就去了这么几个地方,也能感到上海比北京要繁华。也可能是因为我在北京太不爱玩了。
在北京的话,即使是去专柜买某款化妆品,我也是不想去的。因为并没有人陪我去。某些牌子的导购会“看上去很亲切”地问我:今天一个人来的呀?然后我心里就觉得自己被鄙视了——我没朋友。
在这里逛步行街的感觉让我想起了唯一一次跟之前的同事一起逛街,逛的是大栅栏。
事实上我过去一年时间里最好的朋友就是在趣游坐在我对面的那位。而我和他除工作时间之外几乎没有任何交集。
这件事的负面影响是我有事没事总喜欢往公司跑,我甚至心甘情愿地没有个人生活。只要他在,我就会开心。
我对他的感情已经说不清了。人总是被各种误会各种错觉所蒙蔽,察觉到的时候心态已经跟之前不一样了。然而即使意识到那些是错觉,心态和感情也回不到从前了。
我不爱他。我喜欢跟他聊天,我几乎什么都会跟他说。

然而当初逛大栅栏的时候,虽然有别人在,我心里想着的却也是:这是我第一次和你逛街。
现在想来,估计那也是最后一次了。
言归正传。
有朋友陪我玩真好。虽然经常LJ和ZH哥儿俩有说有笑地就把我落下十米远。
但我这次不是一个人压马路了。

多谢。

windows崩溃调试经验谈

我是一个对各种语言特性来之不拒的人,我不会因为一个人写了各种goto而抨击这个人的代码质量,同样我也不会因为一个人在c++代码里写了c风格的代码而批评他的c++水平。语言特性本无罪,人为制定规范只是为了降低问题发生的概率,然而bug是恒存在的,因为谁也不能阻止人类犯二。
特别说明一下在C++代码里使用本该被C++替换的C特性的行为。首先必须强调现在的软件开发过程中用到C语言实现的(开源)第三方组件几乎是必然发生的,在发生于这类第三方库内部崩溃的情况下,C++的异常系统是不可用的。所以对我来说,看崩溃dump的时候,C++和C没啥区别。
1.只有栈没有堆的minidump
通常见到的都是minidump。运行于windows上的程序通常都是客户端程序,没有时间和空间来dump整个内存。所以你手上拿到的都是只有栈没有堆的信息,只能通过栈上的临时变量来反推导致崩溃的逻辑。dump会告诉你是怎样的内存访问导致了崩溃,比如下面这一行:
Unhandled exception at 0x0063e8fe (xxx.exe) in crashdump.dmp: 0xC0000005: Access violation reading location 0x2933b000.
意思就是越界读到了0x2933b000这个非法地址。需要注意是reading还是writing,因为这是判断哪个变量出错的关键信息。
2.令人困惑的变量信息
visual studio开了最高的编译优化(ox)之后,visual studio很多时候就不认识自己编译优化后的代码了。无论是debug过程中,还是看dump的过程中,这种情况几乎每次都会出现。它的调用栈是对的,但显示的变量信息是错的。大概是vs不认识自己的栈指针了(
关于这个问题,stackoverflow上有过讨论:

遇到这种情况,只能开汇编代码看了。运气好的话,能看到刚刚从栈上读到寄存器里的出错变量,运气不好的话只能根据栈指针往后查。
3.令人困惑的调用栈
这里讨论的是函数上一层调用和这一层调用完全对不上的情况——也就是说,从一个莫名其妙的函数call到了另一个跟它看似毫无关系的函数。
首先可能的原因就是call的目标地址不对,也就是说函数地址错了。这种情况并不多见,很可能你传入非法地址的时候就读越界崩溃了。
其次是编译优化导致的问题。
第一种情况是inline导致的。这里说的是inline这种编译优化行为,而不是C++里那个关键字。(事实上并不是inline这个关键字就会触发inline优化。)这种情况可以通过反汇编代码轻易判断出来。
第二种情况是编译器把两个函数实现当成了一个实现处理。比如两个不同的类,它们都有一个getter,返回的同样是offset为16的成员变量的值,它们生成的函数实现可能就是一样的。这时候,编译器可能就把这两个实现当成一个函数,于是从这一个类里的函数call到了另一个类的成员函数。这种情况对照一下两个函数的实现也能轻易看出来。
再次就是栈被破坏了。
这种就是典型的写缓冲区越界导致的,写坏了栈,连同之前入栈的返回地址。这种情况下,没人能保证栈上有多少正确的信息。如果后面有幸在当前函数内崩溃的话,你看到的当前函数是正确的,但上层调用很可能已经被抹去了。
PS:一个很容易就破坏栈的方法是用sprintf,第一个参数是一个栈上的临时数组,然后打印出的文本大小超出了数组的范围,就会写到栈上之前的数据。新版本的visual studio默认的编译选项下喜欢把sprintf这种不安全的函数当编译错误处理,你可能会使用sprintf_s来替代它。然而sprintf_s充其量就是不会把栈写坏而已,它会判断字符串大小是否超出了范围,如果超出了范围就会触发断言——然后在用户看来程序还是崩溃了。
4.