【AS3 Coder】任務九:遊戲新手引導的製做原理(上)

使用框架:AS3
任務描述:瞭解
遊戲中新手的製做原理及流程html

難度:3前端

 

本章源碼下載:http://www.iamsevent.com/zb_users/UPLOAD/GuideManager/Test1.zipweb

 

有人問我,都兩年過去了,AS3 Coder系列怎麼纔出了10篇文章都不到?答案很簡單:我TM懶得寫!原計劃出到10篇就洗手不寫了,如今還有最後兩篇,加把勁衝刺一下吧!編程

新手引導基本上在每一個遊戲中都會出現,或長或短,或簡單或複雜,固然,新手引導流程越長越容易出現BUG,且傳統的新手引導作法會極大地破壞代碼的耦合性,爲了解決「不穩定」及「破壞耦合性」這兩個問題,貧道想了一種相對較好一點的(究竟是好仍是很差,列位看完本文以後就仁者見仁智者見智了)方式,在本文中介紹給你們。數組

 

傳統的新手引導製做方式服務器

傳統的新手引導方式通常是設置一個全局的靜態變量來保存當前新手引導進度,而後在項目中每一個可能出現新手引導的位置添加一句判斷:若當前新手引導步驟等於我所指望的步驟就執行引導部分的邏輯。例如,一個遊戲中的新手引導第四步是引導用戶去打開一個A窗口,而後第五步引導則是引導用戶點擊A窗口中的某個按鈕。那麼在A窗口的「打開窗口」函數中就會加上對當前新手引導步驟的一個判斷,若當前步驟等於5,就執行相應的新手引導邏輯(例如,給A中須要引導用戶點擊的按鈕加上一個箭頭神馬的)框架

public function onOpen():voidide

{   函數

 if( GuideManager.currentStep == 5 )//GuideManager.currentStep是一個靜態變量,用於存儲當前新手引導進行到的步驟   post

 {     //執行相應的引導邏輯   }

}

這種作法的弊端在於,它破壞了代碼的耦合性,由於新手引導每涉及到一個組件,就須要在該組件中添加相應的判斷語句及引導邏輯。並且當新手引導的步驟一多以後就會出現不穩定的狀況,最煩人的是,一旦策劃要求你在新手引導中插入幾個步驟,那你幾乎所有的涉及到新手引導的組件都會遭殃(原先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():void;

}

聲明完該接口以後,須要讓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):void;/** 執行新手引導卸載相關清理工做 */function guideClear():void;/** 註冊到GuideManager中的實例名 */function get instanceName():String;function set instanceName(value:String):void;}

該接口定義了兩個方法(guideProcess及guideClear)和一個屬性(instanceName)。在新手引導過程當中會用到的類,都須要實現該接口才能夠。使用基於接口的編程的好處有如下三點:

1.避免冗餘代碼的產生,哪一個類須要實現新手引導的功能就讓哪一個類來實現該接口;

2.方便查找:你只要在項目中搜索該接口的引用位置,就能夠一次性找全所有涉及到新手引導的類;

3.方便管理:所有與新手引導有關的邏輯代碼都存放於名爲guideProcess的函數中,某組件涉及到的新手引導步驟執行完畢後的清理工做都放在guideClear函數中。

 

新手引導管理器

爲了便於管理和查詢新手引導各步驟,我建立了一張XML表guide.xml用於記錄新手引導的各個步驟,其格式以下:

<?xml version="1.0" encoding="utf-8"?>
<guide>
<!-- 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"/>
 
</guide>

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.stageWidth;

    _globalVariables.stageHeight = stage.stageHeight;

}

private function loadGuideXML():void

