案例14——游戏技能天赋树的制作

天赋树这个东西相信大家都不会陌生,比如暗黑破坏神和魔兽世界的天赋树是灰常经典的(我没有给暴雪打广告的意思)。如果你是个做游戏的,那就有可能会涉及到天赋树的制作。
    先来看一下最终的结果:http://www.iamsevent.com/upload/SpellToolTip.swf
   第一步需要解决的问题是技能toolTip的显示,所谓toolTip,中文翻译过来叫做贴士,就是一系列解释性的文字,当鼠标移到一个图标上时会弹出一个矩形框里面写着对此图标的解释文字,在Flex中对所有组件都自带了toolTip的属性,只要设置了此属性,鼠标移到相应组件上就会弹出toolTip文字,但是在AS中是没有的,因此我们就需要自己使用textField类来写一个toolTip组件:
LifeToolTip.as:

  1. public class LifeToolTip extends Sprite
  2.         {
  3.                 public static const NORMAL_VIEW:int = 101;
  4.                 public static const ARROW_VIEW:int = 102;
  5.                 
  6.                 protected var textField:TextField;
  7.                 
  8.                 private var bgW:Number;
  9.                 private var bgH:Number;
  10.                 private var backgroundColor:uint;
  11.                 private var backgroundAlpha:Number;
  12.                 private var type:int;
  13.                 private var Width:Number;
  14.                 private var Height:Number;
  15.                 
  16.                 public function LifeToolTip(text:String="", width:Number=0, height:Number=0, fontColor:uint=0x000000, backgroundColor:uint=0xffffff, backgroundAlpha:Number=0.6, type:int=NORMAL_VIEW )
  17.                 {
  18.                         super();
  19.                         this.mouseEnabled = false;//不允许此组件接收鼠标消息
  20.                         this.backgroundColor = backgroundColor;
  21.                         this.backgroundAlpha = backgroundAlpha;
  22.                         this.type = type;
  23.                         Width = width;
  24.                         Height = height;
  25.                         textField = new TextField();
  26.                         textField.htmlText = text;
  27.                         textField.selectable = false;//不允许文本被选择
  28.                         textField.textColor = fontColor;
  29.                         textField.multiline = true;//允许文本多行
  30.                         textField.wordWrap = true;//让文本自动换行
  31.                         addChild( textField );
  32.                         
  33.                         drawToolTipBg();
  34.                         this.visible = false;
  35.                 }
  36.                 
  37.                                 //设置文本,重新设置文本后需要根据文字多少重绘背景
  38.                 public function set text( value:String ):void{
  39.                         textField.htmlText = value;
  40.                         drawToolTipBg();
  41.                 }
  42.                 
  43.                 private function drawToolTipBg( ) : void {
  44.                         if(Width == 0){
  45.                                 textField.width = textField.textWidth + 5;
  46.                                 bgW = textField.width + 10;
  47.                         }else{
  48.                                 textField.width = Width;
  49.                                 bgW = Width;
  50.                         }
  51.                         if(Height == 0){
  52.                                 textField.height = textField.textHeight + 5;
  53.                                 bgH = textField.height + 10;
  54.                         }else{
  55.                                 textField.height = Height;
  56.                                 bgH = Height;
  57.                         }
  58.                         this.graphics.clear();
  59.                         this.graphics.lineStyle(0, 0x000000, .6);
  60.                         if( backgroundAlpha > 1 ){
  61.                                 backgroundAlpha = 1;
  62.                         }else if( backgroundAlpha < 0 ){
  63.                                 backgroundAlpha = 0;
  64.                         }
  65.                         this.graphics.beginFill( backgroundColor, backgroundAlpha );
  66.                         switch( type )
  67.                         {
  68.                                 case NORMAL_VIEW:
  69.                                         this.graphics.drawRect(0, 0, bgW, bgH);
  70.                                         break;
  71.                                 case ARROW_VIEW:
  72.                                         this.graphics.lineTo(-10, 20);
  73.                                         this.graphics.lineTo(-bgW/2, 20);
  74.                                         this.graphics.lineTo(-bgW/2, 20 + bgH);
  75.                                         this.graphics.lineTo(bgW/2, 20 + bgH);
  76.                                         this.graphics.lineTo(bgW/2, 20);
  77.                                         this.graphics.lineTo(10, 20);
  78.                                         this.graphics.lineTo(0, 0);
  79.                                         textField.x = -bgW/2 + 3; //这里必须改变一下文本的起始位置于箭头下矩形框的左侧
  80.                                         textField.y = 23;
  81.                                         break;
  82.                         }
  83.                         this.graphics.endFill();
  84.                 }
  85.         }
