Minecraft 为何没有明确的任务和新手任务引导引导?

最新消息:
你的位置: >
> 做好新手引导的3个建议
说到新手引导,可能很多游戏都遇到过玩家的吐槽,由于游戏的玩法太复杂,开发者们在一开始会给出长达十多分钟甚至半小时的新手引导,这样不仅让玩家们觉得反感和无聊,也会延长游戏研发的周期。那么优秀的新手引导到底该怎么做?
最近,一名海外资深游戏策划在博客中分享了一些经验:
在本月初,我的Twitter上有很多人抱怨有些游戏的新手引导完全让他们无从下手,这个话题很快就消失了,但这让我想起了一个非常有趣的问题:你做新手引导到底是为了什么呢?
《我的世界》做法:完全不做新手引导
开发商们是如何改变了做新手引导的方式?像《我的世界》、《Dark Souls》以及《DayZ》等几乎没有新手引导的游戏为何对行业产生了巨大的影响?或许,对于开发者们来说最重要的是,如果你想要通过新手引导吸引玩家,如何才能够做好呢?
最近,我一直在想这个问题,所以跟一些比较有经验的开发者们针对新手引导的问题交流了意见。
如果要做渐进式,确保个性化
资深游戏策划Brenda Romero认为,在新手引导这个问题上,没有一个可以适合所有游戏的***,甚至也没有适合所有游戏的多个方法。她说,“这取决于单个游戏,玩家以及发行商等方面,你的用户量越大、用户年龄越年轻,你的新手引导就要做的越平滑。”Romero也承认,的确存在一些例外,比如《我的世界》,这款游戏中没有新手引导,而是创造了巨大的在线社区,让玩家们自己去探索。
而其他有经验的策划则表示,也有的开发商把新手引导做的‘太平滑’,这样会让玩家们反感,尤其是已经玩过这类游戏的用户。
不要让玩家觉得你限制了他们的操作
《光明旅者(Hyper Light Drifter)》游戏的策划Teddy Diefenbach认为,做游戏的新手引导完全取决于开发者,不过有2种方式是非常容易被玩家们接受的。
Diefenbach说,“第一个方法就是完全避免新手引导,在一些游戏中,玩家们的探索过程本身就是一种独特的玩法,《光明旅者》就是这么做的,我们相信玩家们会进行探索和实践,发现我们游戏战斗的有趣之处。”他表示,这样的做法可以让玩家们在发现特定功能的时候获得成就感,有些时候,只有开发者提供了足够的探索空间,玩家们才可以获得这样的体验。
如果你的游戏中无法实现这样的功能,Diefenbach建议使用‘分散式’的新手引导,可以根据玩家们的要求指引他们获得新的能力。他说,“分阶段的解锁一些功能按钮,这样玩家们可以了解他们所需要的每一个功能,你要让玩家们一开始就可以体验游戏,在玩家需要某些信息的时候才给他们提供。”
《镜之边缘》截图
独立开发者Adrian Chmielarz对此表示同意,不过他同时指出,这样的方式也并非十全十美,如果你在太短的时间内让玩家们进行特别复杂操作的话,很可能让玩家觉得沮丧。他拿《镜之边缘》为例,“当这个游戏很快的从‘左键移动’到‘多键结合’的时候,我立即就流失了,而且再也不想玩这个游戏了。”
如果你不想在短期内给玩家推出太多的东西,最好还是使用PENS(Player Experience of Need Satisfaction)模式。这种模式下,玩家们可以根据需要而不断的解锁游戏玩法,这种模式需要自己发现游戏中的所有功能。
给玩家们自己发现的空间
对于Mohawk Games共同创始人 Soren Johnson来说,让玩家们适应新手引导的方法,既不是面面俱到也不是完全的忽略,而是通过玩家进度决定。他说,“我觉得在游戏中为玩家们提供发现新事物的空间是非常重要的,新手引导会剥夺玩家们发现的机会,迫使他们一次性的学习很多东西。”
《文明5》截图
Jonhson从事策略游戏研发,他说,“我更倾向于依靠游戏内的悬停帮助菜单,也就是说,玩家们在想要得到帮助的时候可以自行点击,而不是要手把手式的教学。”他指出,《文明5》在这方面就做的非常成功。
Johnson说,“这个游戏只告诉你有新手攻略,你可以打开攻略去了解特定的功能,《文明5》在这方面做的非常出色。”他还对《植物大战僵尸》策划George Fan的方法非常赞同,在2012年的GDC大会上,Fan分享了自己的新手引导方法:让玩家不知不觉的学到东西。
Fan说,“非常奇怪的是,很多游戏更像是一种学习体验,人们喜欢学习,而且学习本身也是非常有趣的。但大多数的人们都不知道自己是喜欢学习的,这也是我们做游戏的从业者必须了解的。”
转自:GameLook
转载请注明: &
与本文相关的文章资料图鉴:教程速查:
当前位置:
我的世界红石新手教程引导大全
小编:3F时间: 16:04
  五花八门的红石玩法等你探索,今天周日,小编送来了红石粉们的最好,各类红石玩法,只有你想不到没有红石做不到的,不会玩红石的玩家速速收藏。
