DevNotes_190308
重构极其重要和必要:
放弃沉没成本,不要因为已经有的为即将到来的埋榴。该重构的即可开始重构,可以花一周左右的时间去重新整理需求和实现思路,也包括策划们,可以花一周时间整理一下设计,把混乱的玩法和系统需求梳理一遍。
一些脑洞的功能最后没能实现;不论什么怪物刷出来都像丧尸一样“没有脑子”;或者是刷个怪真不容易,海量的数据要填写。是的,这些感觉都没错,因为我们总认为刷怪是个简单的小功能,从来不重视他,甚至会把一些真实的需求给搪塞了,以至于最后刷怪就被默认为是这么一件做出来效果不咋的的体力活。
但是为什么我们很少在国游里看到这些落实呢?因为背后的代价太大了,要做这个效果,在没有好的设计的情况下,简直是天方夜谭。所以我们接下来就要开始提炼需求了。
这个是个很容易想到但实际上并不好用的设计想法:
而有效的、易扩展的应该类似于这个
即美术做了一张地图,所以全世界就有了这么一张地图,而不是一张地图可能是有2张策划设计的地图,以及服务器运行时候n个副本
刷怪时,需要依赖 地图区域数据
这个也可以叫做locationModel,区域信息
它是静态的数据,比如包括
+ string id;区域名称
+ array<string> Tag ,区域tag,如果有的话,
+ polygon(or rect) Area 坐标区域;
这就真的只是静态数据,而不提供逻辑作用:
即比如这个区域在什么区域下有什么反应是与区域信息无关的;
·:隶属于这个区域的怪物,作为索引信息,可以细节地、充分地对这个区域做各种各样的限制;
·:刷怪倒计时,还可以有多个倒计时;
·:刷怪条件,SpawnInfo
→→:扩展的刷怪条件,比如各种 返回bool的函数们;候选怪物信息,
“将一个怪物的填表数据“MobModel””,变成运行时的characterObj,这里除了索引信息,还可以有一些动态数据;
一些动态数据,是不应该属于怪物表的,比如等级、掉落、等级、ai段、buff信息(此处,例如刷在雨中的敌人不会着火,这个不应该是刷新时带的,而是刷新出来后才带上的)
反过来验算:
带着我们之前的脑洞,回过头来看看这些功能能否实现,以及一些相关的玩法功能能不能实现,要尽可能的刁难自己,因为越是刁难,越是会出现边际情况,越是可以催促我们返回去进一步设计。
设计这些做法为的是让人更容易发挥,所以从一开始就应该考虑的是如何更容易维护的开放式思维,而不是开始就想好有哪些约束,让别人只能在约束下设计,这样是有违设计精神的。
1,设计任务机制的基本思想”You call me, I don’t call you”。
假设我是一个系统,另外一个系统需要我提供相关数据的时候,仅仅由它主动来访问我,而我从不向他提供任何帮助
回调,回调,回调。
触发的灵活性、可扩展性远比 监视 要好得多。
甚至他还可以是个buff系统,比如我们采集了4/10个道具,就是4层buff,那么任务系统可以只检测这个buff系统(以及进行tick)而不是监视玩家的每个行为;
在实际研发过程中,这种”You call me”的任务机制,对于程序员的开发来说是非常清晰的——
Step1:在写功能的时候注意回调点。
Step2:提供3个接口来提高任务进度、降低任务进度、直接完成或者失败某个任务。
Step3:调试并确保可用。
回调的点,是不关心任务的具体数据的,比如要找npc说话,威胁他;
那么实际上
____在NPC的表中,加入 和某个npc谈话时,增加一个TAG,类似为,“和npc说话并威胁他”,给这个tag加一,然后检测玩家是否在执行这个任务,如果是就给这个TAG真的加一,否则就跳过;
eg:
玩家获得4种道具,之后如果将辣椒和汽油组合就能获得阿拉伯神油(现实中配方并非如此,我先声明——如果你照做后果自负),但是玩家尝试过程中也许会失败几次。因此我们制作这样的任务的时候,就需要在每次Craft时候去执行:
1)给玩家添加一个记录尝试次数的Buff,通过这个Buff的层数,改变完成任务时候任务给于者说的话,当然你也可以给不同的buff,根据buff组合说出更有趣的话。
2)当Craft出神油的时候触发任务进度提高。
8楼的例子:
我觉得……可能这么描述这个流程比较好理解些:
1,怪物死亡,并发出信号给所有符合和怪物接触的人(在WOW中就是击杀怪物的人)。
2,接受到信号的人处理这个信号。
我们用伪代码来进一步说明就是:
怪物mob下有一个列表记录和怪物接触的人(其实在WOW中,一般情况下就1人,一些共享掉落的怪物则会有很长的列表,只要你打了就在列表内,激战2则采用后者,只要你打过就在列表内),这个列表假设为killer:Array<Player>。
那么怪物的die()中就应该有
for (guy in killer){
guy.increaseQuestFinish(mob.questSign);
}
通过这个发出信号给相关角色,而increaseQuestFinish执行的则是
if (this.currentQuest.exists(sign)){
…..
}
类似这样的去处理掉这个任务进度的问题。但之前的游戏大多地做法则正好相反——
for (i in 0…guy.quests.length){
for (j in 0…guy.quests.condition.length){
……
}
}
这样的做法下就存在很严重的效率问题,即使你杀死的是一个根本和任务不相关的怪物,你也要去遍历一次,这就成了”I ask u”了,所以需要”u call me”来提高这件事情的效率,而”u call me”的思想其实并不仅仅用于杀怪,包括比如一个任务让你学蛙跳100次,你可以在每次蛙跳时Call一下,而不是因为任务系统的完成条件中带有蛙跳100次,所以总是在遍历。
这样的设计其实越早提出越好,当然所有的设计都是如此,但是在项目后期,其实要去改进就要看之前的代码构架了,一般来说如果用的是entity system的思路,会非常简单。当然很多时候程序员不乐意改也处于很多其他原因,所以逻辑层一般来说,我们这里策划自己动手。
13楼的tag举例:
我就举一种Tag机制的灵活运用开拓一下思路好了。
一个怪物击杀的QuestTag(暂且叫这个,反正就是杀死时影响任务变化的),一个火焰歌不林,如果你填写:goblin_humanoids_area03_schoolfire,那么你可以让程序用“_”作为分割标记来看,用伪and运算和任务的条件的tag标志产生关系:
比如有个任务叫消灭火源,他的任务目标是杀死区域内100个带火特性的生物,你只需要把这个条件的Tag定义为schoolfire,那么(schoolfire and goblin_humanoids_area03_schoolfire)肯定是==schoolfire的,两者都有schoolfire,那么这个哥不林就在这个任务的击杀范围内了。
此时你心添加了一个任务叫做屠夫,条件之一是杀死这个区域内的人形生物20个,只需要将这个条件的标志定义为”humanoids”,哥布林一样在范围内。此时你又设计了一个消灭区域3内所有哥布林的任务,它的条件之一是”goblin_area03″,那么这个哥布林仍然是符合范围的,但是另外一个哥布林(goblin_humanoids_schoolwater)就不符合条数少的那个,因为他而事实上你在填哥布林的时候并没有想过有这么多任务。
所谓的“分析请求”实际上就是这个管理中枢的核心工作
内含中枢的举例;