复制代码

这个类里面难点不是很多,语句都能看明白,没有陌生的类出现,在构造函数里我们可以对toolTip组件进行全面的设置,包括显示文字,宽,高,字体颜色,背景颜色及透明度,还有一个参数是设置外形类别的,外形类别分两种,第一是最一般的单纯的矩形框,第二是带箭头的对话框,在drawToolTipBg()函数中会根据其类别绘制不同的外观。对于箭头形的外形绘制原理,用一幅图就很容易理解了。
<ignore_js_op>1.JPG 
如果知道了我们要画的图形是怎么样的,各个点的位置坐标是什么,就不难用graphics.lineTo方法画出一个带箭头的toolTip框。
   好了,接下来,在看到我们做的这个toolTip组件的样子之前,我们需要先把每个技能的图标做出来放到舞台上,但是每个技能都有自己的一些属性,比如技能名,技能等级以及技能说明等,这样的话就需要一个SpellView类来表现外观,一个SpellVO类来记录属性,这是我们在案例8中提到过的M-V概念,让视图与数据分离的思想。好吧,来看看代码:
SpellVO.as:

  1. public class SpellVO
  2.         {
  3.                 public var name:String;
  4.                 public var level:int;
  5.                 public var maxLevel:int;
  6.                 public var imgSource:String;
  7.                 public var description:String;
  8.         }
复制代码

SpellView.as:

  1.         public class SpellView extends Sprite
  2.         {
  3.                 private var _spellVO:SpellVO;
  4.                 private var _bm:Bitmap;
  5.                 private var _toolTipStr:String = "";
  6.                 private var toolTipComponent:LifeToolTip;
  7.                 
  8.                 public function SpellView( )
  9.                 {
  10.                         super();
  11.                         _bm = new Bitmap();
  12.                         addChild( _bm );
  13.                 }
  14.                 
  15.                 public function set spellVO( value:SpellVO ):void{
  16.                                            //第一次设置时会加载图片,图片路径更改时也会加载
  17.                         if( _spellVO == null || _spellVO.imgSource != value.imgSource ){
  18.                                 updateView( value.imgSource );
  19.                         }
  20.                         _spellVO = value;
  21.                 }
  22.                 
  23.                 public function get spellVO():SpellVO{
  24.                         return _spellVO;
  25.                 }
  26.                 
  27.                 private function updateView( url:String ):void{
  28.                         var loader:Loader = new Loader();
  29.                         loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
  30.                         loader.load( new URLRequest( url ) );
  31.                 }
  32.                 
  33.                 private function onComplete(event:Event):void{
  34.                         _bm.bitmapData = ( (event.currentTarget as LoaderInfo).content as Bitmap ).bitmapData ;
  35.                 }
  36.         }
复制代码

这次,我们在SpellView里不直接使用一个public var spellVO:SpellVO而改用get/set方法来进行vo属性的读取,因为我们想在spellVO被设置的时候会在后台自动进行一系列的动作。比如,我们注意到在SpellVO中存在一个imgSource的属性是用来记录该技能图标图片路径的,我们希望在spellView中的spellVO实例被设置时它会自动加载vo中imgSource记录的图片,那么这种自动化的最佳实现者非set方法莫属了。
    嗯,准备工作都做好了,接下来我们想设置3个技能放到舞台上,那就得给每个技能设置对应的属性,你可以选择在主应用文件中一个个设置,这样比较繁琐而且不便于修改,修改一次属性就得重新编译一次。因此我们选择了更佳的解决方案——用xml文件。(之前没有学过XML相关知识的爱卿可以先去网上搜索学习一下)
看看我的xml文件里都记录了些啥子东东:
spellsInfo.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <root>
  3.         <item name="失明学" imgSource="assets/Spell_Arcane_Blink.jpg" maxLevel="3"  description="敌方攻击时有一定几率丢失"/>
  4.         <item name="流星雨" imgSource="assets/Spell_Arcane_StarFire.jpg" maxLevel="3" description="对范围内敌人造成伤害"/>
  5.         <item name="火球术" imgSource="assets/Spell_Fire_FlameBolt.jpg"  maxLevel="2"  description="对单个敌人造成高额火焰伤害"/>        
  6. </root>
