关于新手引导设计的一些新思路

背景

在游戏设计过程中,新手引导往往是在游戏制作偏后期的时候开始制作,这时候往往功能已经有了相应的完善,而且每个功能都已经独立完整,这时候基本不可能去更改游戏的逻辑部分,也很少有见过预留下新手引导的接口之类的。其中还有一个非常大的问题是,各自有各自的风格,变量命名方式,还有规范性,特别是Lua下基本是各自一个风格。这些都给新手引导的设计上留下了无数的坑。

思路

因为不太了解其它引擎的特性,所以只讨论关于cocos2d-x(lua).
在cocos2d更新到3.x之后,新的cocostudio的加入为整个引导流程带来了很大的便利,这里考虑到新手引导至少99%以上都是对于UI控件的引导,只有极少数是自建的Node的子类。

事件的支持

Widget::addTouchEventListener(const ccWidgetTouchCallback& callback); 方法中,原来的做法是直接覆盖掉原来的旧的事件监听,我把这里改成了一个std::map ,用一个uint32_t做为key,来作为一个事件的handle.可以用来删除事件。每次事件触发都把整个map中的事件监听执行一遍,新手引导的事件无非就是触发一个事件跳到下一个引导,这样有个好处就是可以在任意能够获取到需要引导的控件的地方加入一个新的事件,这样就能在事件结束之后删掉事件监听,完全不影响原来的控件和系统,实现解耦,同时还能获取和真实事件完全一样的事件,如按钮结束了发送消息跳转还是移动跳转都可以随机应变,而不是在Dispatcher注册一个监听来拦截消息。而且这整个过程对于Lua来说是非常容易支持的,找到相应功能的设计者,获取相应的控件,注入新的事件,引导结束,销毁新的事件,进入下一步。

例如:

1
2
3
4
5
6
--function1.lua  功能1的lua文件
UIDialog = {}

UIDialog.uiButtons = {}
---....省略注册过程
---假设UIDialog.uiButtons里有N个UI::Button并且都有各自的事件,其中有非常多复杂的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
---Newbie_Guide.lua 新手引导
Guide = {}
function Guide.init()
local listenerID = nil
local function guideListener(sender,event)
if eventType == end then
--发送消息给服务器完成引导,进入下一步引导
sender:removeEventListener(listenerID)
end
end
listenerID = UIDilog.uiButtons[x]:addTouchEventListener(guideListener)
end

通过这个过程就能把新手引导从功能中剥离出来。同时可以把这个方法用于多个各种类似的场景。用于解耦。

pipeline

虽然使用另外添加事件能够完成大多数功能,甚至于传入一个可点击控件名称或者Tag就可以了。但是实际使用中还是会遇到很多奇怪的问题,比如要引导的控件是在ListView中,或者有重名之类的。这里把每个引导步骤当成一个pipeline:
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GuideStepInfo = {} --用于初始化步骤信息
GuideStepInfo.onBegan = function()

end
GuideStepInfo.getGuideBtn = function()

local guideBtn = .....
return guideBtn
end

GUideStepIinfo.finished = function()
--send info
end

GuideStep = class("GuideStep")

function Guidestep:ctor(guideInfo)

end

相当于每个过程都是可编程的,而不是固定于名字,但是可以在guideStep加入一个默认的用于处理通用情况。有点类似于OpenGL..所以想到这里就加入了一个状态机。用于主循环每个步骤,调用相应的方法。然后把最终结果用一个UI显示到界面上。通过这种方法能够一定程度上解除新手引导带来的耦合性。