{

    var loader:URLLoader = new URLLoader();

    loader.addEventListener(Event.COMPLETE, 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 obj;

    }

    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:Stage;
 /** 新手引導完成一個步驟以後執行函數。此函數需接受一個Object型參數,表明當前完成步驟的配置數據 */
 public static var onStepFinish:Function;
 /** 新手引導播放完成後執行函數 */
 public static var onGuideFinish:Function;
 
 /** 註冊成員地圖。格式爲{className1:IGuideComponent, className2:IGuideComponent, ......} */
 private static var _memberMap:Object = {};
 
 /** 新手引導播放隊列,其中元素爲每一步的實例 */
 private static var _guideQueue:Vector.<IGuideComponent>;
 
 private static var _isSetUp:Boolean = false;

 /** 當前執行的步驟索引 */
 private static var _currentStep:int=-1;
 
 /** 下一個將執行的步驟索引 */
 private static var _nextStep:int=0;
 
 /** 記錄新手引導具體步驟的數組。其中元素爲每一步的實例名 */
 private static var _sequenceArray:Array;
 
 /** 記錄新手引導每步所包含數據的數組 */
 private static var _dataArray:Array;
 
 /** 完成步驟列表。鍵爲步驟序號,值爲true/false,表示是否完成 */
 private static var _finishList:Object;
 
 private static var _paused:Boolean;
 
 /** 存儲一切當前使用的遮罩對象 */
 private static var _maskHome:Object = {};
 private static var _border:Shape;
 
 /** 啓動新手引導 */
 public static function setUp( config:Array ):void
 {
  if( _isSetUp == false )
  {
   _isSetUp = true;
   _sequenceArray = [];
   _dataArray = [];
   _finishList = {};
   var len:int = config.length;
   
   for(var i:int=0; i<len; i++)
   {
_sequenceArray[i] = config[i].instanceName.toString();
_dataArray[i] = config[i];
   }
   
   _guideQueue = new Vector.<IGuideComponent>();
   for(i=0; i<len; i++)
   {
_guideQueue[i] = _memberMap[_sequenceArray[i]];
   }
  }
 }
 
 /** 卸載新手引導 */
 public static function uninstall():void
 {
  if( _isSetUp )
  {
   _isSetUp = false;
   if( _currentStep >= 0 )
doClear(_guideQueue[_currentStep]);
   _guideQueue = null;
   _currentStep = -1;
  }
 }
 
 /**
  * 註冊一個 IGuideComponent 到GuideManager中,這樣它就會出如今新手引導過程當中。
  * GuideManager會根據註冊對象的instanceName來註冊對象類名。若註冊時發現instanceName
  * 已被註冊,則不執行接下來的註冊過程
  * @param instance  欲註冊對象
  * 
  */  
 public static function register(instance:IGuideComponent):void
 {
  if( instance )
  {
   var name:String = instance.instanceName;
if( _memberMap[name] )
{
 return;
}
_memberMap[name] = instance;

//註冊的時候如果發現新手引導已經啓動,則搜索當前註冊對象是不是新手引導的其中
//一個步驟,如果,則加入到引導隊列中
if( _isSetUp )
{
 var index:int = _sequenceArray.indexOf(name);
 while( index != -1 )
 {
  _guideQueue[index] = instance;
  //有時候,兩個相鄰步驟間會存在時間差。如步驟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();
   return;
  }
  
  if( designedStep < 0 )
  {
   _nextStep = _currentStep+1;
  }
  else
  {
   _nextStep = designedStep;
  }
  
  //若該方法是由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 = _nextStep;
  }
  //若沒法執行欲跳轉到的步驟,則不改變_currentStep的值
  else
  {
   //播放結束
   if( _nextStep == _sequenceArray.length )
   {

if( onGuideFinish != null )
 onGuideFinish();
uninstall();
   }
  }
 }
 
 /** 暫停引導播放 */
 public static function pause():void
 {
  if( !_paused )
  {
   _paused = true;
  }
 }
 
 /** 繼續引導播放 */
 public static function resume():void
 {
  if( _paused )
  {
   _paused = false;
   
   _guideQueue[_nextStep].guideProcess(_dataArray[_nextStep]);
   _currentStep = _nextStep;
  }
 }
 
 /**
  * 顯示全屏遮罩以限制交互範圍
  * @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 = stage;
   if( !parent )
return;
  }
  var mask:Sprite = _maskHome[maskName];
  if( !mask )
  {
   //遮擋物必須是可以響應鼠標事件的類,如Sprite。不然鼠標點擊之將會穿透它以觸發其擋住的對象的鼠標事件
   mask = new Sprite();
   _maskHome[maskName] = mask;
  }
  var w:Number = parent == stage ? stage.stageWidth : parent.width;
  var h:Number = parent == stage ? stage.stageHeight : parent.height;
  var g:Graphics = mask.graphics;
  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 = stage;
   if( !parent )
return;
  }
  
  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] = true;
  }
 }
 
 /** 是否已啓動新手引導 */
 public static function get isSetUp():Boolean
 {
  return _isSetUp;
 }

 /** 新手引導是否正被暫停 */
 public static function get paused():Boolean
 {
  return _paused;
 }

 /** 當前執行到的步驟 */
 public static function get currentStep():int
 {
  return _currentStep;
 }
 
 }

}

 

GuideManager是本章代碼最多也是最複雜的一個類,若是你如今看不懂,不要緊,你只須要學會如何使用就能夠了,畢竟這個類也不是我一朝一夕就寫出來的,也是通過了反覆的修改才造就的。下圖演示了GuideManager的大體工做原理:

image

主要須要解釋的地方有以下幾個:

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
 *   at 2013-5-27 
 */