复制代码

这样就设定好了所有需要的技能属性,我们将在主应用中读取它,来看主应用代码,不过贴了代码后我发现字数超了,看来得贴在二楼了……

 

才晚了一天就有两楼被占领了,贫道只能站在二位脚下继续发代码了,献上主应用的代码一坨:
ToolTipTest:

  1. public class ToolTipTest extends Sprite
  2.         {
  3.                 private var xml:XML;
  4.                 private var spellList:Array = new Array();
  5.                 private var voList:Array = new Array();
  6.                 private static const VIEW_WIDTH:Number = 64;
  7.                 private static const VIEW_HEIGHT:Number = 64;
  8.                 private var _toolTipStr:String = "";
  9.                 private var toolTipComponent:LifeToolTip;
  10.                 
  11.                 public function ToolTipTest()
  12.                 {
  13.                         initXML();
  14.                 }
  15.                 
  16.                 private function initXML():void{
  17.                         var loader:URLLoader = new URLLoader();
  18.                         loader.addEventListener(Event.COMPLETE, onComplete);
  19.                         loader.load( new URLRequest("data/spellsInfo.xml") );
  20.                 }
  21.                 
  22.                 private function onComplete(event:Event):void{
  23.                         xml = new XML( (event.currentTarget as URLLoader).data );
  24.                         for each( var x:XML in xml.item )
  25.                         {
  26.                                 var vo:SpellVO = new SpellVO();
  27.                                 vo.name = String(x.@name);
  28.                                 vo.level = 0;
  29.                                 vo.maxLevel = x.@maxLevel;
  30.                                 vo.description = String(x.@description);
  31.                                 vo.imgSource = String(x.@imgSource);
  32.                                 voList.push( vo );
  33.                         }
  34.                         initView();
  35.                 }
  36.                 
  37.                 private function initView():void{
  38.                         var len:int = voList.length;
  39.                         for ( var i:int=0; i<len ; i++ )
  40.                         {
  41.                                 var item:SpellVO = voList[i];
  42.                                 var view:SpellView = new SpellView();
  43.                                 view.spellVO = item;
  44.                                 spellList.push( view );
  45.                                 view.x = 50 + i * (VIEW_WIDTH + 30);
  46.                                 view.y = 50;
  47.                                 addChild( view );
  48.                                 view.addEventListener(MouseEvent.ROLL_OVER, onMouseOver);
  49.                                 view.addEventListener(MouseEvent.ROLL_OUT, onMouseOut);
  50.                         }
  51.                         toolTipComponent = new LifeToolTip("", 100, 0, 0x000000, 0xffffff, 0.6, LifeToolTip.ARROW_VIEW );//为了节省资源,我们应用中只有一个LifeToolTip组件,当鼠标移动到图标上就让此组件可见并使其位置移到鼠标上,从图标上移开时就设置此组件不可见。这里为了看起来比较酷,我使用了提示框的箭头形外观
  52.                         addChild( toolTipComponent );//要保证提示框不被遮挡,就必须最后AddChild
  53.                 }
  54.                 
  55.                 private function onMouseOver(event:MouseEvent):void{
  56.                         addEventListener(Event.ENTER_FRAME, mouseFollow);
  57.                         var vo:SpellVO = (event.currentTarget as SpellView).spellVO;
  58.                         toolTipComponent.text = setToolTip( vo );//设置提示框中显示信息格式
  59.                         toolTipComponent.visible = true;
  60.                 }
  61.                 
  62.                 private function onMouseOut(event:MouseEvent):void{
  63.                         toolTipComponent.visible = false;
  64.                         removeEventListener(Event.ENTER_FRAME, mouseFollow);
  65.                 }
  66.                 
  67.                 private function mouseFollow(event:Event):void{
  68.                         toolTipComponent.x = mouseX - 1;                        
  69.                                                 toolTipComponent.y = mouseY + 5;;// -1和+5是偏移量,为了让鼠标指针不挡住提示框的箭头
  70.                 }
  71.                 
  72.                 private function setToolTip( itemVO:SpellVO ):String{
  73.                                  //为了实现提示框中不同属性显示不同颜色与大小,我们使用了html文本
  74.                         var str:String = toHTMLText(itemVO.name, 20) + "<br>"
  75.                                 + toHTMLText("等级:" + itemVO.level + "/" + itemVO.maxLevel, 14, "#666600") + "<br><br>";
  76.                         if(itemVO.level != 0){
  77.                                 str += toHTMLText("当前等级", 20, "#ffffff") + "<br>"
  78.                                         + toHTMLText(itemVO.description, 14, "#666600") + "<br><br>";
  79.                         }else{
  80.                                 str += toHTMLText(itemVO.description, 14, "#666600") + "<br><br>";
  81.                         }
  82.                         return str;
  83.                 }
  84.                 
  85.                 private function toHTMLText(text:String, fontSize:int = 12, color:String = "#000000" ):String{
  86.                         var str:String = "<font size='" + fontSize + "' color='" + color + "'>" + text + "</font>";
  87.                         return str;
  88.                 }
  89.         }
