2D横版冒险文字游戏Horror Book2项目总结

前些时间接手了个烂尾的社团游戏,其实也没出什么事,就是上一个码农字面意思上的删码跑路了而已(想想也是蛮吓人的orz),美术他们手上只剩下一个仅做了标题界面的demo,没办法只能找新的猴子重构一个游戏出来。做到现在核心功能的开发已经全部完成,剩下的工作是满足新增的需求和debug,因为还在等测试那边给反馈,难得有一天放假,写写开发心得也好以后要是遇到相同的issue能快速解决。

单人开发比起团队开发更加要注重效率,因为一个人的时间是有限的,游戏做不到可以玩的完全体就是0,团队开发你可以钻研一个东西很深,例如信息交互,写一个系统内的Message传递,或者写个大家都能用的任务队列,但是单人开发不可以,唯一的目标就是做出来可以玩的东西,性能和代码low不low应该是第二去考虑的事情。如何最大利用单人开发0沟通成本的优势,回避开发人时少的劣势是作为单身开发者必须要想明白的问题


1、聊聊GUI的制作

我觉得只要是开发者,UI都是绕不开的坎,工程量大,微调耗时,经常会因为像素级的UI摆放和美术打架,是个没啥意思还不好搞的活。

想要解决和美工打架的问题,想办法从PS里得到WorldPosition再原封不动丢进Unity里再做分层是比较好的办法,不过Unity的UGUI默认并不显示WorldPosition(也许只是我没找到显示的按钮),虽然可以代码控制,但如果想要手动修正的话还是比较麻烦,我解决的办法是导入Unity时不依靠UGUI的Canve,而是当做Sprite做导入,这样导入进来的坐标是Tranform,基于World,然后再统一地把tranform组件换成Recttransform然后加一个Canve作为父物件。如果做的过程中想要微调worldposition可以直接把物体从Canve的子关系里拉出来,记录Transform的数值后再拉回去orz。

UGUI的AnchorPosition我觉得太过注重屏幕自动适配功能的便利,然而忽视了做UI动画时方面的考虑,UI的动画我个人强烈不推荐将AnchoredPosition作为动画的坐标参数,因为只要美术突发奇想要求你改锚点来适应新的美术需求,用了这个参数的动画就会立马崩溃。我觉得Unity他觉得UI动画这种东西也应该放在Unity里面去做,而不是在外面做好了导入进来,依靠我们的Animator/Animation不是很好嘛,可实际上很多时候UI动画应该是怎么样美术方面早就定好了,比起让程序员自己做动画的主观美术,更多的需求是用代码对动画的死的控制,动画的位置是有很强的相对关系的,就是我定好一个点,之后的动画素材应该距离这个点50*n个像素地排列进来,可是如果在Canve里,只能用worldPosition弄到这个坐标,而且你无法在属性面板里看到这条属性orz,所以还是上面的方法,如果一定要在UGUI里做复杂的UI动画,我建议先当做Sprite在WorldSpace里做好,再放进Canve里(当然这个Canve的坐标就要设死在0,0,0不能动了)。或者如果是非常复杂的动画关系,建议不要放进UGUI里搞,UGUI不像NGUI(这很重要),只负责纯粹的GUI是好的,因为自动适配确实很便利,但是如果想要做复杂的相对位移动画,甚至是什么卡片游戏的动画逻辑,请在WorldSpace里做。

