谈谈游戏中新手引导是如何制作的

43 篇文章 1 订阅
33 篇文章 0 订阅

转自:https://blog.csdn.net/operhero1990/article/details/51482734

目前已经历了两款游戏的制作。而两款游戏的新手引导,都是由我来完成的。因此,想写篇文章记录制作新手引导过程中的一些心得。

一、新手引导的分类
从触发方式上,引导分为强制引导和非强制引导。现在国产游戏上来就是一大段的强制引导,强制玩家点击某一区域来熟悉游戏。强制引导过程中,玩家没有多余的操作选择。这种方式虽然受到了很多的诟病,但依然大行其道,这背后的市场数据分析暂且不予讨论吧。而非强制指引,是根据游戏进度和剧情需要,触发不同的引导。再我看来,非强制指引,只不过是由某种特殊事件触发了一段强制指引行为而已。所以,强制指引是实质,非强制指引只是再次基础上做了灵活运用。

在来看看强制指引。大体上可以分为对白和指引点击两种情况。对白用于介绍剧情,指引点击则指引玩家具体操作。由于项目使用的是cocos引擎,且游戏逻辑使用lua脚本编写,因此下面将以cocos-lua方式距离说明。

二、新手引导的实现
如何构建一套完整的新手引导系统呢?最终要的就是为策划构建一张新手引导表格,再以此表格为依据,实现一个管理类guideMgr以及相应的表现UI(假设我们按上节提到的对白和指引的分类方式,则需要分别实现guideDlg和guideOper)。

<表格>

那一张新手引导表格需要哪些内容呢?这就需要设想新手引导会遇到哪些问题:

1、同步服务器?告诉服务器我完成到了哪一步,一次来获得相应奖励或判断完成新手引导

2、重启游戏后继续引导?因各种原因重启游戏后,需要从上次中断处继续引导,第一个问题解决了,这个问题也好解决

3、游戏随机性大,如何确保引导不出问题?因为新手引导需要确保流程的通畅,以为游戏中的偶然因素需要排除。可能需要做一些特殊的处理,比如构建一些虚拟数据来取缔原来的游戏随机数据

4、如何指引特定界面的特定按钮?如何确定上一步指引打开了指定界面,并且当前指引了特定的按钮

带着以上问题,我们来设计引导表格:

指引id。id有两个作用,第一个是用于客户端与服务器的同步,第二个是方便客户端使用id做特殊处理。这是为了解决问题1、2、3。

类型变量type。他的作用是指明这是什么指引类型(对白or操作指引)

指引所在场景ui。解决问题4。

指引按钮widget。解决问题4。

是否同步sync。解决问题1。

当然表格里还可以有其它内容,比如显示的图片路劲,文字内容以及面对各种复杂情况的需求(实际解决问题的时候,自行扩充吧)

假设我们需要指引玩家签到,正常流程是在主场景或主UI(MainScene)上点击签到按钮(btnSign),签到界面(SignUI)打开后,点击签到(btnOK),最后关闭签到界面(btnClose)。注意,如果玩家在1002步点击了签到按钮,这时候他退出了游戏,重进游戏后,应当判断他完成了签到指引而跳过1003步。所以1002步上传给服务器的同步id应该是1004。这样,重启游戏时,客户端从服务器获得同步id1004,从1004继续指引。

<guideMgr>
guideMgr首先需要同步服务器指引信息。通过syncFromServer(id)来同步从服务器发来的信息(在进入游戏时执行),syncToServer(id)来向服务器同步信息。

其次,guideMgr需要知道指定界面是否打开(由于某些界面需要网络返回,因此不是及时打开,这时候指引应该被挂起,直到等到特定界面)。如果你的游戏中有UIMgr,那这件事情就相当好办。当通过UIMgr创建一个界面时,调用guideMgr.addUI(uiname, uiInstance)方法,通知guideMgr你所需要的界面已经打开。通过UIMgr关闭界面时,调用guideMgr.removeUI(uiname)来删除记录。

其次,guideMgr提供方法stepEx()供外部调用。当指引所在场景的指引按钮被点击时,主动调用guideMgr.stepEx(),告诉guideMgr我完成这一步指引啦,快点开始下一步吧!

local guideMgr = {}
local isGuiding -- 是否开启指引
local isSearchingUI -- 等待指引的场景打开,进行指引
local uis -- 打开的界面集合
local guideIndex = 1 -- 指引的顺序索引
local guideInfo -- 当前引导信息

local function startCurGuide()
    if guideInfo and uis[guideInfo.ui] then 
        isSearchingUI = false
        if guideInfo.guideType == 1 then
            UIMgr:showUI("UI_guideDlg", guideInfo)
        elseif guideInfo.guideType == 2 then
            UIMgr:showEx("UI_guideOper", guideInfo, uis[guideInfo.ui])
        end 
    else
        isSearchingUI = true
    end
end