复制代码

首先要做的自然是从xml中读取配置信息,在一般的商业项目中,xml文件被称作配置表,所有游戏用到的数据都会记录在其中,他不是写死在程序中的,所以你可以在外部动态改变游戏信息。就是说你改变xml中信息后运行swf文件,就会发现游戏中你所修改的信息生效了。配置表一般是由策划人员来进行修改的,因为他们的工作就是保持游戏的平衡性与游戏性,而这些东西都不是我们程序员需要操心的,把游戏的信息放在一个可读性更高且修改方便的xml文件中供一个不懂编程的策划人员来修改是不会具有任何难以理解的东东的。在把xml中数据遍历并保存为一个vo数组后我们需要的游戏数据就得到了,之后把这些数据放入他们所对应的视图中去。我们还需要给每个视图加上鼠标事件,为了让技能的提示框跟随者鼠标移动,我们一开始的设想是使用MOUSE_OVER事件,因为此事件的官方解释是鼠标在显示对象上移动鼠标即会不断触发。但是实际效果不佳,由于此事件的触发并不连贯,鼠标在刚移上技能图标时会出现技能提示框,但是在出现之后仍操纵鼠标在技能图标上来回移动,会发现提示框并不能够一直跟随鼠标移动,而是间断性地跟随。于是我们就不得不把ROLL_OVER与ENTER_FRAME事件联合起来使用以达到我们想要的效果,ROLL_OVER事件只在鼠标刚移到显示对象时触发,当触发后就开始侦听ENTER_FRAME事件以保证提示框能够持续连贯地跟随鼠标移动……
    好吧,如果逻辑有点混乱就把代码拷到Flash Builder下,然后使用Ctrl + 鼠标左键 点击你不知道有什么作用的函数,看看这个函数的代码是怎样的吧。让我们来看看主应用的运行效果图:
<ignore_js_op>2.JPG 
shit!截图中我的鼠标怎么不见了!算了,不管他,虽然鼠标不见了但是它永远留在我们心中……

     技能提示框有了,接下来需要做的就是天赋树了,技能的学习总不可能是随意的,需要满足一些前提条件才行,首先,学习一个技能需要消耗技能点。其次,在天赋树里面我们不可能一开始就学到很高级的技能,想学习一个高级技能必须具备前提条件,比如需要先学习一些低级技能才可以。还有,每个技能会有很多级,比如一个二级技能必须学习了一级技能后才可以升级,而且在N多的技能中还存在分类,比如一些是冰魔法一些是火魔法什么的……好了,暂时就想到这一些限制要素,让我们来用代码做出这条规则。
     首先,我们的技能数据需要增加一些属性了,为SpellVO中增加id,type,requirePoint以及precondition四个属性:

  1.         public class SpellVO
  2.         {
  3.                 public var id:Number;//每个技能特有的ID标志号,它记录了技能的类型以及级别
  4.                 public var type:int;//技能类别
  5.                 public var name:String;
  6.                 public var level:int;
  7.                 public var maxLevel:int;
  8.                 public var requirePoint:int;//学习需要技能点
  9.                 public var precondition:Array;//修炼前提
  10.                 public var imgSource:String;
  11.                 public var description:String;
  12.         }
复制代码