关于UGUI或者其他的一些什么动画,DOTween非常好用,有点像NGUI的Toween(好像就是同一家出的?),支持位移,Fade和Complete之后的委托,支持大部分的基础元件(Sprite,Image,Material,Color都支持),基本能满足大部分需求,内置的几十种速度曲线可以满足99%的需求(反正让程序员自己拉曲线肯定只会拉出一坨狗屎),具体可以看http://robertpenner.com/easing/easing_demo.html 预览自己想要的曲线效果。而且DOTween还支持用Lamba表达式或者delegate直接输出参数,例如我这样:DOTween.To(() => skeleton.skeleton.GetColor(), delegate(Color x) { skeleton.skeleton.SetColor(x); }, Color.white, duration.Value); 为什么要推荐DOTween这种运动插件,因为我很讨厌在代码里写大量的协程,协程会破坏代码的结构美感(在一堆Function里窜出来一个Ienumerator),特别是写协程只是为了运动这种几乎在你想到的所有地方都可能用到的功能,你要么写一个类似DOTween的功能类,要么不写功能类,不厌其烦地在代码里写进原本和它毫无关系的运动协程!所以如果你不想自己写,还是老实装一个吧。

Unity5之后增加的Animator是好东西,虽然和老版本的LegacyAnimation不合,我发现他创建AnimationClip都是用AnimationLabel的同一个按钮,生成的动画文件后缀相同,但有时候创建出来的AnimationClip就是不一样,注意需要仔细区分自己的动画文件是Legacy的还是新版本的,新版本的没有很多属性设置,只有一个设定是否需要Loop,新版本Animator不支持LegayAnimation,千万要注意。

Animator一个好处是做主观动画很方便,例如我做个暂停,停了之后需要一个UI元素进来,这时候美术大概就只会给你素材,不要求你具体摆在哪个地方,这时候用Animator做动画就很方便了,只需要录制,拖进来,录制结束,搞定。而且Animator是状态机,我们甚至在使用动画的基础上还可以用Event来做UI的层级管理,例如我点开始按钮,初始界面元素FadeOut,选人界面的角色素材FadeIn,这里我就可以给开始按钮加一个Animator的Trigger,点击这个按钮之后,从StartState进入到SelectState,给SelectState写一个Event,OnStateEnter的时候就把对应的UI元素做动画,我再点击下返回按钮,从SelectState返回到StartState,把刚刚做的动画倒放,OK收工。而且Event的成员是可以预先初始化的,把成员public出来就可以了,string,gameobject都可以,这样可以用一段通用的代码管理类似的功能,就很方便。管理UI的逻辑层级用这种可视化的方式可以节约大量的开发时间。

这里注意一点,Animator是默认每个状态的转化有0.2s的duration,如果不需要用Animator的动画功能,只是当做逻辑判断的状态机来使用的话,记得要把duration设置为0s。我当初用Animator作为Spine角色的动画控制机,就是没有设置好这个duration导致角色的动画转化有莫名其妙的停顿otz

但是Animator也有局限性,首先他只能初始化参数,不能在运行的过程中输入或者更改参数,更重要的是他无法输出成员,所以只能作为简单的预制状态机来使用,对复杂的情况难堪大用。毕竟人家Unity写出来是作为动画状态机来给你使用的……

最后提一个让我debug了半天的问题:这次的角色对话框我是用prefab直接生成的,然后发现点击事件和射线检测不能生效,最后发现是使用UGUI时没有同时生成EventSystem,这个EventSystem在编辑器模式下是会在你创建Canves时自动帮你创建的,但是当你要自己手动创建时,记得别忘了EventSystem,我选择办法是把EventSystem做成Canves的子对象,这样我生成Canves的prefab时就不用关心有没有忘记生成EventSystem了


2、Spine动画

Spine大家都很熟了,其实我是第一次真正用到Spine动画,因为以前的美术同学都不玩这个orz…………哭哭,Spine的使用到现在已经很简单了,基本上就是搞清楚Spine动画的版本,然后去官网下插件包 for Unity就行了。这里说两个注意事项吧:

一个是Spine的atlas文件,要想读进Unity里需要将后缀加上.txt,注意是加上,不是改成

然后是Spine动画输出后美术不要随意变更文件名,因为输出的时候文件名已经写在了atlas里,不一起改动的话,会导致无法读取Spine动画。

