系列前言
有一年多没写文章了,主要是去年换了工作,进入了苍龙城项目组担任猪程序。
这是我们项目的宣传片:https://space.bilibili.com/1849722793
由于接触到这样体量比较大的项目,在这一年里,我在技术上也获得比较大的收获。所以打算起一个系列。
这个系列不会很长,也不特定于解决某个技术难题或是实现某种功能,而是根据我在这个项目的工作经验,从UE4项目管理上,和技术选型,游戏架构方式上,做一个简要的记录,希望能给大家给予一定的参考。
初来咋到
刚来到这个项目,我非常吃惊,用简单的话来说,项目的代码是一座屎山,项目文件资源结构混乱,代码关联耦合非常严重,几乎到了一个无法继续开发新功能的状态。我甚至可以得出结论,一个较大项目里会出现的糟糕情况基本上这个项目都出现了。其中只从程序角度讲,有如下特征:
1.无模块化设计,至少Actor-Component模式都没有很多的利用起来,逻辑很多硬塞到Character类里。
2.无解耦,各个功能之间相互引用关联,即交叉引用随处可见。没有什么设计模式体现在项目中。
3.无可拓展性。
4.无可读性,如果你想知道一个动画是哪里播放的,你需要去代码里一层一层追半天。
我追了代码15天,尝试在现有代码基础上做出优化,最后我放弃了,我抱着不让我重构就马上走人的心态跟制作人提出了重构需求,可没想到的是制作人同意了。我需要在3个月内重构过去两年的代码。
Content根结构划分
感谢屎山创造就业机会,为了开始重构,我根据github上非常完善的UE4项目规范风格指南:https://github.com/Allar/ue5-style-guide 开始了我的第一步操作。
在项目Content根目录下建立一个全新的文件夹叫CangLong,因为我希望Content顶层目录尽可能不混乱,且不会因为项目自身的特殊命名结构,而与任何导入的第三方包的内容产生重叠(这也是虚幻商城要求内容创作者将所有内容都集中在Content下的一个文件夹内的原因)。
我期望Content下面的类似这样:CangLong(项目本体),包1,包2,包3,包4,包5。
而由于项目非程序部分不受我掌控,所以至少程序部分的内容,我从Content/CangLong开始,其他的乱七八糟的顶层文件夹随着以后的迭代,慢慢整理。
而且,我强烈建议,所有的UE4/5项目,最好都可以遵循上面提到的风格指南,已经足够规范了。
插件化开发
我私以为,程序写的代码,如果只拿来用一次,那是一件很亏的事情,而开发UE4项目,我建议第一件要做的事情就是以做插件的方式来开发整个项目,
不为别的,就是尽可能地去做一次投入反复受益的事情。
为什么要插件化开发
一般来说,假如项目名是BBQ,那么一般的UEC++项目就会在项目根目录的Source下建立一个BBQ模块,在这个模块里面写代码,而里面的所有C++文件,一般会是BBQCharacter,BBQGameMode,BBQPlayerController之类的命令规则,且每一个头文件里面,一般都会以BBQ_API的字样。
这,不通用。如果你想让这些代码放到别的项目里去使用(比如PXX项目),尤其是像我这种有强迫症的人,那么就会把Source下的代码Copy到PXX项目的Source下,然后改模块名,BBQ.build.cs->PXX.build.cs,BBQCharacter.h->PXXCharacter.h,BBQ_API->PXX_API。这样的操作,或多或少,一定有人干过,而这很痛苦。且网络上很多范例,也都是以这样的方式来开发的。 但是,插件化开发就能解决这个问题。
我的插件化开发方案
对于我们项目,我建立了一个插件,叫做UECpp,其中包含UECpp模块和UECppEditor模块。为什么叫UECpp呢,因为我不假定这些代码会用到任意一个项目如RTSGame,ActionRPG,那干脆我就叫他UECpp算了。
而项目主要Game模块代码呢,就是仅仅针对某个具体项目而特有的代码,比如我们项目的话,就是CLCProject模块,它会依赖于UECpp模块,但UECpp模块不会依赖于CLCProject。
而UECpp模块虽然是一个插件,但它也不能完成所有的事情、也不能把所有的代码都往里面放,不然它一定会随着开发迭代变得越来越庞大。所以随着项目迭代,当我发现UECpp模块中又出现或者存在了一些看起来可以更通用的东西(比如栈状态机),我会把它提成另外一个插件,叫GenericStateMachine插件,为什么叫这个名字呢?因为它的代码很Generic啊。
如下图所示,除了CLCProject模块,其他全都是插件模块。
插件化开发的其他好处
除了插件化开发能更方便地让代码在不同项目之间使用之外。
它还能防止见缝插针。想必很多程序员,都可能遇到过“先写在这里,以后再完善”的事情,其实、恰恰是这样的心态,导致代码会随着开发变得越来越乱,因为项目周期原因、或者赶工,很可能这种临时写在某个地方的代码,就成了屎,越堆越多。
而插件化开发,就能让写具体代码的人,迫使自己去思考这个代码应该写在什么地方更合适,举个例子:
我们项目需要处理玩家进入游戏中的某个地点,播放一段音乐,如果没有插件化隔离代码模块,那么程序就可能直接在游戏地点相关的代码里去写上一段“进入这个地方播放音乐”的代码。但有了插件化隔离,那么项目具体代码,会请求游戏地点插件的维护者,暴露一个玩家进入游戏地点的事件,从而写音频播放代码的人,就会在另外一个合适的地方去监听这个事件,并进行处理,比如音频管理器去监听游戏地点管理器的事件,并进行处理。
同时插件化开发也能培养各个程序员的模块化意识,并将自己负责的模块以自己的作品来对待。下面我秀秀图片:
项目结构
本篇只是简单从项目结构、模块结构上给了一些小建议。下一篇会讲到我是怎么用AbilitySystem来理清项目的一团乱麻。