接着xml中也必须做出对应的改变:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <root>
  3.         <!--id:第一位是标志位无意义,前两位塔的类别,后两位塔的级别-->
  4.                 <item id="10000" name="失明学" imgSource="assets/Spell_Arcane_Blink.jpg" requirePoint="1" maxLevel="3" precondition="" description="敌方攻击时有一定几率丢失"/>
  5.                 <item id="10001" name="失明lv1" imgSource="assets/Spell_Arcane_Blink.jpg" requirePoint="1" maxLevel="3" precondition="" description="敌方攻击时有10%几率丢失"/>
  6.                 <item id="10002" name="失明lv2" imgSource="assets/Spell_Arcane_Blink.jpg" requirePoint="1" maxLevel="3" precondition="" description="敌方攻击时有20%几率丢失"/>
  7.                 <item id="10003" name="失明lv3" imgSource="assets/Spell_Arcane_Blink.jpg" requirePoint="1" maxLevel="3" precondition="" description="敌方攻击时有30%几率丢失"/>
  8.                 <item id="10100" name="流星雨" imgSource="assets/Spell_Arcane_StarFire.jpg" requirePoint="1" maxLevel="3" precondition="" description="对范围内敌人造成伤害"/>
  9.                 <item id="10101" name="流星雨lv1" imgSource="assets/Spell_Arcane_StarFire.jpg" requirePoint="1" maxLevel="3" precondition="" description="对范围内敌人造成每波流星雨100的伤害"/>
  10.                 <item id="10102" name="流星雨lv2" imgSource="assets/Spell_Arcane_StarFire.jpg" requirePoint="1" maxLevel="3" precondition="" description="对范围内敌人造成每波流星雨200的伤害"/>
  11.                 <item id="10103" name="流星雨lv3" imgSource="assets/Spell_Arcane_StarFire.jpg" requirePoint="1" maxLevel="3" precondition="" description="对范围内敌人造成每波流星雨300的伤害"/>
  12.                 <item id="10200" name="火球术" imgSource="assets/Spell_Fire_FlameBolt.jpg" requirePoint="1" maxLevel="2" precondition="10001_10101" description="对单个敌人造成高额火焰伤害"/>
  13.                 <item id="10201" name="火球lv1" imgSource="assets/Spell_Fire_FlameBolt.jpg" requirePoint="1" maxLevel="2" precondition="10002_10102" description="对单个敌人造成500点火焰伤害"/>
  14.                 <item id="10202" name="火球lv1" imgSource="assets/Spell_Fire_FlameBolt.jpg" requirePoint="1" maxLevel="2" precondition="10003_10103" description="对单个敌人造成1000点火焰伤害"/>
  15.                         
  16. </root>
复制代码

在设置时要注意逻辑严密,比如你一个技能明明写的是最高等级为2级(maxLevel=“2”),但你的xml标签却只写到Lv1为止,就不行了嘛……字数有限,楼下继续连载ing……

 

说实话,我也不愿意一篇文章拖拉拉写那么长,但是没办法,想跟大家分享的东西不是几句代码就能实现的,若真用“一行流”写完了代码,各位看了也是云里雾里,这就是在装B而不是在分享知识了。现在的flash AS相关书籍已经是层出不穷,在论坛上写的教程都是我个人做过,培训班里MoonSpirit教过的感觉比较实用的东西分享给大家,若与某书上的一些案例不谋而合那么纯属巧合,我也不为了图什么利益,纯属个人兴趣而已,我也不怕兄弟们学去之后跟我们抢饭碗,现在市场还是供不应求的,大家好才是真的好嘛,哈哈~
   OK,言归正传了列位仙家。刚才楼上的楼上说到了我们需加载的xml得做一些改变(用“楼上的楼上”一次好像有点……),我们为每一个技能都加上了一个id位,这个id不是随便设的,它的5位数字中,第一位为标记位,没有意义,接下来的两位代码技能类别,最后两位代表技能等级。接下来在主应用文件中xml读取完毕的事件处理函数onComplete中作出相应改变:

  1. private function onComplete(event:Event):void{
  2.         xml = new XML( (event.currentTarget as URLLoader).data );
  3.         for each( var x:XML in xml.item )
  4.         {
  5.                 var vo:SpellVO = new SpellVO();
  6.                 vo.id = Number(x.@id);
  7.                 vo.type = vo.id / 100 % 100;
  8.                 vo.name = String(x.@name);
  9.                 vo.level = vo.id % 100;
  10.                 vo.maxLevel = x.@maxLevel;
  11.                 vo.requirePoint = int(x.@requirePoint);
  12.                 vo.description = String(x.@description);
  13.                 var tmpStr:String = x.@precondition;
  14.                 if(tmpStr){
  15.                         vo.precondition = tmpStr.split("_"); //切割字符
  16.                 }else{
  17.                         vo.precondition = [];
  18.                 }
  19.                                 vo.imgSource = String(x.@imgSource);
  20.                                 voList.push( vo );
  21.                 }
  22.                 initView();
  23.                 }