其实做Spine动画的时候遇还到了一个问题,就是策划要求做个Spine角色的FadeOut动画,但是如果直接控制Spine动画的透明度,所有的部件都会同时降低透明度,导致关节部分重叠,颜色会特别突出,简单点说就是会直接看到Joint,因为给我的Spine里不包含深度信息(我不清楚Spine里能不能写入深度信息),所以也就没办法从shader方面来解决这个问题,只能是在做Fadeout动画时,临时Render一张Texture,把Spine SetActive=false,立绘打印出来代替Spine来FadeOut,不过这么做有个问题就是Render的效率我无法控制,有的时候来不及Render效果就会很尴尬orz,美术那边提出个方案,FadeOut的时候把色相和饱和度降低,这样Overlap就不会太明显,玩家不是盯着看很难察觉,也算是一个trick了,google了一下,挺多人在Spine的论坛提出这个问题,希望以后能有通用solution出来orz


3、playmaker

一开始让我用playmaker,我是拒绝的,因为不能你让我用,我就马上用,第一我要先google一下,因为不能看介绍说用了这个插件不需要写代码也能做游戏,游戏Duang一下,很cool,很juicy,这样其他人就会骂我收了钱,根本没有这样的插件,是假的。fnmdp,我在这里郑重安利,没有playmaker的话,我根本不可能用这点可怜的人时里把策划那变态的需求给做完。

我觉得网上那些介绍都太玄乎,什么从此摆脱代码,傻瓜式做游戏,而且很多freshman的发言,搞得playmaker好像是一个专门开发给初学者的插件一样,事实上,playmaker的最大价值在于:它提供了可视化的状态机编写界面

朋友,听清楚了,状态机啊,就是那个在游戏里别管是什么奇葩玩意儿都能解决的万能法宝FSM有限状态机啊

我知道有很多人对可视化和傻瓜式嗤之以鼻,认为技术开发人员不应该使用这些本应该开发给美术或策划人员的工具,或者这些傻瓜式工具太过低能,无法满足多变的需求。如果是在其他领域,说不定我会认同这个观点,但是在游戏开发领域,我认为这种观点是绝对错误的,因为事实上我们的工具确实是在越来越傻瓜的,我从以前用flash开发到XNA再到现在Unity,最大的感觉就是以前花超多时间做的东西,现在已经成了自带功能,我们开发游戏的,难道还要像以前那样从引擎逻辑开始做起吗?这显然是不可能的,我们绝大多数时候是直接站在别人的成果上面做拓展,如果功能不满足自己的需求,那作为开发人员难道不会自己写吗?playmaker所有的代码都是公开的,dll直接丢进.net里就能改,而且内置的几百条action全是c#编写的,你甚至不需要dictionary就能照猫画虎地自己编写需要的指令,事实上我也是中途才开始使用playmaker,与原系统的交互完全没有问题。

我写游戏最怕的是什么,我害怕写的就是带锁的问题,有生产者与消费者的问题。代码是顺序执行的,游戏不是,所以游戏要有各种用以并发的方式,可是并发就代表了难以控制,这是代码去很难做的,这也是为什么大型的工程会非常排斥使用单例模式,因为多人开发中,单例模式很难去控制什么时候生成,什么时候释放,一旦释放顺序出错,系统就会出问题。代码想要控制执行的顺序实在是太难了,有的时候,甚至只是想让某段代码wait 1s都要大费周章地测试1s的时间是否合理,其中的逻辑安全性想要保证太困难。

而可视化的优点就在这里,逻辑可视,执行的顺序非常清楚,而且可以很轻松地调整顺序关系,而且减少了重复的反复编写逻辑代码的时间,不说其他的,光是new,然后吧把成员分别初始化的过程就足够让人崩溃了。而且如果大量的这样的初始化代码堆积在一个文件里,别说修改了,我就是连点开的勇气都没有。