分享到:更多
类型:休闲娱乐平台:PC,iOS,安卓
游戏大礼包手游开测表
血族国庆狂欢享乐礼包青丘狐传说金秋畅嗨礼包风暴幻想十一国庆双端礼包赛尔号超级英雄国庆回馈礼包夏目的美丽日记十一国庆礼包舞创天团新服礼包
10-12内测09-30内测09-28内测09-28封测09-28内测09-27内测09-27封测09-27内测09-27内测09-27内测
攻略推荐本月最新
手游排行网游单机【AS3 Coder】任务九:游戏新手引导的制作原理(上) - 推酷
【AS3 Coder】任务九:游戏新手引导的制作原理(上)
使用框架:AS3
任务描述:了解
游戏中新手的制作原理及流程
本章源码下载:
有人问我,都两年过去了,AS3&Coder系列怎么才出了10篇文章都不到?***很简单:我TM懒得写!原计划出到10篇就洗手不写了,现在还有最后两篇,加把劲冲刺一下吧!
新手引导基本上在每个游戏中都会出现,或长或短,或简单或复杂,当然,新手引导流程越长越容易出现BUG,且传统的新手引导做法会极大地破坏代码的耦合性,为了解决“不稳定”及“破坏耦合性”这两个问题,贫道想了一种相对较好一点的(到底是好还是不好,列位看完本文之后就仁者见仁智者见智了)方式,在本文中介绍给大家。
传统的新手引导制作方式
传统的新手引导方式一般是设置一个全局的静态变量来保存当前新手引导进度,然后在项目中每个可能出现新手引导的位置添加一句判断:若当前新手引导步骤等于我所期望的步骤就执行引导部分的逻辑。例如,一个游戏中的新手引导第四步是引导用户去打开一个A窗口,然后第五步引导则是引导用户点击A窗口中的某个按钮。那么在A窗口的“打开窗口”函数中就会加上对当前新手引导步骤的一个判断,若当前步骤等于5,就执行相应的新手引导逻辑(例如,给A中需要引导用户点击的按钮加上一个箭头神马的)
public&function&onOpen():void
&if(&GuideManager.currentStep&==&5&)//GuideManager.currentStep是一个静态变量,用于存储当前新手引导进行到的步骤&&&
&{&&&&&//执行相应的引导逻辑&&&}
这种做法的弊端在于,它破坏了代码的耦合性,因为新手引导每涉及到一个组件,就需要在该组件中添加相应的判断语句及引导逻辑。而且当新手引导的步骤一多之后就会出现不稳定的情况,最烦人的是,一旦策划要求你在新手引导中插入几个步骤,那你几乎全部的涉及到新手引导的组件都会遭殃(原先if语句里的判断条件都会发生变动),而且你涉案组件那么多,难免会漏改几个位置。
基于接口的编程----降低耦合度的最佳方式
为了降低新手引导对项目耦合度的破坏性,有人提出了使用“继承”的方式,即游戏中的全部可视化组件都继承于同一个父类,然后在这个父类里面写上新手引导相关的逻辑代码,这样就可以一劳永逸了。不过话说回来,不一定全部涉及到新手引导的类都是继承自你这个共同的父类,而且使用你这种继承的方式来做,就会让全部继承自该共同父类的那些子类里面多出很多冗余的代码(因为只有极少的一部分子类会涉及到新手引导),尤其是在未启动新手引导时(玩家已经完成全部新手引导之后每次登陆游戏都是不会启动新手引导的)产生极大的浪费。
使用“接口”(Interface)的编程思想来做,就可以仅给有需要出现的新手引导的类添加相应的代码,最大限度地避免浪费的产生。
所谓“接口”的作用,就是
让没有继承关系的类之间产生联系
。让我们先来看一个小例子吧。现在我们有两个窗口类A与B,它们之间没有继承关系,虽然如此,但它们还是有共同之处的,就是都声明了一个名为“open”的打开窗口的方法,当执行该方法时,A/B窗口就会被打开。现在我想创建一个格式如下的方法:
public&function&openWindow(&win:*&):void
&&&&win.open();
使用openWindow方法,我可以快速地打开一个窗口,而A和B类的对象都有可能被当做参数传入该方法中,但是由于A、B两个类之间没有共同的父类,所以我openWindow的参数类型只能写成通配符(*)。当然,使用通配符是存在隐患的,因为传入的参数很有可能不具备一个名为open的public方法,这样的话就会发生报错。
为了将A、B联系起来,我们此时可以声明一个接口,该接口中声明了A、B类中所拥有的那些个同名函数和属性(在该例中A、B所拥有的同名函数只有一个open方法):
public&interface&IWindow
&&&&/**&执行打开窗口逻辑&*/
&&&&function&open():
声明完该接口之后,需要让A、B实现该接口:
public&class&A&extends&AP&implements&IWindow
&&&&public&function&open():void&&&&
&&&&&&&&&//do&something&&&&
//--------------------------------------------------------------//
public&class&B&extends&BP&implements&IWindow
&&&&public&function&open():void&&&&
&&&&{&&&&&
&&&&&&&&//do&something&&&&
现在,A、B类中的open方法都是对于接口IWindow的一个实现。之后,不论一个对象是A类型还是B类型,我们都可以使用IWindow的类型来引用它,这样的话,我们之前定义的openWindow方法就可以改成:
public&function&openWindow(&win:IWindow&):void
&&&&win.open();
参数win的类型不再是全部类型(*),而是将范围缩小到了所有实现了IWindow接口的对象,由于所有实现了IWindow接口的对象中都一定会有open方法,所以我们可以放心大胆地调用win.open()而不必担心再报错了。
&&“接口”不同于“继承”,继承必须按照从上至下的层级顺序,且一个类只能于一个父类,但接口却不同,一个类可以实现多个接口,如下类就同时实现了两个接口:
public&class&A&extends&AP&implements&IWindow,&IFucker
&&&&//class&body
正是因为接口的这个特性,使我们“仅给需要类添加代码”的假设成为了可能。下面,我们就来写一个接口,该接口约定了一些新手引导过程中将会用到的方法和属性:
&/**&*&如果某个面板将在新手引导中出现,那么它必须实现该接口&&*&@author&S_eVent&*&&*/public&interface&IGuideComponent{/**&处理新手引导相关事务&*&@param&data执行当前步骤时需用到的数据&*/function&guideProcess(data:Object=null):/**&执行新手引导卸载相关清理工作&*/function&guideClear():/**&注册到GuideManager中的实例名&*/function&get&instanceName():Sfunction&set&instanceName(value:String):}
该接口定义了两个方法(guideProcess及guideClear)和一个属性(instanceName)。在新手引导过程中会用到的类,都需要实现该接口才可以。使用基于接口的编程的好处有以下三点:
1.避免冗余代码的产生,哪个类需要实现新手引导的功能就让哪个类来实现该接口;
2.方便查找:你只要在项目中搜索该接口的引用位置,就可以一次性找全全部涉及到新手引导的类;
3.方便管理:全部与新手引导有关的逻辑代码都存放于名为guideProcess的函数中,某组件涉及到的新手引导步骤执行完毕后的清理工作都放在guideClear函数中。
新手引导管理器
为了便于管理和查询新手引导各步骤,我创建了一张XML表guide.xml用于记录新手引导的各个步骤,其格式如下:
&?xml&version=&1.0&&encoding=&utf-8&?&
&!--&Author:S_eVent
说明:节点名请保证使用step,否则将不被识别。
每个节点中的必须属性如下:
&sequence:显示此步骤出现的次序
&instanceName:此步骤所关联的实例名称
每个节点中的可选属性如下:
&subSeq:子步骤。某些界面可能会涉及到多次引导步骤,在每次步骤时执行的逻辑都不一样。此时用该属性来识别当前步骤该干嘛
&&step&sequence=&0&&instanceName=&ButtomButtonBar&&subSeq=&1&/&
&&step&sequence=&1&&instanceName=&Window1&&subSeq=&1&/&
&&step&sequence=&2&&instanceName=&Window1&&subSeq=&2&/&
&&step&sequence=&3&&instanceName=&ButtomButtonBar&&subSeq=&2&/&
sequence属性用以标示新手引导的步骤号;
instanceName则表示负责展示该步引导的实例名;
subSeq用于区分同一个组件展示出来的两个不同步骤。比如在上面的XML里面,“Window1”这个实例将负责展示步骤1和步骤2的新手引导,展示步骤1时,“Window1”这个窗口将引导用户点击窗口中的某个功能按钮(比如在该功能按钮上加一个箭头),而展示步骤2时,“Window1”就将引导用户点击窗口右上角的关闭按钮来关闭窗口。subSeq属性将会被传入&Window1&类的guideProcess方法中用于区分当前引导步骤应该执行哪个引导动作(是该引导用户点击窗口中某个功能按钮还是关闭按钮)。
在某个用户登录游戏时,服务器端会判断该用户是否需要进行新手引导,若该用户需要进行新手引导,那么咱们Flash前端就需要加载guide.xml以获取新手引导的步骤数据。实现代码如下:
private&function&onGameStart(&e:Event&):void
&&&&if(&_needGuide&)
&&&&&&&&loadGuideXML();
private&function&onResize(&e:Event&):void
&&&&_globalVariables.stageWidth&=&stage.stageW
&&&&_globalVariables.stageHeight&=&stage.stageH
private&function&loadGuideXML():void
&&&&var&loader:URLLoader&=&new&URLLoader();
&&&&loader.PLETE,&onGuideXMLLoadComp);
&&&&loader.load(&new&URLRequest(&guide.xml&)&);
private&function&onGuideXMLLoadComp(e:Event):void
&&&&var&data:XML&=&XML(&(e.currentTarget&as&URLLoader).data&);
&&&&var&guideData:Array&=&[];
&&&&for&each(var&x:XML&in&data..step)
&&&&&&&&guideData.push(&xml2Object(x)&);
&&&&guideData.sortOn(&sequence&,&Array.NUMERIC);
&&&&function&xml2Object(&xml:XML&):Object
&&&&&&&&var&obj:Object&=&{};
&&&&&&&&var&attributes:XMLList&=&xml.attributes();
&&&&&&&&for&each(var&a:XML&in&attributes)
&&&&&&&&&&&&obj[a.name().toString()]&=&a.toString();
&&&&&&&&return&
&&&&GuideManager.setUp(&guideData&);
&&&&GuideManager.start();
&加载完guide.xml之后我们需要将XML中的每一个标签都转换成相应的Object对象便于使用,最后把全部步骤对象放进一个数组中传递给我们接下来要介绍的新手引导管理器GuideManager类的setUp方法进行新手引导的启动工作,稍后,在需要开始新手引导时调用GuideManager.start方法开始新手引导的播放。
GuideManager类负责调度新手引导的暂停与播放,它提供了一系列static的静态方法,在项目中任意位置都可以调用到这些方法。
&*&新手引导管理器。请确保只有需要进入新手引导时才调用其setUp方法。
&*&@author&S_eVent
public&class&GuideManager&
&/**&指示符容器。高亮边框、引导指针等指示符都会被添加于此容器之上。若不设置值,则无法显示指示符&*/
&public&static&var&stage:S
&/**&新手引导完成一个步骤之后执行函数。此函数需接受一个Object型参数,代表当前完成步骤的配置数据&*/
&public&static&var&onStepFinish:F
&/**&新手引导播放完成后执行函数&*/
&public&static&var&onGuideFinish:F
&/**&注册成员地图。格式为{className1:IGuideComponent,&className2:IGuideComponent,&......}&*/
&private&static&var&_memberMap:Object&=&{};
&/**&新手引导播放队列,其中元素为每一步的实例&*/
&private&static&var&_guideQueue:Vector.&IGuideComponent&;
&private&static&var&_isSetUp:Boolean&=&
&/**&当前执行的步骤索引&*/
&private&static&var&_currentStep:int=-1;
&/**&下一个将执行的步骤索引&*/
&private&static&var&_nextStep:int=0;
&/**&记录新手引导具体步骤的数组。其中元素为每一步的实例名&*/
&private&static&var&_sequenceArray:A
&/**&记录新手引导每步所包含数据的数组&*/
&private&static&var&_dataArray:A
&/**&完成步骤列表。键为步骤序号,值为true/false,表示是否完成&*/
&private&static&var&_finishList:O
&private&static&var&_paused:B
&/**&存储一切当前使用的遮罩对象&*/
&private&static&var&_maskHome:Object&=&{};
&private&static&var&_border:S
&/**&启动新手引导&*/
&public&static&function&setUp(&config:Array&):void
&&if(&_isSetUp&==&false&)
&&&_isSetUp&=&
&&&_sequenceArray&=&[];
&&&_dataArray&=&[];
&&&_finishList&=&{};
&&&var&len:int&=&config.
&&&for(var&i:int=0;&i&&i++)
_sequenceArray[i]&=&config[i].instanceName.toString();
_dataArray[i]&=&config[i];
&&&_guideQueue&=&new&Vector.&IGuideComponent&();
&&&for(i=0;&i&&i++)
_guideQueue[i]&=&_memberMap[_sequenceArray[i]];
&/**&卸载新手引导&*/
&public&static&function&uninstall():void
&&if(&_isSetUp&)
&&&_isSetUp&=&
&&&if(&_currentStep&&=&0&)
doClear(_guideQueue[_currentStep]);
&&&_guideQueue&=&
&&&_currentStep&=&-1;
&&*&注册一个&IGuideComponent&到GuideManager中,这样它就会出现在新手引导过程中。
&&*&GuideManager会根据注册对象的instanceName来注册对象类名。若注册时发现instanceName
&&*&已被注册,则不执行接下来的注册过程
&&*&@param&instance&&欲注册对象
&public&static&function®ister(instance:IGuideComponent):void
&&if(&instance&)
&&&var&name:String&=&instance.instanceN
if(&_memberMap[name]&)
_memberMap[name]&=&
//注册的时候若是发现新手引导已经启动,则搜索当前注册对象是否是新手引导的其中
//一个步骤,若是,则加入到引导队列中
if(&_isSetUp&)
&var&index:int&=&_sequenceArray.indexOf(name);
&while(&index&!=&-1&)
&&_guideQueue[index]&=&
&&//有时候,两个相邻步骤间会存在时间差。如步骤1执行完毕后调用nextStep发现步骤2
&&//尚未注册,此时会导致GuideManager暂停运作,那么就等待步骤2在注册时重新启动
&&//GuideManager的播放
&&if(&_nextStep&==&index&)
&&&nextStep(index);
&&&index&=&_sequenceArray.indexOf(name,&index+1);
&/**&开始新手引导
&&*&@param&from&从第几部开始&*/
&public&static&function&start(from:uint=0):void
&&nextStep(from);
&&*&进行下一步引导&
&&*&@param&designedStep&跳到指定的步骤。若该值为-1,则走到当前步骤的下一步。若将跳转到的步骤不存在,则结束新手引导
&public&static&function&nextStep(designedStep:int=-1):void
&&//若在暂停时调用nextStep,则自动执行resume方法继续播放新手引导
&&if(&_paused&)
&&&resume();
&&if(&designedStep&&&0&)
&&&_nextStep&=&_currentStep+1;
&&&_nextStep&=&designedS
&&//若该方法是由start方法调用情况下(此时_currentStep==-1)不需要让上一部引导完成:
&&if(&_nextStep&&&0&&&&_currentStep&&=&0&)
&&&markFinish(_currentStep);
&&if(&&_nextStep&&&_guideQueue.length&&&&_guideQueue[_nextStep]&)
&&&var&data:Object&=&_dataArray[_nextStep];
&&&_guideQueue[_nextStep].guideProcess(data);
&&&_currentStep&=&_nextS
&&//若无法执行欲跳转到的步骤,则不改变_currentStep的值
&&&//播放结束
&&&if(&_nextStep&==&_sequenceArray.length&)
if(&onGuideFinish&!=&null&)
&onGuideFinish();
uninstall();
&/**&暂停引导播放&*/
&public&static&function&pause():void
&&if(&!_paused&)
&&&_paused&=&
&/**&继续引导播放&*/
&public&static&function&resume():void
&&if(&_paused&)
&&&_paused&=&
&&&_guideQueue[_nextStep].guideProcess(_dataArray[_nextStep]);
&&&_currentStep&=&_nextS
&&*&显示全屏遮罩以限制交互范围
&&*&@param&showRect&唯一显示出来的能接受交互的矩形区域
&&*&@param&maskAlpha&遮罩透明度
&&*&@param&maskColor&遮罩颜色
&&*&@param&parent&&&遮罩添加到的父容器。若留空,则父容器就等于GuideManager.indicatorContainer
&public&static&function&showScreenMask(&showRect:Rectangle=null,&maskAlpha:Number=0.5,&maskColor:uint=0,&
&parent:DisplayObjectContainer=null,&maskName:String=&hotgirl&&):void
&&if(&!parent&)
&&&parent&=&
&&&if(&!parent&)
&&var&mask:Sprite&=&_maskHome[maskName];
&&if(&!mask&)
&&&//遮挡物必须是能够响应鼠标事件的类,如Sprite。否则鼠标点击之将会穿透它以触发其挡住的对象的鼠标事件
&&&mask&=&new&Sprite();
&&&_maskHome[maskName]&=&
&&var&w:Number&=&parent&==&stage&?&stage.stageWidth&:&parent.
&&var&h:Number&=&parent&==&stage&?&stage.stageHeight&:&parent.
&&var&g:Graphics&=&mask.
&&g.clear();
&&g.beginFill(maskColor,&maskAlpha);
&&g.drawRect(0,&0,&w,&h);
&&if(&showRect&)
&&&//利用Graphics重叠绘制会消去重叠区域像素的原理进行挖洞动作
&&&g.drawRect(showRect.x,&showRect.y,&showRect.width,&showRect.height);
&&g.endFill();
&&if(&!parent.contains(mask)&)
&&&parent.addChild(mask);
&&*隐藏全屏遮罩&
&public&static&function&hideScreenMask(maskName:String=&hotgirl&):void
&&var&mask:Sprite&=&_maskHome[maskName];
&&if(&mask&&&&mask.parent&)
&&&mask.parent.removeChild(mask);
&&*&显示一个高亮矩形边框,该边框会被添加到当前正在播放新手引导的组件上
&&*&@param&bounds&矩形边框显示位置。该矩形的参考系是当前正在播放新手引导的组件的父容器
&&*&@param&parent&&边框添加到的父容器。若留空,则父容器就等于GuideManager.indicatorContainer
&public&static&function&showRectBorder(&bounds:Rectangle,&parent:DisplayObjectContainer=null&):void
&&if(&!parent&)
&&&parent&=&
&&&if(&!parent&)
&&if(&!_border&)
&&&_border&=&new&Shape();
&&&_border.filters&=&[new&GlowFilter(0xff911b,&.8,&8,&8,&4,&2)];
&&if(&!parent.contains(_border)&)
&&&parent.addChild(_border);
&&_border.graphics.clear();
&&_border.graphics.lineStyle(1,&0xFFFF00);
&&_border.graphics.drawRect(0,&0,&bounds.width,&bounds.height);
&&_border.x&=&bounds.x;
&&_border.y&=&bounds.y;
&&*&隐藏边框&
&public&static&function&hideBorder():void
&&if(&_border&&&&_border.parent&)
&&&_border.parent.removeChild(_border);
//------------------------------------------private&functions--------------------------------------------------//
&private&static&function&doClear(step:IGuideComponent):void
&&if(&step&)
&&&step.guideClear();
&&hideBorder();
&&hideScreenMask();
&private&static&function&markFinish(sequence:int):void
&&if(&!_finishList[sequence]&)
&&&doClear(_guideQueue[sequence]);
&&&if(&onStepFinish&!=&null&)
onStepFinish(_dataArray[sequence]);
&&&_finishList[sequence]&=&
&/**&是否已启动新手引导&*/
&public&static&function&get&isSetUp():Boolean
&&return&_isSetUp;
&/**&新手引导是否正被暂停&*/
&public&static&function&get&paused():Boolean
&&return&_
&/**&当前执行到的步骤&*/
&public&static&function&get¤tStep():int
&&return&_currentS
GuideManager是本章代码最多也是最复杂的一个类,如果你现在看不懂,没关系,你只需要学会如何使用就可以了,毕竟这个类也不是我一朝一夕就写出来的,也是经过了反复的修改才造就的。下图演示了GuideManager的大致工作原理:
主要需要解释的地方有如下几个:
1.加载guide.xml后得到的新手引导数据先会被存放进一个数组中,之后该数组被作为实参传给GuideManager.setUp()方法供GuideManager使用。细心的朋友会注意到,GuideManager在调度每一步的新手引导执行顺序的时候是根据每一步在数组中的索引,而并不是按照每一步的sequence属性。换句话说,如果我guide.xml里面的XML标签中sequence属性的最小值不是1,而是100,那么该标签代表的步骤仍然是首先被播放的:
&step&sequence=&100&&instanceName=&ButtomButtonBar&&subSeq=&1&/&&!--第一步--&
&&step&sequence=&200&&instanceName=&Window1&&subSeq=&1&/&&!--第二步--&
&&step&sequence=&300&&instanceName=&Window1&&subSeq=&2&/&&!--第三步--&
&&step&sequence=&400&&instanceName=&ButtomButtonBar&&subSeq=&2&/&&!--第四步--&
因此,根据我的这种方法,在guide.xml里面配置的新手引导步骤的sequence属性不必遵循从0开始的连贯数值,这样就便于插入新的步骤数据。比如我在设计新手引导步骤时,考虑到两个步骤间有可能在今后会插入一些新的步骤,那么我就可以让这两个步骤的sequence值差距大一些:
&step&sequence=&1&&instanceName=&ButtomButtonBar&/&
&&step&sequence=&20&instanceName=&Window1&/&
2.由于涉及到新手引导的组件不可能在程序刚启动的时候都已经准备好展示新手引导(如实例化完成时、被添加到舞台上时、摆好位置时等等),所以我需要提供一个regist方法来让外部调用,在涉案组件准备好时才会被注册到引导管理器中。当一个组件被注册到引导管理器时,引导管理器会检查当前是否正在播放新手引导,若正在播放,则会检查当前播放到的引导步骤是否是由当前被注册组件负责展示的,若是,则马上开始展示当前引导
3.nextStep方法被调用时会让新手引导进入到下一步。若下一步的instanceName对应组件还未注册,则暂停引导,直到它被注册了再继续播放。若当前步是最后一步,则结束引导,执行卸载工作
有了guide.xml,有了IGuideComponent和GuideManager之后我们的新手引导基本框架已经搭建完毕,接下来就是需要在咱们的项目中实际运用上这套框架来试试看效果如何了。
在下面这个例子里,我希望能够引导用户逐个点击我游戏右下角摆放着的按钮条(ButtomButtonBar)中的四个按钮。于是我可以这样设计guide.xml表的内容:
&step&sequence=&0&&instanceName=&ButtomButtonBar&&subSeq=&1&/&
&&step&sequence=&1&&instanceName=&ButtomButtonBar&&subSeq=&2&/&
&&step&sequence=&2&&instanceName=&ButtomButtonBar&&subSeq=&3&/&
&&step&sequence=&3&&instanceName=&ButtomButtonBar&&subSeq=&4&/&
下面是文档类和ButtonBar的代码:
&*&&&新手引导测试
&*&&&Created&by&S_eVent
[SWF(backgroundColor=&0xFFFFFF&)]
public&class&GuideTest&extends&Sprite
&private&var&_uiContainer:Sprite&=&new&Sprite();
&private&var&_buttonBar:ButtonBar&=&new&ButtonBar();
&private&var&_globalVariables:GlobalVariables&=&GlobalVariables.
&private&var&_needGuide:Boolean&=&
&public&function&GuideTest()
&&initUI();
&&if(&stage&)
&&&onAdded(null);
&&&addEventListener(Event.ADDED_TO_STAGE,&onAdded);
&private&function&initUI():void
&&addChild(_uiContainer);
&&var&dp:Array&=&[];
&&for(var&i:&i&6;&i++)
&&&dp[i]&=&{label:&按钮&&+&(i+1)};
&&_buttonBar.dataProvider&=&
&&_uiContainer.addChild(_buttonBar);
&private&function&onAdded(&e:Event&):void
&&stage.scaleMode&=&StageScaleMode.NO_SCALE;
&&stage.align&=&StageAlign.TOP_LEFT;
&&stage.addEventListener(Event.RESIZE,&onResize);
&&onResize(null);
&&Message.stage&=&
&&if(&_needGuide&)
&&&loadGuideXML();
&private&function&onResize(&e:Event&):void
&&_globalVariables.stageWidth&=&stage.stageW
&&_globalVariables.stageHeight&=&stage.stageH
&private&function&loadGuideXML():void
&&var&loader:URLLoader&=&new&URLLoader();
&&loader.PLETE,&onGuideXMLLoadComp);
&&loader.load(&new&URLRequest(&guide.xml&)&);
&private&function&onGuideXMLLoadComp(e:Event):void
&&var&data:XML&=&XML(&(e.currentTarget&as&URLLoader).data&);
&&var&guideData:Array&=&[];
&&for&each(var&x:XML&in&data..step)
&&&guideData.push(&xml2Object(x)&);
&&guideData.sortOn(&sequence&,&Array.NUMERIC);
&&function&xml2Object(&xml:XML&):Object
&&&var&obj:Object&=&{};
&&&var&attributes:XMLList&=&xml.attributes();
&&&for&each(var&a:XML&in&attributes)
obj[a.name().toString()]&=&a.toString();
&&&return&
&&GuideManager.setUp(&guideData&);
&&GuideManager.stage&=&
&&GuideManager.onStepFinish&=&onStepF
&&GuideManager.onGuideFinish&=&onGuideF
&&GuideManager.start();
&private&function&onStepFinish(data:Object):void
&&Message.show(&您已完成第&&+&data.sequence&+&&步&);
&private&function&onGuideFinish():void
&&Message.show(&恭喜您,您已完全部新手引导步骤!&);
//---------------------------ButtonBar.as---------------------------------//
&*&&&按钮条
&*&&&Created&by&S_eVent
public&class&ButtonBar&extends&Sprite&implements&IGuideComponent
&/**&当ButtonBar中的某个按钮被按下时调用。该函数接收一个代表按下按钮索引号的int型参数&*/
&public&var&onBtnClick:F
&private&var&_dataProvider:A
&private&var&_buttons:Vector.&CustomButton&&=&new&Vector.&CustomButton&();
&private&var&_gap:Number&=&4;
&private&var&_globalVariables:GlobalVariables&=&GlobalVariables.
&public&function&ButtonBar()
&&super();
&&this.mouseEnabled&=&
&&GuideManager.register(this);
&&this.addEventListener(Event.ADDED_TO_STAGE,&onAdded);
&public&function&clear():void
&&var&btn:CustomB
&&while(&_buttons.length&&&0&)
&&&btn&=&_buttons.pop();
&&&if(&this.contains(&btn)&)
this.removeChild(&btn&);
//--------------------------------private&functions-----------------------------------//
&private&function&onAdded(&e:Event&):void
&&this.addEventListener(Event.REMOVED_FROM_STAGE,&onRemoved);
&&this.addEventListener(MouseEvent.CLICK,&onClick);
&&//侦听舞台尺寸发生变化事件
&&_globalVariables.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,&onPC);
&private&function&onRemoved(&e:Event&):void
&&this.removeEventListener(Event.REMOVED_FROM_STAGE,&onRemoved);
&&this.removeEventListener(MouseEvent.CLICK,&onClick);
&&_globalVariables.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,&onPC);
&private&function&onClick(&e:MouseEvent&):void
&&var&btn:CustomButton&=&e.target&as&CustomB
&&if(&btn&&&&onBtnClick&!=&null&)
&&&onBtnClick(&_buttons.indexOf(btn)&);
&private&function&onPC(&e:PropertyChangeEvent&):void
&&if(&e.property&==&&stageWidth&&||&e.property&==&&stageHeight&&)
&&&this.x&=&_globalVariables.stageWidth&-&this.
&&&this.y&=&_globalVariables.stageHeight&-&this.
&&&if(&_guideTarget&)
var&maskArea:Rectangle&=&_guideTarget.getBounds(stage);
GuideManager.showScreenMask(maskArea);
&private&function&layout():void
&&var&crtW:Number&=&0;
&&for&each(var&btn:CustomButton&in&_buttons)
&&&btn.x&=&crtW;
&&&crtW&+=&btn.width&+&_
//-------------------------------get&/&set&functions----------------------------------//
&public&function&get&dataProvider():Array
&&return&_dataP
&public&function&set&dataProvider(value:Array):void
&&_dataProvider&=&
&&clear();
&&var&len:int&=&_dataProvider.length,&btn:CustomB
&&for(var&i:&i&&i++)
&&&btn&=&new&CustomButton(&_dataProvider[i].label&);
&&&addChild(&btn&);
&&&_buttons[i]&=&
&&layout();
&public&function&get&gap():Number
&&return&_
&public&function&set&gap(value:Number):void
&&layout();
//-------------------------------interface&implement----------------------------------//
&private&var&_instanceName:String&=&&ButtomButtonBar&;
&private&var&_guideTarget:CustomB
&public&function&guideProcess(data:Object=null):void
&&_guideTarget&=&_buttons[data.subSeq-1];
&&var&maskArea:Rectangle&=&_guideTarget.getBounds(stage);
&&GuideManager.showScreenMask(maskArea);
&&_guideTarget.addEventListener(MouseEvent.CLICK,&onNextStep);
&public&function&guideClear():void
&&//没什么好做的这里
&private&function&onNextStep(&e:MouseEvent&):void
&&e.currentTarget.removeEventListener(MouseEvent.CLICK,&onNextStep);
&&GuideManager.nextStep();
&public&function&get&instanceName():String
&&return&_instanceN
&public&function&set&instanceName(value:String):void
&&_instanceName&=&
对于文档类来说,它首先要做的,自然就是在需要启动新手引导的时候去加载guide.xml,之后将加载得到的XML数据转换成GuideManager能识别的Object数组并传递给GuideManager使用,之后马上开始新手引导的播放。
对于按钮条ButtonBar来说,要让它成为一个能够展示新手引导的组件,必须实现之前我们所说的IGuideComponent接口,然后在文件末尾处写上实现IGuideComponent接口的两个方法及一个属性。由于在我的项目中,ButtonBar只可能有一个实例,所以它的instanceName我就直接在它内部写死了。如果在项目中存在多个ButtonBar实例,那么我们需要在外部动态地为每个ButtonBar实例的instanceName属性赋值才行。在guideProcess方法中我需要写出轮到ButtonBar展示引导时会发生什么事情,在本例中,它要做的就是根据引导数据的子步骤sebSeq的不同而引导用户点击不同的按钮。为了方便,我这里就不加什么箭头来指示用户了,直接用GuideManager里面自带的全屏遮罩(实现原理可参考《
》)来限制用户的点击范围为我需要让用户点击的区域。
那么上述代码最终的实现效果如下:
经过这个例子,差不多我们熟悉了一点这套新手引导框架的使用方式了,那么在
教程中,我们将考虑新手引导的更多方面:如与服务器端进行同步的问题、新手引导组件注册时机不对导致引导箭头指向位置不正确的问题以及如何使用开放式引导的问题等等。如果遇到这些问题,你知道该如何解决吗?i&know&what&to&do....
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致

参考资料

 

随机推荐