复制代码

由于在xml中不可能设置一个数组,我们就使用了一种以字符串来表现数组结构的“伪数组法”,即把多个元素id之间以一个符号隔开,常用的符号有下划线“_”,竖线“|”等等,在flash中读取进这个伪数组字符串后必须对其进行切分后才能得到正确的数组,详细方法还是自己从上面的代码中领悟吧。
恩哼,数据改变了视图自然也需要做一些大便(本来想打“改变”的,既然打错了就打错算了),当技能前提条件没有满足处于不可学习状态时我们需要让其变成灰色。于是在spellView中我们加上一个设置图标可选与否的状态切换接口:

  1. private var _canbeUpdated:Boolean = true;                
  2. public function set canBeUpdated( value:Boolean ):void{
  3.         _canbeUpdated = value;
  4.         if( _canbeUpdated ){
  5.                 this.filters = [];
  6.         }else{
  7.                 this.filters = [new ColorMatrixFilter(
  8.                         [1,0,0,0,0,  
  9.                         1,0,0,0,0,  //在案例十一中制作技能冷却动画时提到过的灰度矩阵
  10.                         1,0,0,0,0,                          
  11.                         0,0,0,1,0                                                 
  12.                         ])];  
  13.         }
  14. }
  15.                 
  16. public function get canbeUpdated():Boolean{
  17.         return _canbeUpdated;
  18. }
复制代码

之后,我们在主应用文件里面添加一个记录当前可用技能点的变量availablePoint ,并在初始化视图函数initView中要设置一下图标初始状态是灰色的不可学习状态还是彩色的可学习状态:

  1. private var availablePoint:int = 5;
  2. private function initView():void{
  3.         var len:int = voList.length;
  4.         var spellCount:int = 0;  //记录需要添加到舞台上的技能图标数
  5.         for ( var i:int=0; i<len ; i++ )
  6.         {
  7.                 var item:SpellVO = voList[i];
  8.                 if( item.level == 0 ){  //取出每个技能的0级状态添加到舞台上
  9.                         var view:SpellView = new SpellView();
  10.                         view.spellVO = item;
  11.                         spellList.push( view );
  12.                         view.x = 50 + spellCount * (VIEW_WIDTH + 30);
  13.                         view.y = 50;
  14.                         addChild( view );
  15.                         view.addEventListener(MouseEvent.ROLL_OVER, onMouseOver);
  16.                         view.addEventListener(MouseEvent.ROLL_OUT, onMouseOut);
  17.                         if(item.precondition.length > 0 || availablePoint < item.requirePoint){ //如技能有学习前提或者可用技能点数没有满足技能要求则不可学习
  18.                                 view.canBeUpdated = false;
  19.                         }else{
  20.                                 view.canBeUpdated = true;
  21.                         }
  22.                         spellCount++;
  23.                 }
  24.         }
  25.         ……
  26. }
复制代码