[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.instance;
 
 private var _needGuide:Boolean = true;
 
 public function GuideTest()
 {
  initUI();
  
  if( stage )
   onAdded(null);
  else
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
 }
 
 private function initUI():void
 {
  addChild(_uiContainer);
  
  var dp:Array = [];
  for(var i:int; i<6; i++)
  {
   dp[i] = {label:"按鈕" + (i+1)};
  }
  _buttonBar.dataProvider = dp;
  _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 = stage;
  
  if( _needGuide )
   loadGuideXML();
 }
 
 private function onResize( e:Event ):void
 {
  _globalVariables.stageWidth = stage.stageWidth;
  _globalVariables.stageHeight = stage.stageHeight;
 }
 
 private function loadGuideXML():void
 {
  var loader:URLLoader = new URLLoader();
  loader.addEventListener(Event.COMPLETE, 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 obj;
  }
  
  GuideManager.setUp( guideData );
  GuideManager.stage = stage;
  GuideManager.onStepFinish = onStepFinish;
  GuideManager.onGuideFinish = onGuideFinish;
  GuideManager.start();
 }
 
 private function onStepFinish(data:Object):void
 {
  Message.show("您已完成第" + data.sequence + "步");
 }
 
 private function onGuideFinish():void
 {
  Message.show("恭喜您,您已徹底部新手引導步驟!");
 }

}

 

//---------------------------ButtonBar.as---------------------------------//

 

/** 
 *   按鈕條
 *   Created by S_eVent
 *   at 2013-5-27 
 */
public class ButtonBar extends Sprite implements IGuideComponent
{
 /** 當ButtonBar中的某個按鈕被按下時調用。該函數接收一個表明按下按鈕索引號的int型參數 */
 public var onBtnClick:Function;
 
 private var _dataProvider:Array;
 private var _buttons:Vector.<CustomButton> = new Vector.<CustomButton>();
 private var _gap:Number = 4;
 private var _globalVariables:GlobalVariables = GlobalVariables.instance;
 
 public function ButtonBar()
 {
  super();
  this.mouseEnabled = false;
  
  GuideManager.register(this);
  
  this.addEventListener(Event.ADDED_TO_STAGE, onAdded);
 }
 
 public function clear():void
 {
  var btn:CustomButton;
  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 CustomButton;
  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.width;
   this.y = _globalVariables.stageHeight - this.height;
   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 + _gap;
  }
 }
 
//-------------------------------get / set functions----------------------------------//
 
 public function get dataProvider():Array
 {
  return _dataProvider;
 }

 public function set dataProvider(value:Array):void
 {
  _dataProvider = value;
  
  clear();
  
  var len:int = _dataProvider.length, btn:CustomButton;
  for(var i:int; i<len; i++)
  {
   btn = new CustomButton( _dataProvider[i].label );
   addChild( btn );
   _buttons[i] = btn;
  }
  
  layout();
 }

 public function get gap():Number
 {
  return _gap;
 }

 public function set gap(value:Number):void
 {
  _gap = value;
  layout();
 }

//-------------------------------interface implement----------------------------------//
 
 private var _instanceName:String = "ButtomButtonBar";
 private var _guideTarget:CustomButton;
 
 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 _instanceName;
 }
 
 public function set instanceName(value:String):void
 {
  _instanceName = value;
 }

}

 

對於文檔類來講,它首先要作的,天然就是在須要啓動新手引導的時候去加載guide.xml,以後將加載獲得的XML數據轉換成GuideManager能識別的Object數組並傳遞給GuideManager使用,以後立刻開始新手引導的播放。

對於按鈕條ButtonBar來講,要讓它成爲一個可以展現新手引導的組件,必須實現以前咱們所說的IGuideComponent接口,而後在文件末尾處寫上實現IGuideComponent接口的兩個方法及一個屬性。因爲在個人項目中,ButtonBar只可能有一個實例,因此它的instanceName我就直接在它內部寫死了。若是在項目中存在多個ButtonBar實例,那麼咱們須要在外部動態地爲每一個ButtonBar實例的instanceName屬性賦值才行。在guideProcess方法中我須要寫出輪到ButtonBar展現引導時會發生什麼事情,在本例中,它要作的就是根據引導數據的子步驟sebSeq的不一樣而引導用戶點擊不一樣的按鈕。爲了方便,我這裏就不加什麼箭頭來指示用戶了,直接用GuideManager裏面自帶的全屏遮罩(實現原理可參考《使用繪圖API繪製鏤空矩形》)來限制用戶的點擊範圍爲我須要讓用戶點擊的區域。

那麼上述代碼最終的實現效果以下:

http://www.iamsevent.com/zb_users/UPLOAD/GuideManager/test1/GuideTest.html

 

通過這個例子,差很少咱們熟悉了一點這套新手引導框架的使用方式了,那麼在下一篇教程中,咱們將考慮新手引導的更多方面:如與服務器端進行同步的問題、新手引導組件註冊時機不對致使引導箭頭指向位置不正確的問題以及如何使用開放式引導的問題等等。若是遇到這些問題,你知道該如何解決嗎?i know what to do....

相關文章
相關標籤/搜索