此笔记以Mac系统为平台。
任务系统(2019.12.09)
任务系统是MMO的核心系统之一,大致流程为:在NPC处接任务,玩家做任务,条件满足后提示玩家交任务,玩家在某个NPC处交任务获得奖励,周而复始,服务器会追踪任务完成情况,并给客户端发消息反馈。
接任务
一般来讲接任务需要和某个NPC互动,在GameScene
中和NPC交互后调用onHitNpc
方法,然后它会调用TaskData中的reqVisitNpc
方法向服务器请求访问NPC,服务器收到后便会返回一条访问NPC的消息,我们通过消息中的thisid查找NPC的实例,如果找到,则调用消息中的lua脚本(调用前确保dlgMain
方法存在?)。调用方法是用TaskData
定义的方法executeAPI
,它会在脚本Config_TaskAPI
中检索对应的API调用。比如该脚本作为字符串传入,如果我们需要在NPC的对话框上加入可接任务,就可以在其入口函数调用APIAddItem
,将任务描述和条件等参数作为另一个函数传入,它便会调用UIManager
的getPanel
方法得到UITaskDialogue
实例,然后用成员函数addDialogItem
将任务选项加入对话。
//随后在任务配置xml脚本中出发on_visit
条件,判断NPC身上的任务状态,这些是服务器的工作
任务列表
任务面板左侧是当前玩家身上的任务列表,和任务追踪栏略有不同,点击某个任务后会在右侧的任务详情显示该任务的具体描述、奖励以及放弃任务的按钮。任务列表用Cocos中的TableView
实现,创建每个TableViewCell
后根据注册的tableCellAtIndex
方法用索引得到对应的任务数据,然后将其设置到生成的UITaskListCell
实例上,再把这个实例加入TableViewCell
的child。每次任务信息有变动时,客户端收到消息后都会调用_onTaskInfoUpdate
方法更新表现,先说一下它的作用。
玩家可以同一时间追踪多个任务,服务器会通过一条消息告知玩家当前有哪些正在追踪的任务,每次任务状态更新时,都会调用_onTaskInfoUpdate
方法,它会刷新任务数据,对成员_taskInfo
进行遍历,它存有每个任务的任务ID,我们用这个ID对_taskTraceConfig
进行检索,看看玩家当前是否正在追踪这个任务,如果检索到了,就用该任务的状态(state)作为二级索引,继续检索,取出任务配置,然后在在_taskTraceInfo
中储存这个任务的信息,包括ID、类型、描述等。任务配置中有一个包含NPC状态的表npc_status
,我们还要对它进行遍历,通过状态的baseid检查NPC头顶的图标,如果之前没有则添加进本地变量npcTaskState
中,如果之前已经设置过则检查优先级,如果优先级更高则替换。
任务遍历完成后,就需要对显示的任务栏中的任务进行排序,排序规则为根据taskID
及其state得到任务类型(在Enum.TaskType
中定义),顺序为使命 > 考核 > 委托 > 次级使命。下一步更新NPC头顶的状态,前面我们获得了NPC头顶状态的信息并储存在了本地变量npcTaskState
中,脚本类中还有一个成员变量_npcTaskState
,它储存的是上一次NPC头顶的状态,我们需要对其遍历,检查其中的npcBaseID是否同样存在于npcTaskState
中,或是否发生了改变,如果是则需要调用onNpcTaskStateChange
方法进行更新。然后注意还需要遍历npcTaskState
一次,因为可能添加了新的_npcTaskState
中还没有的状态,我们要把它添加进去,同样调用onNpcTaskStateChange
方法。
最后刷新任务追踪界面,分别刷新UITaskTrace
和UITaskList
。
taskIDSort
通过枚举类型(列表和追踪)来储存排序好的任务信息id,这个表每次调用方法_onTaskInfoUpdate
时都会初始化,其内容根据当前_taskInfo
中的信息决定,如果遍历时_taskInfo
中的任务信息没有要让该任务在列表中隐藏的字段,则将这个任务的id加入taskIDSort
,否则不加,之后会进入排序流程。如此一来在UITaskList
中创建cell的时候那些没有在_taskIDSort
中的任务就不会显示在列表中了,因为取任务信息的时候是先取_taskIDSort
中的id,然后再用这个id作为索引在_taskInfo
中查找。
任务详情和奖励
任务完成的详情和奖励都显示在详情面板中,每个任务的奖励有所不同,点选某个任务任务后显示该任务的详情和奖励。
设置面板直接用UITaskList
中的方法setContent
,它会将传入的任务数据提取并分别对不同的区域进行设置,任务详情是一段富文本,如果没有初始化我们要先初始化,然后将创建的富文本设为详情节点的子物体,如果富文本已经存在,则直接将新的详情覆盖。
奖励信息设置前要先将所有已有的item设为不可见,本地所有已有的奖励信息储存在表_rewardItemList
中,这个表只存具体的奖励物品,不关心这个物品到底是哪个任务的奖励,我们设置时遍历传入的奖励信息,先检查一下该奖励是否已存在于_rewardItemList
中,如果不存在则创建,存在则直接将当前的道具baseid和数量覆盖,然后设为可见。
无任务时的面板表现
要求当前无任务时面板隐藏某些UI,并显示文字提示玩家当前无任务。
当某个任务不显示后,如任务完成或放弃任务,面板详情中会显示当前任务列表中第一个任务的信息,如果列表中没有任务,那么就显示“当前无任务”文字提示。每次玩家点选某个任务或调用方法refreshPanel
时,会用setCurTaskID
方法设置当前taskid,同时用setContent
方法设置面板信息,如果传入setContent
方法的参数为nil
,则设置面板为空界面。刷新面板时,默认详情会设置为列表中第一个任务的信息,因此如果此时没有任务,就会传入nil
。
放弃任务
在任务列表中点击某个任务,由于任务详情面板中的每个任务信息都是一个按钮UITaskListCell
,会触发回调函数onTaskNameClick
,随后会调用UITaskList
中的setCurTaskID
方法更新当前选中的任务id,这样在点击放弃任务按钮后就可以给服务器发送放弃该任务的消息。这里发送的是proto消息,格式参照其他功能的写法。
判断任务是否可以放弃时,为服务器发过来的C++消息注册一个新的成员canabandon
,在LuaCmdDef.lua
脚本中按变量名:类型
格式添加进对应的消息结构中。然后将其一并添加到_taskInfo
中,在放弃任务按钮被按下时对其进行判断,如果等于1则弹出确认框,否则直接显示“无法放弃”上漂。确认框直接使用之前写好的g_UIUitl:showPopBox
方法,并将发送消息的方法注册到弹窗的确认按钮。
新的背包需求(2019.12.17)
新增5个不同的道具背包道具类型,分别为普通、装备、副职、任务和特殊,其中只有装备类型有一键丢弃按钮。每个类型默认有5x12一共60个格子,格子可用金币扩展,待扩展的格子上有小锁的标志,点击时出现弹窗询问玩家是否话费若干金币进行解锁,如果选择是且剩余金币足够,则扣除金币然后解锁一排。
页签切换
背包界面右侧有两个页签按钮选择道具与时装,页签按钮被点击后,首先将当前背包类型更新为道具或时装,然后调用onSelectTabType
方法,把之前对应的tableview设为不可见,并激活与当前类型不同的页签按钮(就是说选择了道具就激活时装,选择了时装就激活道具),关闭当前类型的页签按钮,显示当前类型的tableview。
tableview有一个tableCellAtIndex
方法,这里的index指的是其包含tableViewCell的序号,tableViewCell就是我们的行,每行的元素如UIItem
就创建其中,但index只有当用户滑动时才会更新。
新增道具类型
目前两个页签分别是主背包和时装,它们是两个不同的tableview,背包加入的五个子类型都显示在主背包这个tableview上,当点击背包子类按钮时,背包需要知道现在是哪个子类(更新),然后设置按钮状态,我们创建tableview时,创建物品item的函数定义为一个局部函数,再将这个函数作为参数传入UIGridTableView
类,这样tableview这个模块就不用去关心该如何创建item,只用在需要的时候调用这个传入的方法就行了,后面获得格子布局大小时也可以采用这个思路,在不同情况下我们需要背包显示不同数量的格子,而GridTableView其实不用去管这些内容,只用在需要格子数量的时候去调用一个方法就行,这个方法同样可以在创建tableview时由外部传入,由外部去定义,这里就是在UIPack
中去定义。
PackData
中的_packInfo
负责储存服务器发过来的道具信息,它根据背包主类型和子类型计算出的哈希值索引背包的完整信息。
现在只有装备能一键丢弃,proto关于丢弃道具的消息结构加入了一个判断背包类型的变量,之前是只传入thisid就发送,现在为了降低服务器的时间复杂度,把背包类型和子类型也一并发过去。
除了任务背包,其他子背包最后一行在达到最大上限之前都多显示一行带锁的格子,供玩家点击解锁更多格子,如果达到最大上限则不显示。这就要求我们针对最后一行单独处理,如果是任务背包,则直接设为空格子(后面详细讲),再判断当前背包是否达到了最大上限,若达到则按刷新道具的流程刷新道具,否则创建为带锁格子,注意带锁一行是刷新的,不是创建的。
任务背包上限很大,当前只显示初始格子数与其他子背包格子数的最大值的格子,如果剩余的格子小于10,则自动扩展一行,现在是5格一行,也就是说始终要有两行为空
Bug1:tableview要注意手动初始化,因为tableview为了效率只会创建比屏幕上看见的行数多若干行的行数,然后滑动时动态的去赋值,如果没有正确的管理每行的数据,就会出现格子上显示不该显示的道具的问题。
Bug2:装备类型道具装备后会进入“装备”类型的背包,子类型为NONE
,凡是还没有定义子类型的背包,默认类型都是NONE
,这点要注意,因为子类型会影响到哈希值的计算。
Bug3:任务背包更新后没有主动刷新格子,导致扩展的格子不能第一时间看见
时装背包需求(2019.12.25)
增加套装,配置多个占位序号,装备后同时占用多个槽
tableview的reloadData
方法会将tableview的表现刷新,并把视野拉倒初始位置,如果我们不希望刷新后变动当前的视野,那么在调用前先记录y方向的offset,调用后用setContentOffset
将tableview调过去。
时装背包分页根据时装道具表中的新字段tabtype
分类,但与主背包逻辑不同,时装背包实际上只有一个背包,分页按钮只是将背包中的道具分开显示,其他排序等逻辑不变。由于PackData
中已经储存了时装道具的信息,我们不需要在分类时在储存一份,而只需要记录每个tabtype下的thisid,然后通过thisid去索引道具信息。
时装装备后并不会从背包中消失,而是会被标记为正在使用,分类页签下这一表现同样要更新。注意时装卸下和装备时都会收到更新消息,需要查重,如果已经存在相同thisid的道具需要覆盖信息,而不是跳过。
其实这个功能只需要正确的处理数据,UI的各模块便会表现出正确的效果。
Bug1:cocos studio中搭界面时,所有节点均不可重名,否则会导致死循环
Bug2:时装穿戴和卸下时,各分类页签下的道具没有更新是否穿戴的标识。原因是刷新道具信息时,对储存的时装信息查重时跳过了重复的道具,而没有覆盖,因此新的道具信息没有显示
多占位符
现在要求时装可以在装备后占用多个槽位,如果在其他时装穿戴后占用了任意一一个已占用的槽位,那么包含这个槽位的时装就会被卸下。
客户端在显示已装备时装时,是查找由服务器发过来的背包中的道具信息,看看它的信息中in_use
值是否为true
,因此如果要实现将占有多个槽位的时装挤下的效果,就要在收到装备刷新信息时,在UIFashionMain
中正确的刷新八个时装装备槽
Bug1:解析占位符字符串时使用了split方法,返回的是单个数字的字符串而非整型数字,导致作为索引时取不到时装槽位数据
Bug2:只占一格的时装穿卸没有第一时间刷新纸娃娃,处理占位符字符串的时候出了问题
Bug3:背包重开后看不见副槽位的占位图标,因为PackData
中读取时装道具占位符时只对主占位符做了操作
bbl的开发到此告一段落,以后如果有机会再回来续写笔记(叹气2020.01.06)