由于当前记录在xml中的技能标签有 1*4 + 1*4 + 1*3 = 11个,不过我们把同一个技能的不同等级写在了不同标签中而已,比如前四个标签都是在描述“失明学”这一个技能。我们需要添加到舞台上的技能图标实际上只有3个。因此在initView方法中创建视图时我们仅选取每种技能的第一个标签信息即可。创建后,在舞台上,我们将看到每个技能的0级图标,这样就够了,在升级后这些技能的图标的信息或图标外观将会发生改变。
     剩下要做的就是在鼠标停留于技能图标上时显示更多的技能相关信息了,看到setToolTip()方法中,需要改造成这样:

  1. private function setToolTip( itemVO:SpellVO ):String{
  2.         var str:String = toHTMLText(itemVO.name, 20) + "<br>"
  3.         + toHTMLText("等级:" + itemVO.level + "/" + itemVO.maxLevel, 14, "#666600") + "<br><br>";
  4.         if(itemVO.level != 0){
  5.                 str += toHTMLText("当前等级", 20, "#ffffff") + "<br>"
  6.                         + toHTMLText(itemVO.description, 14, "#666600") + "<br><br>";
  7.         }else{
  8.                 str += toHTMLText(itemVO.description, 14, "#666600") + "<br><br>";
  9.         }
  10.         if(itemVO.level != itemVO.maxLevel){
  11.                 var nextSpeel:SpellVO = findNextSpell( itemVO );
  12.                 str += toHTMLText("下一等级", 20, "#55aa55" ) + "\n"
  13.                         +toHTMLText(nextSpeel.description, 14, #55aa55") + "\n\n";
  14.         }
  15.         if(itemVO.precondition.length > 0){
  16.                 str += toHTMLText("前提天赋", 18, "#ffffff") + "\n";
  17.                 var isFit:Boolean = false;
  18.                 for each(var elem:String in itemVO.precondition){
  19.                         var conditionVO:SpellVO = findVOById(elem);
  20.                         var color:String = "#ff0000";
  21.                         isFit = checkLearn( conditionVO );
  22.                         if( isFit )color = "#00ff00";
  23.                         str += toHTMLText(conditionVO.name, 14, color) + "</font>\n";
  24.                 }
  25.         }
  26.                 str += toHTMLText("需求技能点:" + itemVO.requirePoint, 14) + "<br>";
  27.         return str;
  28. }
  29. /**
  30. *  查找传入参数spellVO所是否已经学习过
  31. */        
  32. private  function checkLearn( value:SpellVO ):Boolean{
  33.         for each(var vo:SpellVO in learnedSpell){
  34.                 if( vo.id == value.id )return true;
  35.         }
  36.         return false;
  37. }
  38.                 
  39. /**
  40. * 根据指定ID查找spellVO,若查找不到则返回NULL 
  41. */        
  42. private  function findVOById( id:String ):SpellVO{
  43.         for each(var elem:SpellVO in voList){
  44.                 if( elem.id == Number( id ) ){
  45.                         return elem;
  46.                 }
  47.         }
  48.         return null;
  49. }
  50.                 
  51. /**
  52. * 根据传入spellVO参数查找它下一级别技能的spellVO,若查找不到则返回NULL
  53. */                
  54. private function findNextSpell( value:SpellVO ):SpellVO{
  55.         var index:int = voList.indexOf( value );
  56.         //如果数组中存在此技能则试着查找下一级别
  57.         if( index != -1 ){
  58.                 //为了保证index+1不因超过数组长度而报错,这里需要对index进行一下检查
  59.                 if( index < voList.length - 1 && 
  60.                         voList[index + 1].type == value.type ){
  61.                         return voList[index + 1];
  62.                 }
  63.         }
  64.         return null;
  65. }
复制代码

好了,做完这一切后的运行结果正如我们想象得那样,在技能提示框中显示出了足够多的信息,为了美观,我把toolTipComponent的宽增加了50:
<ignore_js_op>3.JPG 

2010-12-6 23:27:03 上传
下载附件 (27.6 KB)
 


啊……今天写了很多了,休息会明天再接着写吧……

 

事实上我们在setToolTip函数中已经做了很多事情,在第10-14行里判断了当前技能是否已升至最高等级,若没有升至最高级别则显示下一级别信息。在15-25行里判断当前技能是否存在前提条件技能,若存在,则遍历该技能的前提条件数组precondition中所有前提技能的id并通过findVOById方法找到该前提技能ID所对应的VO,之后通过一个叫做checkLearn的方法从learnSpell这个记录了所有已学习技能的数组中检查该前提技能是否已学习过,若已学习过则在提示框中显示该前提技能的文字为绿色,表示已满足条件,若没有学习过该技能则显示红色,表示未满足条件。
     准备工作都做好了,剩下的事情就是设置点击技能图标进行技能学习的操作了。先给主应用添加一个显示当前有多少可用技能点的文本:

  1. private var pointText:TextField;
  2. private function initView():void{
  3.                         pointText = new TextField();
  4.                         pointText.text = "可用天赋点数:" + availablePoint;
  5.                         pointText.width = pointText.textWidth + 5;
  6.                         pointText.height = pointText.textHeight + 5;
  7.                         addChild(pointText);
  8.                         ……
复制代码

之后,我们为每个技能添加鼠标点击事件侦听:

  1. private function initView():void{
  2. ……
  3. for ( var i:int=0; i<len ; i++ )
  4. {
  5.         var item:SpellVO = voList[i];
  6.         if( item.level == 0 ){
  7.                 ……
  8.                 addChild( view );
  9.                 view.addEventListener(MouseEvent.ROLL_OVER, onMouseOver);
  10.                 view.addEventListener(MouseEvent.ROLL_OUT, onMouseOut);
  11.                 view.addEventListener(MouseEvent.CLICK, onClick);
  12.                 ……
  13. }
  14. ……
  15. }
  16. private function onClick(event:MouseEvent):void{
  17.         var sv:SpellView = event.currentTarget as SpellView;
  18.         if( sv.canBeUpdated ){
  19.                 availablePoint -= sv.spellVO.requirePoint;//扣除技能点
  20.                 pointText.text = "可用天赋点数:" + availablePoint;//更新天赋点数文本
  21.                 var nextSpell:SpellVO = findNextSpell( sv.spellVO ); //找到下一技能vo
  22.                 learnedSpell.push( nextSpell );//向已学习数组中追加元素
  23.                 sv.spellVO = nextSpell; //更新当前点击技能的vo
  24.                 toolTipComponent.text = setToolTip( sv.spellVO );//更新提示框信息
  25.                 updateAllView();
  26.         }
  27. }
  28.                 
  29. /**
  30. * 更新所有技能视图状态 
  31. */                
  32. private function updateAllView():void{
  33.         for each(var sv:SpellView in spellList){
  34.         //检查技能是否已升至最高级
  35.                 if( findNextSpell(sv.spellVO) == null )
  36.                 {
  37.                 //若已升至最高等级则不继续之后的“前提条件”与“需求技能点”的检查,使用continue语句继续检查spellList数组中的下一项技能
  38.                         sv.canBeUpdated = false; 
  39.                         continue;
  40.                 }
  41.                 //查找具有前提条件的技能并在其前提条件满足并且可用技能点满足升级需求技能点时给它置于可选状态
  42.                 if( sv.spellVO.precondition.length > 0 ){
  43.                         var isFit:Boolean = false;
  44.                         for each(var elem:String in sv.spellVO.precondition){
  45.                                 var conditionVO:SpellVO = findVOById(elem);
  46.                                 isFit = checkLearn( conditionVO );
  47.                                 //若条件中有一个不满足则不继续后续条件的检查了,避免isFit标志被置回true
  48.                                 if( !isFit )break;
  49.                         }
  50.                         if( isFit && sv.spellVO.requirePoint <= availablePoint ){
  51.                                 sv.canBeUpdated = true;
  52.                         }else{
  53.                                 sv.canBeUpdated = false;
  54.                         }
  55.                 }else{
  56.                         //对于不需要前提条件的技能只需检查需求点数是否满足条件
  57.                         if( sv.spellVO.requirePoint > availablePoint ){
  58.                                 sv.canBeUpdated = false;
  59.                         }else{
  60.                                 sv.canBeUpdated = true;
  61.                         }
  62.                 }
  63.         }
  64. }
复制代码

这里面的逻辑可能会有点绕,其中涉及了对continue和break语句的灵活运用,不过这些都是在所难免的,当限制条件多的时候为了保证逻辑严谨无误,就必须写一些很“纠结”的算法,当然我的算法肯定还有可以优化的地方,这里只是抛个砖以引出玉罢了。虽然if-else语句多得令人蛋疼,但实际执行的效果还是不错的:
<ignore_js_op>1.jpg 
前提条件满足一个的情况;为了表示得清楚些,我在spellView里面加了一个显示当前技能等级的文本。
<ignore_js_op>2.jpg 
前提条件均满足的情况;
<ignore_js_op>3.jpg 
其中一个技能学到最高级的情况,注意他的技能提示框中不再显示“下一等级”的信息。
      OK,图片演示完毕,我这里只是抛砖引玉,我的代码还是非常不规范,看看主应用文件里都有将尽300行代码了,还有待优化,不过这里只是给出一个思路而已,更多的功能正等待兄弟们的开发呢,比如你可以做得更加美观一些,技能直接存在条件关系的还可以用线连接起来啥的。
最后奉上全部源码: <ignore_js_op> src.rar (14.86 KB, 下载次数: 573)

你可能感兴趣的:(游戏)