代码控制的优点是什么,是条件判断,逻辑判断和数值计算,几行代码可以包含极大的信息量,这是代码的优点,而且流程化的代码复用性极高,可以将类似的流程迅速复用,甚至直接写成泛型。但是这是死的,要求功能极度相似,或者有重复开发的味道才能这样做。

可视化相对的,条件判断和逻辑判断能力极差,只能做最简单的一重判断,二重三重就会开始混乱不堪无法阅读,数值计算更是,所以这些东西我写在代码里,playmaker负责调用这些我写好的指令就可以了,playmaker负责什么,负责逻辑,负责状态的转移和储存,这些是代码很难做的。甚至写AI的任务我也交给了playmaker,因为我的AI都是针对地图特化过的,只适合特定的地图下使用,代码根本谈不上高度复用,然而用可视化的状态机来编写,高速的优势就体现出来了,因为内置了几百条指令,很多与Unity原生交互的代码我能找到现成的指令直接使用(顺带一提,有我最爱的camera fade in和wait,我超爱的),基于我游戏模式的也可以自己写指令来完成。

上面是游戏里其中一条故事线负责全局事件的状态机,光是状态我就写了57个,还有8个全局事件负责跳转存档点,如果这些全部用代码来处理,按照一个状态机的初始化平均用15行来计算,光是初始化我就要写1000行的代码,就是ctrl+f也得被我摁烂了!而上图的我只用了30个人时

因为这次的游戏是横版文字冒险类游戏,所以要记录的状态极多,很多物件我都需要存储3种甚至4种以上的状态,而且玩家可以随意在地图走动对物件调查,还有相当多的动画事件和spine小人的演出。例如里面有一个登山者的角色,一开始碰到他会触发一段对话演出,然后和他再次对话会解锁一条支线剧情,在中期这个角色会被杀死,调查他的尸体才能解锁后续剧情,在后期再次调查他的尸体,可以摸出一件某个支线任务的剧情道具(如果这个支线已经解锁的话),是不是光听起来就觉得鸡皮疙瘩都起来了……整个游戏现阶段我用了100+个FSM……没有可视化的工具,光是等待代码编译的时间就是个天文数字。


4、easysave

easysave我是拿来和playmaker一起用的,实际上也就是看中他自带写给playaction的指令,可以很方便地做读档和存档的功能。记得导入后还需要update一下才会有action代码


5、json

文字冒险类游戏怎么少得了文字,这次文本存储的格式我还是选择用了JSON,毕竟JSON的解析速度是要比XML快上一点的。因为这次的编写量很大,所以我用JsonBuddy代替了txt,起码保证了不会因为忘记写逗号或者多写一个逗号导致要重新编译orz

这个我还不太会用,只能当做一个编辑器来用,一些批量替换的操作只能自己用解析器来做,不过平时用起来也不错了。格式编写自己是有点问题的,当初没有考虑是觉得问题,应该要把每个文本元素换成array,这样可以直观地看到文本的id,不然蓝压压一片的找死我了。

Json我之前有篇文章也讲过,不过写的不太好,主要是在build之后发现文本读不出来了,检查好久才发现是找不到Json文件,一开始还傻乎乎跑人家playmaker的论坛里问为什么build之后就无效了orz。我Json文件放在了Resources文件夹里,这个文件夹的东西unity是会在build后压缩的(我也不想让玩家看到文本的Json文件,不然剧透了还有啥意思哈哈),所以像我之前那样用System.IO直接路径搜索是搜不到的,需要用Resources.Load,更改后的代码如下:

        TextAsset json = Resources.Load("Json/Text") as TextAsset;
        itemData = JsonMapper.ToObject<JsonData>(json.text)["Text"];


6.Unity的build有检查模式

在Build界面里选择development build,把下面三个打钩,可以在生成游戏里看到游戏的错误信息,方便debug

猜你喜欢

转载自blog.csdn.net/keven2148/article/details/80641747