local function finishCurGuide()
    if guideInfo.guideType == 1 then
        UIMgr:removeUI("UI_guideDlg")
    elseif guideInfo.guideType == 2 then
        if uis["UI_guideOper"] then
            uis["UI_guideOper"]:onEnd()
            UIMgr:removeUI("UI_guideOper")
        end
    end

    if guideInfo.sync ~= -1 then
        Framework.Uti:SendMsg("cSyncGuide " .. guideInfo.sync)
    end
end

function guideMgr.init()
    guideIndex = 1
    isGuiding = false
    isSearchingUI = false
    uis = {}
    guideInfo = nil
end

-- 添加当前打开的所有UI界面
function guideMgr.addUI(uiname,uiInstance)
    uis[uiname] = uiInstance
    if isSearchingUI then
        startCurGuide()
    end
end

-- 移除关闭的界面
function guideMgr.removeUI(uiname,ui)
    if uis[uiname] then
        uis[uiname] = nil
    end
end

-- 同步服务器数据
function guideMgr.syncFromServer(idx)

    -- 没有同步数据则从第一部开始指引
    if idx == -1 then
        guideInfo = game.data["Guide"][1]
    else
        for i = 1,#game.data["Guide"] do
            if idx == game.data["Guide"][i].id then
                guideInfo = game.data["Guide"][i]
                guideIndex = i
                break
            end
        end
    end

    if guideInfo == nil then
        isGuiding = false
        uis = {}
        return
    end

    guideMgr.run()
end

-- 重进游戏后,一些断开的步骤需要重新打开之前的界面
function guideMgr.restartCheck()

end

-- 开始新手引导
function guideMgr.run()
    isGuiding = true
    guideMgr.restartCheck()
    startCurGuide()
end

function guideMgr.stepEx()
    if isGuiding == false then
        return 
    end

    finishCurGuide()

    if guideIndex < #game.data["Guide"] then
        guideIndex = guideIndex + 1
        guideInfo = game.data["Guide"][guideIndex]
        startCurGuide()
        return 
    end

    Framework.Uti:SendMsg("cSyncGuide over")
    isGuiding = false
    isSearchingUI = false
    guideInfo = nil
end

-- 是否进行新手引导
function guideMgr.isGuiding()
    return isGuiding
end

-- 获取当前引导信息
function guideMgr.getInfo()
    return guideInfo
end

return guideMgr

以上就是guideMgr部分的代码,其中startCurGuide()依据类型分别创造不同的指引UI(guideDlg or guideOper)
<guideDlg>
这部分暂略吧,无法就是现实一些图片和一些文字,点击屏幕任意位置就结束现实,调用guideMgr.stepEx()进入下一步

<guideOper>
在guideMgr的startCurGuide()方法中,创建了guideOper,并将指引信息(表中当前指引行数据)和需指引界面实力uiInstance传递给guideOper。uiInstance需要实现三个方法,uiInstance.onGuideBegin(id)、uiInstance.onWidgetTouched(id, touchEvent)、uiInstance.onGuideEnd(id),实现指引前的数据初始化(如果需要),点击操作的响应(需要相应地方调用guideMgr.stepEx()结束这一步的指引),指引完成后的处理(如果需要)。这部分也不打算贴代码了,因为实际游戏中,有些特殊情况需要特殊处理(比如有点击指引,也有拖拽指引。有高亮点击区域的美术需求,有指引特效等等),实际代码比较杂。guideOper是遮罩在游戏层之上的,他的底层baffleLayer会吞噬点击事件(让玩家不会乱点),并且依据需要指引的widget,初始化touchLayer大小与位置。

当touchLayer接收到点击事件后,会调用uiInstance.onWidgetTouched函数来向下传递点击事件。

源码实现比较简单,这里提供一段有用的代码,就是让点击区域高亮,其余地方用半透明黑色蒙版遮罩,先看效果

再上代码:

local function highlightWidget(widget)
    local clip = cc.ClippingNode:create()
    clip:setAnchorPoint(0, 0)
    clip:setPosition(0,0)
    clip:setName("guideClip")
    clip:setInverted(true)
    clip:setAlphaThreshold(0)

    self.uiInstance.csbNode:getParent():addChild(clip)

    local layerBg = cc.LayerColor:create(cc.c4b(0, 0, 0, 150))
    clip:addChild(layerBg)

    local stencil = widget:clone()
    local ach = widget:getAnchorPoint()
    local p1 = widget:convertToWorldSpace(cc.p(0,0))
    stencil:setAnchorPoint(cc.p(0,0))
    stencil:setPosition(p1)

    clip:setStencil(stencil)
    clip:setInverted(true)
    clip:setAlphaThreshold(0.0)
end


看下cocos裁剪节点的知识就懂啦!
先写这么多,实际过程中,还有许多特殊情况需要处理。这里只是实现一套通用的基本框架。后续可能会继续补充~

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值