案例15——游戏物品栏的制作(AS3高级应用)

罗,列位BOYS,我们又见面了,你们好吗?(作倾听状)啊?没人回答我,好吧,那我回答你们,我很好,哈哈~在案例13中vrbcy男孩提出“求一背包系统的教程”,既然看他这么饥渴我就花了点空闲时间写了一个,我们大家一起来分享下。先看一下案例的运行效果截图:
<ignore_js_op>1.png 
作为一个比较实用的案例,我会尝试以更深层次的面向对象思想来构建此应用,列位Boys,are you fucking ready? Let`s fucking go!

面板制作
     在做之前我们必须先想好我们需求哪些东东。首先我们需求一个面板,可以用作物品栏面板,人物属性面板,商店面板等,whatever……这个面板我们提供给他3大功能:
1. 可动态设置长,宽,背景颜色,背景透明度,设置后会自动重绘更新视图;
2. 可设置标题栏,若标题栏文本未设置过,则面板不会创建标题栏(既然你不想要我,我出现也没用~)
3. 可拖动设置,若开发者设置了面板可拖动,则当你按住标题栏时可以对面板进行拖动(很符合《魔兽世界》的界面)
OK,OK,对这些需求来编写一个面板类吧:
Board.as:

  1. /**
  2.          * 基础面板类,提供标题框以及拖动功能 
  3.          * @author S_eVent
  4.          * 
  5.          */        
  6.         public class Board extends Sprite
  7.         {
  8.                 private var _boardWidth:Number;
  9.                 private var _boardHeight:Number;
  10.                 private var _backgroundColor:uint;
  11.                 private var _backgroundAlpha:Number;
  12.                 private var _isDraging:Boolean = false;
  13.                 private var _title:String;
  14.                 private var _dragable:Boolean;
  15.                 protected var titleBar:Sprite;
  16.                 
  17.                 /**
  18.                  * 
  19.                  * @param width                                面板宽
  20.                  * @param height                        面板高
  21.                  * @param backgroundColor        面板背景颜色
  22.                  * @param backgroundAlpha        面板背景透明度
  23.                  * @param title                                面板标题文字,若为空字符串,则不会创建标题栏
  24.                  * @param dragable                        是否允许拖动,若为true,则可通过点击标题栏进行面板拖动
  25.                  * 
  26.                  */                
  27.                 public function Board( Number, height:Number, 
  28.                                                            backgroundColor:uint=0x000000, backgroundAlpha:Number=1,
  29.                                                            title:String = "", dragable:Boolean = false )
  30.                 {
  31.                         _boardWidth = width;
  32.                         _boardHeight = height;
  33.                         _backgroundColor = backgroundColor;
  34.                         _backgroundAlpha = backgroundAlpha;
  35.                         drawBG();
  36.                         this.title = title;
  37.                         this.dragable = dragable;
  38.                 }
  39.                 /**
  40.                  * 为面板添加子元素 
  41.                  * @param instance        子元素实例
  42.                  * @param x                        子元素将要添加到的横坐标位置
  43.                  * @param y                        子元素将要添加到的纵坐标位置
  44.                  * 
  45.                  */                
  46.                 public function addElement( instance:DisplayObject, x:Number, y:Number ):void{
  47.                         instance.x = x;
  48.                         instance.y = y;
  49.                         addChild( instance );
  50.                 }
  51.                 
  52.                 /**
  53.                  * 绘制背景,仅绘制背景 
  54.                  * 
  55.                  */                
  56.                 protected function drawBG():void{
  57.                         this.graphics.clear();
  58.                         this.graphics.lineStyle(1, 0x999999, 0.9);
  59.                         this.graphics.beginFill( backgroundColor, backgroundAlpha );
  60.                         this.graphics.drawRect( 0, 0, boardWidth, boardHeight ); 
  61.                         this.graphics.endFill();
  62.                 }
  63.                 
  64.                 /**
  65.                  * 绘制标题栏 
  66.                  * 
  67.                  */                
  68.                 protected function drawTitle():void{
  69.                         //若是初次创建,则进行初始化工作
  70.                           if( titleBar == null ){                     
  71.                                 titleBar = new Sprite();
  72.                                 titleBar.graphics.clear();
  73.                                 titleBar.graphics.lineStyle(1);
  74.                                 titleBar.graphics.beginFill( 0x444444 );
  75.                                 titleBar.graphics.drawRect( 0, 0, boardWidth, 40 );
  76.                                 titleBar.graphics.endFill();
  77.                                 titleBar.addChild( titleText );
  78.                                 titleText.x = 5;
  79.                                 titleText.y = 5;
  80.                                 titleText.selectable = false;
  81.                                 titleBar.y = -40;
  82.                                 this.addChild( titleBar );
  83.                                 titleBar.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
  84.                                 titleBar.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
  85.                                 titleBar.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  86.                         }
  87.                         titleText.htmlText = title;
  88.                         titleText.width = Math.min( titleText.textWidth + 5, boardWidth ); //限制最大宽度不超过本标题框宽度
  89.                         titleText.height = Math.min( titleText.textHeight + 5, 40 ); //限制最大高度不超过本标题框高度
  90.                 }
  91.                 
  92.                 protected function onMouseDown(event:MouseEvent):void{
  93.                         if( _dragable ){
  94.                                 this.startDrag();
  95.                         }
  96.                 }
  97.                 
  98.                 protected function onMouseUp(event:MouseEvent):void{
  99.                         if( _dragable ){
  100.                                 this.stopDrag();
  101.                         }
  102.                 }
  103.                 
  104.                 protected function onAddedToStage(event:Event):void{
  105.                         stage.addEventListener(Event.MOUSE_LEAVE, onMouseLevel);
  106.                 }
  107.                 
  108.                 protected function onMouseLevel(event:Event):void{
  109.                         if( _dragable ){
  110.                                 this.stopDrag();
  111.                         }
  112.                 }
  113. //----------------------我是分割线,你可以叫我老分-----------------------------//
  114. //----------------------下面开始是读写器-----------------------------//                
  115.                 public function get backgroundAlpha():Number
  116.                 {
  117.                         return _backgroundAlpha;
  118.                 }
  119.                 
  120.                 public function set backgroundAlpha(value:Number):void
  121.                 {
  122.                         _backgroundAlpha = value;
  123.                         drawBG();
  124.                 }
  125.                 
  126.                 public function get backgroundColor():uint
  127.                 {
  128.                         return _backgroundColor;
  129.                 }
  130.                 
  131.                 public function set backgroundColor(value:uint):void
  132.                 {
  133.                         _backgroundColor = value;
  134.                         drawBG();
  135.                 }
  136.                 
  137.                 public function get boardHeight():Number
  138.                 {
  139.                         return _boardHeight;
  140.                 }
  141.                 
  142.                 public function set boardHeight(value:Number):void
  143.                 {
  144.                         _boardHeight = value;
  145.                         drawBG();
  146.                 }
  147.                 
  148.                 public function get boardWidth():Number
  149.                 {
  150.                         return _boardWidth;
  151.                 }
  152.                 
  153.                 public function set boardWidth(value:Number):void
  154.                 {
  155.                         _boardWidth = value;
  156.                         drawBG();
  157.                 }
  158.                 
  159.                 public function set dragable(value:Boolean):void
  160.                 {
  161.                         _dragable = value;
  162.                 }
  163.                 
  164.                 public function get title():String
  165.                 {
  166.                         return _title;
  167.                 }
  168.                 
  169.                 public function set title(value:String):void
  170.                 {
  171.                         _title = value;
  172.                         if( title != "" ){
  173.                                 drawTitle();
  174.                         }
  175.                 }
  176.                 
  177.         }
复制代码

这样子的代码和以往的有一些不同,刚才一开头就说了,我们在这个案例中将采用深层次的面向对象思想,那么在纯粹面向对象编程中,private关键字声明的私有变量命名一般以下划线开头,且类中变量大多使用读写器(get/set方法)来对外提供接口调用。我们一起来细细看看这个简单的几十行代码中隐藏了哪些思想。
     我们在构造函数中期望把构造函数里的参数赋值给类中的私有变量并进行绘制背景和标题栏等初始化工作。不过我们若不希望一个面板只有初始化时才能被设置背景颜色等外观的话,就必须给个接口让某项外观属性,如背景颜色,背景透明度等属性被更改时外观也会随之改变。此时就需要借助set方法来做到,在上一案例中我也用到了set方法的这个方便的特性。于是,我们在backgroundAlpha,backgroundColor,boardWidth以及boardHeight这四个会改变外观的属性的set方法中做了些手脚,之后,当你使用类似backgroundAlpha = 0.5时就不仅仅是做了一次简单的变量赋值而已,后台会去调用drawBG()方法重绘整个背景外观,不用担心Board类中的子元素会被遮挡的问题,因为使用graphics方法绘出的图形会放在最底层。不过,我们在构造函数里,希望只当外观的各项属性(长,宽,背景颜色及透明度)都被设置完毕后再绘制背景,因此我们直接给私有变量进行赋值而不用set方法进行赋值:

  1. //正确方法,只调用一次drawBG();
  2. _boardWidth = width;
  3. _boardHeight = height;
  4. _backgroundColor = backgroundColor;
  5. _backgroundAlpha = backgroundAlpha;
  6. drawBG();
  7. /* 错误方法,每调用一次set方法进行变量赋值都会调用drawBG();进行一次背景绘制, 下面5句代码就进行了五次对drawBG()的调用
  8. boardWidth = width;
  9. boardHeight = height;
  10. this.backgroundColor = backgroundColor;
  11. this.backgroundAlpha = backgroundAlpha;
  12. drawBG(); */
复制代码

绘制完背景后,我们需要为Board类添加标题框,且想让外部在每一次设置标题文字title属性时标题框文本都能正确更新,我们在title的set方法中也做一些工作,让_title属性被赋值的同时刷新标题栏中的文本。阅读一下drawTitle()中的代码,你就知道每一次当用类似赋值语句 title = "12" 时,都会在给_title属性赋值同时调用title的set方法中的drawTitle方法来动态更改标题栏_titleBar中的文本控件_titleText的文本以及长宽。在设置标题栏titleBar位置的时候,为了不让标题框对面板内其他元素产生遮挡,我们选择将其y值设置为负值。另外,为了实现标题框的拖动。我需要对标题栏进行MOUSE_DOWN和MOUSE_UP事件侦听,在这两个事件处理函数中会判断当前面板是否设置拖动开关dragable为true,若是,则对整个面板实例进行拖动。不过在实际应用中你可能会发现当你按住鼠标进行拖动时当鼠标移出flash player窗口外在回来时由于MOUSE_UP事件未被触发,被拖动的物体还是处于拖动的状态,造成一个“鼠标跟随”现象。为了解决这个问题,我们找到在stage对象中存在一个叫做MOUSE_LEAVE的事件,它在鼠标移出FP窗口时触发,为此,我们可以监听之,当此事件触发时调用stopDrag方法来停止拖动。需要注意的是,为了防止stage实例为空,我们需要确保在对stage实例进行访问时,Board实例已被添加到舞台上,因此我们监听了ADDED_TO_STAGE事件。

 

说实话,此贴的关注程度远超我的想象,一天功夫点击率就超过了150,是我一般帖子的2倍以上速度快,这就让本想偷懒的我必须勤快更新以满足列位boys的好奇心。
   不过作为一位AS开发人员来说,追求代码的含金量是十分必要的,在上周五发稿时我界面与程序已经完成制作,但是代码整体构造并不是很符合MVC规范,可拓展性较低,冗余度较大,在公司同事的建议下我用接下来几天时间重构了下代码(一想到重构就想到某人^_^)。在发帖的过程中我也会再修复一些不规范之处,若有任何意见可以随时提出,共同学习吧~
     废话啰了一通,开始我们的……额……又一轮理论讲解吧,不讲下理论看代码会有点头疼。
静态变量与静态函数
     在AS中,有着和C语言,JAVA等其他语言一样的静态变量与静态函数的概念,所谓静态,就是指程序一开始就创建好了的东东,不需要实例化即可使用,且静态的变量或者函数是全局可用的。如下列代码:

  1. public class STC
  2. {
  3.         public static var Pig:String = "s_event is not a pig";
  4.         public static function ohMyGod:String(){
  5.                return "oh, my mother god~";
  6.         }
  7. }
复制代码

这样我们在类STC中声明了一个静态变量Pig,一个静态函数ohMyGod,这样我们在外部就可以不实例化一个STC对象便可以使用之:

  1. public class Main extends Sprite
  2. {
  3.       public function Main(){
  4.           var shit1:String = STC.Pig;//看到没?直接用类名加点操作符即可调用到变量与函数
  5.           var shit2:String = STC.ohMyGod;
  6.       /*此时你不需要通过像下面的代码一样通过实例化STC类来拿到静态变量与函数
  7.        var stc:STC = new STC();
  8.            var shit1:String = stc.Pig;
  9.            var shit2:String = stc.ohMyGod();
  10.       */
  11.       }
  12. }
复制代码

是不是贼TM方便啊?啧啧啧(摇手指)~有利必有弊,对于静态变量,由于其是全局可用的,所以你的任何一次更改都会永久保存在该变量中,例如,在A类和B类中都存在STC.Pig++这样一个自加语句,那么之后不论是在A还是B中访问STC.Pig,都将会是STC.Pig加2后的结果。对于静态函数,在函数中不可访问非静态变量,如下列代码:

  1. public calss Lalala{
  2.      private var comeOn:int = 1;
  3.      private static var baBy:int = 2;
  4.      private static function youAreComing{
  5.          comeOn ++; //报错,不可访问
  6.       baBy++; //无任何问题,可以访问
  7.     } 
  8. }
复制代码

这样解释应该能明白了个大概,还需要通过自己尝试、多加练习才能领悟其中精妙之处。
单例模式
    一般来说,学AS达到入门级别的人一般都听说过单例模式(Singleton),它的基本语法如下:

  1. public class Singleton
  2. {
  3.         private static var instance:Singleton= null;
  4.                 
  5.         public function Singleton()
  6.         {
  7.                 if( instance == null ){
  8.                                          //do something
  9.                 }else{
  10.                          throw new Error("Instance is alreally exist");                
  11.                                 }
  12.         }
  13.                 public static function getInstance():Singleton{
  14.                 if( instance == null ){
  15.                         instance = new Singleton();
  16.                 }
  17.                 return instance;
  18.         }
  19. }
复制代码

先解释一下代码。Singleton是一个单例类,简称单例。其中声明了一个静态变量instance保存它自己,即Singleton类的一个实例,初始化时为空值。在它的构造函数中存在一个条件语句,若instance为空,则像其他非单例类一样,做一些初始化工作。不过若是instance中已存在一个Singleton实例,那么你就别想再次实例化它了,会抛出一个错误,提示说什么什么实例已经存在之类的鬼话。看样子,当你第二次调用new Singleton()语句时程序就会报错阻止你再次实例化一个Singleton实例。这样有啥用呢?简单地说,如果你辛辛苦苦地培养了一个Singleton实例,为它内部保存了很多有用的数据,一旦你或者别的和你合作开发一个项目的程序员不小心重新new了一次Singleton类,那你之前保存的信息将会全部遗失,为此,你将表示遗憾。单例模式一般会具备一个叫做getInstance的静态方法,我们用它来拿到全项目中独一无二的Singleton实例,若instance为空,则会new一个新的实例保存起来并返回。看看下列代码帮助列位理解:

  1. public class A {
  2.     public function A(){
  3.          var s:Singleton = Singleton.getInstance();//上面已经提到过,getInstance是静态方法,可以用这种类名+静态方法的方式调用
  4.          s.counter  = 4;//假如counter是Singleton类中一个public 的 int变量
  5.      }
  6. }
复制代码

在A类中我们通过Singleton的getInstance方法拿到了一个Singleton实例并对其中的counter变量进行了赋值。当执行过此句之后在项目中任何一个地方都可以通过

  1. trace( Singleton.getInstance().counter );
  2. /*或者
  3. var s:Singleton = Singleton.getInstance();
  4. trace(  s.counter  );
复制代码

这样的方式拿到Singleton类中的public变量值,上列的去死(trace)结果都是4。这样的话,单例模式就提供给我们一种极佳的存储数据方式,因为单例是全局可用的,所以哪个类需要数据,就调用单例的getInstance方法去拿到这个独一无二的数据单例然后调用其中保存的Public变量值即可。用下图来表示在一个项目中,单例和其他文件的关系:
<ignore_js_op>1.JPG 
把整个工程比作一个公司的话,别的部门,不管是营销,保洁,活动组织还是什么部门,要用到钱都会去找财务部拿钱,不过这些要用到钱的部门互相之间却没多大关系,它们不需要隔壁是哪个部门,别的部门在什么位置啥的,因为别的部门对它们来说没什么用,它们只需要知道它们需要钱的时候通过getInstance去拿即可,这样就大大降低了耦合度还不容易在写代码时把脑袋搞乱……
物品
     终于讲完了基础理论知识该回到我们的fucking正题上来了,既然是做一个fucking物品栏,那我们的主角自然是那些fucking物品,所以和以前一样,为了把数据与视图分开,形成我们犀利的M-V结构,就得创建我们的fucking物品VO以及fucking物品View,看了代码再fucking~:
ItemVO.as(我敢打赌这是我们这个案例中最容易看懂的文件了)

  1. public class ItemVO
  2.         {
  3.                 public var id:String;
  4.                 public var name:String;
  5.                 public var type:String;
  6.         }
复制代码

ItemView.as

  1. public class ItemView extends Sprite
  2.         {
  3.                 private static const assetsFolder:String = "assets/"; //图片文件存放路径
  4.                 private var _itemVO:ItemVO;
  5.                 private var _viewBM:Bitmap;
  6.                 private var _pos:int;
  7.                 private var _viewW:Number;
  8.                 private var _viewH:Number;
  9.                 private var listenerList:Array = new Array();
  10.                 
  11.                 public function ItemView()
  12.                 {
  13.                         super();
  14.                         _viewBM = new Bitmap();
  15.                         addChild( _viewBM );
  16.                 }
  17.                 /**
  18.                  * 重载的 addEventListener方法,在添加事件侦听时将其存在一个数组中,以便记录所有已添加的事件侦听
  19.                  */                
  20.                 override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void{
  21.                         var obj:Object = new Object();
  22.                         obj.type = type;
  23.                         obj.listener = listener;
  24.                         listenerList.push( obj );
  25.                         super.addEventListener(type, listener, useCapture, priority, useWeakReference);
  26.                 }
  27.                 /**
  28.                  * 自我毁灭,删除所有事件侦听器以及从父显示对象中移除,等待垃圾回收 
  29.                  * 
  30.                  */                
  31.                 public function destory():void{
  32.                         for each( var obj:Object in listenerList ){
  33.                                 removeEventListener( obj.type, obj.listener );
  34.                         }
  35.                         this.parent.removeChild( this );
  36.                 }
  37.                 
  38.                 private function loadImg( url:String ):void{
  39.                         var loader:Loader = new Loader();
  40.                         loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
  41.                         loader.load( new URLRequest( url ) );
  42.                 }
  43.                 
  44.                 private function onComplete( event:Event ):void{
  45.                         _viewBM.bitmapData = ( (event.currentTarget as LoaderInfo).content as Bitmap ).bitmapData;
  46.                         _viewBM.width = width;
  47.                         _viewBM.height = height;
  48.                 }
  49. //----------------------我是分割线,你可以叫我老分-----------------------------//
  50. //----------------------下面开始是读写器-----------------------------//        
  51.                 override public function set width(value:Number):void{
  52.                         _viewW = value;
  53.                         _viewBM.width = value;
  54.                 }
  55.                 
  56.                 override public function get width():Number{
  57.                         return _viewW;
  58.                 }
  59.                 
  60.                 override public function set height(value:Number):void{
  61.                         _viewH = value;
  62.                         _viewBM.height = value;
  63.                 }
  64.                 
  65.                 override public function get height():Number{
  66.                         return _viewH;
  67.                 }
  68.                 
  69.                 public function get pos():int
  70.                 {
  71.                         return _pos;
  72.                 }
  73.                 public function set pos(value:int):void
  74.                 {
  75.                         _pos = value;
  76.                 }
  77.                 public function get itemVO():ItemVO
  78.                 {
  79.                         return _itemVO;
  80.                 }
  81.                 public function set itemVO(value:ItemVO):void
  82.                 {
  83.                         _itemVO = value;
  84.                         loadImg( assetsFolder + itemVO.id + ".jpg" );
  85.                 }
复制代码

这个类中乍看之下没什么复杂的逻辑算法,不过还是有以下几点需要注意的:

 

1.有人会为如何消除一个显示对象而发愁,比如不晓得它的父对象是谁,那看看destory方法里是怎么做的吧,直接通过this.parent来拿到父对象然后将自己从父对象中移除即可。不过仅仅removeChild了还没有彻底毁灭一个对象,flash中的垃圾回收机制是回收那些用不到的对象,不过一旦我们给一个对象添加了事件监听器那么这个对象是不会被回收掉的,所以我们必须将一个对象中添加的所有事件监听给摆平才行,那么如何能知道该对象已被添加了多少事件侦听了呢?这就需要我们在一个事件侦听器被添加时将其保存起来,我们重载了addEventListener方法,在每个事件侦听器被添加时保存removeEventListen方法所需要的两个参数:type以及listener,放到一个Object中去。你应该知道,Object是一个动态对象(dynamic),所谓动态对象,就是可以随时为其添加属性,那么上面在重载addEventListener方法中的语句:

  1. var obj:Object = new Object();
  2. obj.type = type;
  3. obj.listener = listener;
复制代码

就是给一个Object添加type 与listener两个属性,并用此两个属性来保存我们需要的两个信息,怎样,用起来比array还方便些吧?^_^这样就保证了每次添加事件侦听都不会漏掉,每一个侦听器都被保存了起来,在对象自我毁灭时只需要把这些侦听器全部取出然后removeEventListener即可。
PS:请不要写类似 addS_EventListener这样的语句,你这样监听我可是侵犯了我的隐私,我奉劝你还是趁早地调用removeS_EventListener吧。
2.在itemVO的set方法中,我们和上个案例一样,设置了自动加载物品VO所对应图片的功能,每个ItemVO所对应的图片名需要与其id一样,还必须是jpg格式的哦。在加载完毕后,要想改变ItemView的大小,你仅设置this.width / this.height 或者在外部设置ItemView实例的width与height是没用的,因为ItemView继承了Sprite类,对于一个Sprite,它的width和height是不稳定的,随时在变,我玩falsh到现在还是不敢随意调用或者设置一个Sprite的长与宽。因此我们要想改变其大小就得抓其根本,从Bitmap上下毒手。根据这个道理,我们重载了Sprite的width和height读写器,当外部设置ItemView时就间接地改变了图片Bitmap的长宽,如不这样做,你外部设置长宽后将看不到任何效果。若还是不能理解这里为什么要重载width和height的set方法,请拓展阅读:Sprite对象的隐藏特性
物品提示
     鼠标移动到一个物品上时一定得出现物品提示框,不过我们首先要考虑三方面因素:
1. 物品提示框不能被别的fucking东西遮挡;
2. 我们不止物品栏需要提示框,在别的很多窗口,如以后要做的人物装备窗口、道具商店窗口里都要用到它,得提供一种方式让我们随时可以调用到提示框的实例,而不用去寻找其被addChild哪个类里面,然后通过这个类 + 点操作符 + 提示框实例名 来访问。
3. 请参照前两条。
    那么至于解决方案嘛,请允许我去洗个澡再回来讲……1分钟过去了……5分钟过去了……15分钟……嗨,i am coming, baby,刚才在浴池里撒了泡尿,你们别告诉和我合租的那个兄弟,不然他以后都不会进那个浴池洗澡了,哇哈哈~咳咳,继续刚才的话题,第一个和第二个问题的解决方案就是把物品提示框写成单例,然后在主应用中把此单例放在最后addChild即可。第三个问题的解决方案请再参照前两条(少说两句废话行不)。
还记得上个案例我们一起创建过的提示框组件不?它叫做LifeToolTip(Life是我的另一个网名,用来打魔兽用用的),小加修改后即可重用~
LifeToolTip.as:

  1. /**
  2.          * 悬停提示框组件,提供两种外形:普通矩形外观,箭头形外观。初始化后会自动隐藏,使用时请手动在其需要显示时设置visible为true并设置坐标
  3.          * @author S_eVent 
  4.          * 
  5.          */        
  6.         public class LifeToolTip extends Sprite
  7.         {
  8.                 public static const NORMAL_VIEW:int = 101;
  9.                 public static const ARROW_VIEW:int = 102;
  10.                 
  11.                 protected var textField:TextField;
  12.                 
  13.                 private var bgW:Number;
  14.                 private var bgH:Number;
  15.                 private var backgroundColor:uint;
  16.                 private var backgroundAlpha:Number;
  17.                 private var type:int;
  18.                 private var Width:Number;
  19.                 private var Height:Number;
  20.                 
  21.                 /**
  22.                  * 
  23.                  * @param text                                提示框文本信息,可接受HTML标签
  24.                  * @param width                                提示框宽度,若为0会根据文本多少自动调整
  25.                  * @param height                        提示框长度,若为0会根据文本多少自动调整
  26.                  * @param fontColor                        提示框文本颜色
  27.                  * @param backgroundColor        提示框背景色
  28.                  * @param backgroundAlpha        提示框背景透明度
  29.                  * @param type                                外观:可选类型有:<p>NORMAL_VIEW:普通矩形类型;<p>ARROW_VIEW:箭头类型
  30.                  * 
  31.                  */                
  32.                 public function LifeToolTip(text:String="", Number=0, height:Number=0, fontColor:uint=0x000000, backgroundColor:uint=0xffffff, 
  33.                                                                         backgroundAlpha:Number=0.6, type:int=NORMAL_VIEW )
  34.                 {
  35.                         super();
  36.                         this.mouseEnabled = false;//不允许此组件接收鼠标消息
  37.                         this.backgroundColor = backgroundColor;
  38.                         this.backgroundAlpha = backgroundAlpha;
  39.                         this.type = type;
  40.                         Width = width;
  41.                         Height = height;
  42.                         textField = new TextField();
  43.                         textField.htmlText = text;
  44.                         textField.selectable = false;//不允许文本被选择
  45.                         textField.textColor = fontColor;
  46.                         textField.multiline = true;//允许文本多行
  47.                         textField.wordWrap = true;//让文本自动换行
  48.                         addChild( textField );
  49.                         
  50.                         drawToolTipBg();
  51.                         this.visible = false;
  52.                 }
  53.                 
  54.                 public function set text( value:String ):void{
  55.                         textField.htmlText = value;
  56.                         drawToolTipBg();
  57.                 }
  58.                 
  59.                 private function drawToolTipBg( ) : void {
  60.                         if(Width == 0){
  61.                                 textField.width = textField.textWidth + 5;
  62.                                 bgW = textField.width + 10;
  63.                         }else{
  64.                                 textField.width = Width;
  65.                                 bgW = Width;
  66.                         }
  67.                         if(Height == 0){
  68.                                 textField.height = textField.textHeight + 5;
  69.                                 bgH = textField.height + 10;
  70.                         }else{
  71.                                 textField.height = Height;
  72.                                 bgH = Height;
  73.                         }
  74.                         this.graphics.clear();
  75.                         this.graphics.lineStyle(0, 0x000000, .6);
  76.                         if( backgroundAlpha > 1 ){
  77.                                 backgroundAlpha = 1;
  78.                         }else if( backgroundAlpha < 0 ){
  79.                                 backgroundAlpha = 0;
  80.                         }
  81.                         this.graphics.beginFill( backgroundColor, backgroundAlpha );
  82.                         switch( type )
  83.                         {
  84.                                 case NORMAL_VIEW:
  85.                                         this.graphics.drawRect(0, 0, bgW, bgH);
  86.                                         break;
  87.                                 case ARROW_VIEW:
  88.                                         this.graphics.lineTo(-10, 20);
  89.                                         this.graphics.lineTo(-bgW/2, 20);
  90.                                         this.graphics.lineTo(-bgW/2, 20 + bgH);
  91.                                         this.graphics.lineTo(bgW/2, 20 + bgH);
  92.                                         this.graphics.lineTo(bgW/2, 20);
  93.                                         this.graphics.lineTo(10, 20);
  94.                                         this.graphics.lineTo(0, 0);
  95.                                         textField.x = -bgW/2 + 3;
  96.                                         textField.y = 23;
  97.                                         break;
  98.                         }
  99.                         this.graphics.endFill();
  100.                 }
  101.         }
复制代码

我们把此组件写成单例,用一个叫做GameToolTip的类继承之:
GameToolTip.as:

  1. public class GameToolTip extends LifeToolTip
  2.         {
  3.                 private static var instance:GameToolTip = null;
  4.                 
  5.                 public function GameToolTip(text:String="", Number=0, height:Number=0, fontColor:uint=0x000000, backgroundColor:uint=0xffffff, backgroundAlpha:Number=0.6, type:int=NORMAL_VIEW)
  6.                 {
  7.                         if( instance == null ){
  8.                                 super(text, width, height, fontColor, backgroundColor, backgroundAlpha, type);
  9.                                 instance = this;
  10.                         }else{
  11.                                 trace("fuck you!没看见实例已存在了么?!");
  12.                         }
  13.                 }
  14.                 
  15.                 public static function getInstance(text:String="", Number=0, height:Number=0, fontColor:uint=0x000000, 
  16.                                                                                    backgroundColor:uint=0xffffff, backgroundAlpha:Number=0.6, type:int=NORMAL_VIEW):GameToolTip{
  17.                         if( instance == null ){
  18.                                 instance = new GameToolTip(text, width, height, fontColor, backgroundColor, backgroundAlpha, type);
  19.                         }
  20.                         return instance;
  21.                 }
  22.         }
复制代码

你可以选择使用new GameToolTip(若干参数)或者GameToolTip.getInstance(若干参数)的方式来初始化提示框单例,需要注意的是,若使用前一种new GameToolTip的方式来初始化单例时需要在GameToolTip的构造函数中写上instance = this的语句以确保instance保存了初始化得到的实例。
数据模型
     刚才在楼上讲解单例模式的时候提到,单例模式是最好的数据存储方式,那么能够担当管理整个项目中数据的重任就必须交给一个单例的数据模型了,我们称呼其为GameModel:
GameModel.as:

  1. /**
  2.          * 游戏数据模型(单例)
  3.          * @author S_eVent
  4.          * 
  5.          */
  6.         public class GameModel
  7.         {
  8.                 public var voList:Array = new Array();//保存全部ItemVO
  9.                 public var isDraging:Boolean = false;//记录当前是否处于物品拖动状态
  10.                 public var itemList:Array = new Array();//保存全部ItemView
  11.                 public var currentDragItem:ItemView;//记录当前拖动的ItemView
  12.                 
  13.                 private static var instance:GameModel = null;
  14.                 
  15.                 public function GameModel()
  16.                 {
  17.                         if( instance == null ){
  18.                                 instance = this;
  19.                         }else{
  20.                                 trace("fuck you!没看见实例已存在了么?!");
  21.                         }
  22.                 }
  23.                 
  24.                 public static function getInstance():GameModel{
  25.                         if( instance == null ){
  26.                                 instance = new GameModel()
  27.                         }
  28.                         return instance;
  29.                 }
  30.         }
复制代码

这代码里没有难点,我们只需要把需要全局使用的变量放进数据模型中即可。在这个例子中,我们要全局使用到的变量就上述几个了。

 

物品位置管理者
      对于我这样一个物品栏的案例来说,最大的难点之一无非就是对物品位置的管理了,物品摆放时需要在物品栏表格中自左往右依次排开,另外,物品被拖起以后用户有两种改变物体位置的选择:一,把物品放到另一个空格子中;二,与另外一个物品交换位置。更加恶心的一点限制条件是,如果你物品栏中有两个物品,一个排在左边第一个格子中,另一个排在左边第三个格子中,当你新获得一个物品时需要自左往右检查空着的格子,此物品将会被添加到这个空格子中,因此按照我们的想法,此新物品会被摆放在左边第二个格子中,在第一第二个物品之间,而不会被添加到第二个物品右边或者别的什么地方去。对于这些问题,我一开始的想法是在每个ItemView中用一个pos变量来保存物体的位置,然后之后做的都是一些根据鼠标点击位置来换算成格子位置啊,物品间互相交换pos变量值啊什么的事情。不过事实证明这样子做要牵扯到的对象太多了,几乎每个ItemView都会被波及到,然后GameModel里面的保存它们的itemList会被一次又一次的遍历,效率着实不敢恭维。好在后面同事们给了我一个很不错的建议,用一个专门的位置管理者来管理位置信息,因为我们的背包栏中物体都是按格子摆放的,所以我们就给这个位置管理器取名为表格(Grid)。
Grid.as:

  1. /**
  2.          * 格子类,存放物品格子信息,以数组cellsInfo保存全部格子信息,若格子中无物品,则为Null,若有,则为一个ItemView实例
  3.          * @author S_eVent
  4.          * 
  5.          */        
  6.         public class Grid
  7.         {
  8.                 public var cellsInfo:Array;
  9.                 private var _rowNum:int = 0;
  10.                 private var _colNum:int = 0;
  11.                 private var _maxCells:int;
  12.                 
  13.                 public function Grid( rowNum:int, colNum:int )
  14.                 {
  15.                         cellsInfo = new Array( rowNum );
  16.                         for( var i:int=0; i<rowNum; i++ ){
  17.                                 cellsInfo[i] = new Array( colNum );
  18.                         }
  19.                         this.rowNum = rowNum;
  20.                         this.colNum = colNum;
  21.                 }
  22.                                 
  23.                 /**
  24.                  * 设置某个格子的信息 
  25.                  * @param row        欲设置格子的行号
  26.                  * @param col        欲设置格子的列号
  27.                  * @param view        欲设置格子的对象信息
  28.                  * 
  29.                  */                
  30.                 public function setCell( row:int, col:int, value:Object):void{
  31.                         //查询被设置对象是否已在格子中,若在,就把他原来的位置和现在的位置信息互换,若不在,则直接把此格子中填充进信息
  32.                         var pos:Array = getCell(value);
  33.                         if( pos.length > 0 ){
  34.                                 var temp:Object = cellsInfo[row][col];
  35.                                 cellsInfo[row][col] = value;
  36.                                 cellsInfo[pos[0]][pos[1]] = temp;
  37.                         }else{
  38.                                 cellsInfo[row][col] = value;
  39.                         }
  40.                 }
  41.                 
  42.                 /**
  43.                  * 查询某个物品对象所在格子 
  44.                  * @param view        欲查询对象
  45.                  * @return                 位置数组,格式如[row, col],若查询不到,则返回空数组
  46.                  * 
  47.                  */                
  48.                 public function getCell( value:Object ):Array{
  49.                         for(var i:int=0; i<cellsInfo.length; i++){
  50.                                 for(var j:int=0; j<cellsInfo[i].length; j++){
  51.                                         if( cellsInfo[i][j] == value ){
  52.                                                 return [i,j];
  53.                                         }
  54.                                 }
  55.                         }
  56.                         return [];
  57.                 }
  58.                 
  59.                 /**
  60.                  * 为表格中的空格子添加对象,若有空格,则返回对象添加到的索引位置;若无空格,则返回空数组
  61.                  * @param value        欲添加的对象
  62.                  * 
  63.                  */                
  64.                 public function addItem( value:Object ):Array{
  65.                         for( var i:int=0; i<cellsInfo.length; i++ ){
  66.                                 for( var j:int=0; j<cellsInfo[i].length; j++ ){
  67.                                         if( cellsInfo[i][j] == null ){
  68.                                                 cellsInfo[i][j] = value;
  69.                                                 return [i,j];
  70.                                         }
  71.                                 }
  72.                         }
  73.                         return [];
  74.                 }
  75.                 
  76.                 /**
  77.                  * 将某对象从格子数组中删去
  78.                  * @param value        欲删除对象
  79.                  * 
  80.                  */                
  81.                 public function removeItem( value:Object ):void{
  82.                         for( var i:int=0; i<cellsInfo.length; i++ ){
  83.                                 for( var j:int=0; j<cellsInfo[i].length; j++ ){
  84.                                         if( cellsInfo[i][j] == value ){
  85.                                                 cellsInfo[i][j] = null;
  86.                                                 return;
  87.                                         }
  88.                                 }
  89.                         }
  90.                 }
  91.                 
  92.                 /**
  93.                  * 将某格子数组中某位置处元素删去
  94.                  * 
  95.                  */                
  96.                 public function removeItemAt( row:int, col:int ):void{                
  97.                         cellsInfo[row][col] = null;
  98.                 }
  99. //----------------------我是分割线,你可以叫我老分-----------------------------//
  100. //----------------------下面开始是事件处理函数-----------------------------//        
  101.                 public function get maxCells():int
  102.                 {
  103.                         return _maxCells;
  104.                 }
  105.                 
  106.                 public function get colNum():int
  107.                 {
  108.                         return _colNum;
  109.                 }
  110.                 
  111.                 public function set colNum(value:int):void
  112.                 {
  113.                         _colNum = value;
  114.                         _maxCells = colNum * rowNum;
  115.                 }
  116.                 
  117.                 public function get rowNum():int
  118.                 {
  119.                         return _rowNum;
  120.                 }
  121.                 
  122.                 public function set rowNum(value:int):void
  123.                 {
  124.                         _rowNum = value;
  125.                         _maxCells = colNum * rowNum;
  126.                 }
  127.         }
复制代码

唉,对于这种纯逻辑的抽象类,必须上图才能解释清楚,翠花,come some 阿米达 图片~
<ignore_js_op>2.JPG 
     我们在Grid类中创建了一个二维数组,数组中的每一个元素都是一行,数组中有多少个元素表格就有多少行,所以第一行就是G[0],第二行就是G[1]。然而,每一行中又保存了很多格子,如果一行中有N个格子,那么这个表格就有N列。所以第一行第一列就是G[0][0],第一行第二列就是G[0][1]以此类推。
   在Grid的初始化函数中我们需要行数与列数两个参数,它们决定了一个表格类的大小,我们根据传入的行数与列数参数来初始化一个二维数组,这个二维数组中目前所有的元素都为空(null),但是它的容量已经定了,有rowNum个元素,每个元素中又能存放colNum个元素。最后我们通过rowNum和ColNum的set方法给私有变量赋值并计算出格子总数以便日后使用。
   有了存放所有物品位置信息的二维数组,剩下的一些关于物品位置的管理就方便了,我们可以轻易地检索到其中某个物品的位置,高效地交换物品位置等等,详情还是各位自己看看后面几个函数的代码吧。我建议你们把那些方法的参数都设为Object而不是ItemView之类的,要知道,Object是一切类的基类,一切类的祖宗,你用Object类型作参数可以让外部传入任何一种类型,于是你的Grid类就可以在所有涉及到格子管理的flash项目中派上用场而不需要修改任何代码^_^。
物品面板及包裹面板
      楼主,准备工作做得那么多,来点重量级的吧。ALL RIGHT , MY BOY, LET`S See Something interesting.我们在一开始就创建一个Board面板类,它提供给我们一个带背景的,带标题栏的面板。那么对于物品背包、人物装备栏、道具商店窗口等带有道具的面板来说,Board提供的功能就不够用了。我们需要拓展一下之,于是就诞生了ItemsBoard类, 它在继承了它老爹Board类提供的功能基础上又新增了物品提示框,物品拖动以及物品摆放等物品管理功能。
ItemsBoard.as:

  1. /**
  2.          * 可放置物品的面板,基于Board类
  3.          * @author S_eVent
  4.          * 
  5.          */
  6.         public class ItemsBoard extends Board
  7.         {
  8.                 public var grid:Grid;
  9.                 private var gameModel:GameModel = GameModel.getInstance();
  10.                 
  11.                 /**
  12.                  * 
  13.                  * @param width                                面板宽
  14.                  * @param height                        面板高
  15.                  * @param backgroundColor        面板背景颜色
  16.                  * @param backgroundAlpha        面板背景透明度
  17.                  * @param title                                面板标题文字,若为空字符串,则不会创建标题栏
  18.                  * @param dragable                        是否允许拖动,若为true,则可通过点击标题栏进行面板拖动
  19.                  * 
  20.                  */        
  21.                 public function ItemsBoard(Number, height:Number, backgroundColor:uint=0x000000, 
  22.                                                                    backgroundAlpha:Number=1, title:String = "", dragable:Boolean = false)
  23.                 {
  24.                         super(width, height, backgroundColor, backgroundAlpha, title, dragable);
  25.                 }
  26.                 
  27.                 /** 
  28.                  * 开始物品拖动
  29.                  */                
  30.                 public function startItemDrag( ):void{
  31.                         gameModel.currentDragItem.startDrag( true );
  32.                         gameModel.currentDragItem.mouseEnabled = false;
  33.                         gameModel.isDraging = true;
  34.                 }
  35.                 
  36.                 /** 
  37.                  * 停止物品拖动
  38.                  * @param point  物体停止位置
  39.                  */                
  40.                 public function stopItemDrag(  ):void{
  41.                         gameModel.currentDragItem.stopDrag();
  42.                         gameModel.currentDragItem.mouseEnabled = true;
  43.                         gameModel.isDraging = false;
  44.                         refreshView();
  45.                 }
  46.                 
  47.                 public function refreshView():void{
  48.                         for( var i:int=0; i<grid.cellsInfo.length; i++ ){
  49.                                 for( var j:int=0; j<grid.cellsInfo[i].length; j++ ){
  50.                                         if( grid.cellsInfo[i][j] != null ){
  51.                                                 var pos:Array = cellToPos( i,j );
  52.                                                 grid.cellsInfo[i][j].x = pos[0];
  53.                                                 grid.cellsInfo[i][j].y = pos[1];
  54.                                         }
  55.                                 }
  56.                         }
  57.                 }
  58.                 
  59.                 /**
  60.                  * 坐标点转换为行列号 
  61.                  * @return 行列号数组[row, col]
  62.                  * 
  63.                  */                
  64.                 public function posToCell( x:Number, y:Number ):Array{
  65.                         return [];
  66.                 }
  67.                 
  68.                 /**
  69.                  * 行列号转换为坐标点 
  70.                  * @return 坐标点数组[x, y]
  71.                  * 
  72.                  */                
  73.                 public function cellToPos( row:int, col:int ):Array{
  74.                         return [];
  75.                 }
复制代码
  1. /**
  2. * 设置物品提示 
  3. * @param vo 物品VO:ItemVo
  4. * @return   物品提示字符串,用于TextField的htmlText属性
  5. */                
  6. protected function setToolTip( vo:ItemVO ):String{
  7.         var result:String = "";
  8.         result += toHTMLText( vo.name, 13 ) + "<br>";
  9.         result += toHTMLText( vo.type, 11, "#aa0000" );
  10.         return result;
  11. }
  12.                 
  13. /**
  14. * 将普通字符串转换为带有HTML标签的带样式的字符串 
  15. * @param text                欲转换字符串
  16. * @param fontSize  字符大小
  17. * @param color                字符颜色
  18. * @return                         带HTML标签字符串
  19. */                
  20. protected function toHTMLText(text:String, fontSize:int = 12, color:String = "#000000" ):String{
  21.         var str:String = "<font size='" + fontSize + "' color='" + color + "'>" + text + "</font>";
  22.         return str;
  23. }
  24.                 
  25. /**
  26. * 显示物品提示框 
  27. * @param vo        欲显示提示框的物品VO:ItemVO
  28. */                
  29. protected function showToolTip( vo:ItemVO ):void{
  30.         GameToolTip.getInstance().x = stage.mouseX + 9;
  31.         GameToolTip.getInstance().y = stage.mouseY;
  32.         GameToolTip.getInstance().text = setToolTip( vo );//设置提示框中显示信息格式
  33.         GameToolTip.getInstance().visible = true;
  34.         addEventListener(Event.ENTER_FRAME, mouseFollow);
  35. }
  36.                 
  37. /** 
  38. * 隐藏物品提示
  39. */                
  40. protected function hideToolTip():void{
  41.         removeEventListener(Event.ENTER_FRAME, mouseFollow);
  42.         GameToolTip.getInstance().visible = false;
  43. }
  44.                 
  45. private function mouseFollow(event:Event):void{
  46.         GameToolTip.getInstance().x = stage.mouseX + 9;
  47.         GameToolTip.getInstance().y = stage.mouseY;
  48. }
  49. }

复制代码这个类中在Board类的基础上额外提供了一些方法供我们对物品进行一系列的操作,下面详解一下这些方法:
startItemDrag:此方法中对数据模型GameModel中的当前拖拽对象currentDragItem进行了拖动,我给startDrag的第一个参数设为true,这样就会让被拖拽物体的注册点对准鼠标点,当然,你也可以设为flase。另外,为了让物体在被拖拽过程中不会出现物品提示或者可再次被点击,我们需要把它的mouseEnable设为flase,这样它就不会响应鼠标事件了。最后,我们得把数据模型中的isDraging标记设为true。
stopItemDrag:此方法基本上做的就是startItemDrag的逆过程,不过最后需要调用一下refreshView方法来刷新物品位置。
refreshView:遍历二维数组中每一个格子,若格子中存在物品,则将该对象坐标设置为经过cellToPos方法运算后得到的坐标值,cellToPos方法是根据格子位置(某行某列)根据一定规则计算出实际位置(x,y坐标)的。
posToCell:将物体实际位置经过某种规则转换为格子位置。这里没有任何逻辑,只留一个空壳,留给ItemBoard的后代们去重载之,因为不同的道具面板它们的物品摆放规则是不一样的。
cellToPos:你懂的。
showToolTip:显示物品提示框,会把传入的ItemVO作为参数调用setToolTip方法得到一串经过处理的物品提示字符串并通过设置提示框单例的显示位置与显示信息。更多有关提示框的细节问题在上一案例中有说明过哦。
      像ItemView与ItemVO的关系一样,ItemBoard与Grid也是配套的关系,前者负责显示上的一些操作,如坐标更换,visibel设置等等,后者就负责物品位置的数据管理,因此在ItemBoard类中我们声明了一个public的Grid实例供外部调用,每次当位置被用户的操作所改变时就需要靠此实例来进行一些数据运算来得到正确的位置信息。
      有了物品面板之后,包裹面板便可以在其基础上构建出来,我们刚才说到,在物品面板里存在cellToPos与posToCell两个方法是需要其后代根据格子分布规则来重载之的,在包裹面板中我们制定的格子分布规则是,一行一行从左往右均匀分布,见图:
<ignore_js_op>1.jpg 
     这个图表现了我们包裹面板的分布规则,物品将会从左往右分布,一行布满后从第二行开始从左往右分布,以此类推,每格位置之间横距离为一个格子的宽度blockW,纵距离为一个格子的高度blockH。OK,理解了的话看代码应该没啥问题。
SimpleBag.as:

  1. /**
  2.          *  简单包裹类,基于ItemBoard类,带有整齐的物品格子
  3.          * @author S_eVent
  4.          * 
  5.          */        
  6. public class SimpleBag extends ItemsBoard
  7.         {
  8.                 public static const BAG_FULL_EVENT:String = "bagFull";
  9.                 
  10.                 private var _blockW:Number;
  11.                 private var _blockH:Number;
  12.                 private var gameModel:GameModel = GameModel.getInstance();
  13.                 
  14.                 /**
  15.                  * 
  16.                  * @param width                                面板宽
  17.                  * @param height                        面板高
  18.                  * @param rowNum                        物品格行数
  19.                  * @param colNum                        物品格列数
  20.                  * @param backgroundColor        面板背景颜色
  21.                  * @param backgroundAlpha        面板背景透明度
  22.                  * @param title                                面板标题文字,若为空字符串,则不会创建标题栏
  23.                  * @param dragable                        是否允许拖动,若为true,则可通过点击标题栏进行面板拖动
  24.                  * 
  25.                  */        
  26.                 public function SimpleBag( Number, height:Number, rowNum:int = 6, colNum:int = 6, 
  27.                                                                    backgroundColor:uint=0x000000, backgroundAlpha:Number=1,
  28.                                                                    title:String = "", dragable:Boolean = false)
  29.                 {
  30.                         super( width, height, backgroundColor, backgroundAlpha, title, dragable );
  31.                         generateGridInfo( rowNum, colNum );
  32.                 }
  33.                 /**
  34.                  * 根据设置的行列数参数生成表格 
  35.                  * @param rowNum
  36.                  * @param colNum
  37.                  * 
  38.                  */                
  39.                 public function generateGridInfo( rowNum:int, colNum:int ):void{
  40.                         grid = new Grid( rowNum, colNum );
  41.                         _blockH = boardHeight / rowNum;
  42.                         _blockW = boardWidth / colNum;
  43.                         draw();
  44.                 }
  45.                 
  46.                 /**
  47.                  * 新增道具 
  48.                  * @param item 物品VO
  49.                  * 
  50.                  */                
  51.                 public function addItem( item:ItemVO ):void{
  52.                         var view:ItemView = new ItemView();
  53.                         var index:Array = grid.addItem( view );
  54.                         if(  index.length < 1 ){
  55.                                 dispatchEvent( new Event(BAG_FULL_EVENT) );
  56.                         }else{
  57.                                 view.itemVO = item;
  58.                                 var pos:Array = cellToPos( index[0], index[1] );
  59.                                 view.x = pos[0];
  60.                                 view.y = pos[1];
  61.                                 view.width = blockW - 4;
  62.                                 view.height = blockH - 4;
  63.                                 addChild( view );
  64.                                 gameModel.itemList.push( view );
  65.                                 view.addEventListener( MouseEvent.ROLL_OVER ,function(event:MouseEvent):void{
  66.                                         showToolTip( (event.currentTarget as ItemView).itemVO );
  67.                                 } );
  68.                                 view.addEventListener( MouseEvent.ROLL_OUT ,function(event:MouseEvent):void{
  69.                                         hideToolTip();
  70.                                 } );
  71.                         }
  72.                 }
  73.                 
  74.                 /** 
  75.                  * 绘制外观方法,在原先只绘制背景的基础上,提供绘制格子功能
  76.                  */                
  77.                 private function draw():void{
  78.                         drawBG();
  79.                         this.graphics.lineStyle(1, 0x999999, 0.7);
  80.                         for( var i:int=0; i<grid.rowNum; i++ ){
  81.                                 this.graphics.moveTo( 0, i * _blockH );
  82.                                 this.graphics.lineTo( boardWidth - 1, i * _blockH );
  83.                         }
  84.                         for( var j:int=0; j<grid.colNum; j++ ){
  85.                                 this.graphics.moveTo( j * _blockW, 0 );
  86.                                 this.graphics.lineTo( j * _blockW, boardHeight - 1 );
  87.                         }
  88.                 }
  89.                 
  90.                 override public function cellToPos( row:int, col:int ):Array{
  91.                         var pos:Array = new Array(2);
  92.                         pos[0] = col *  blockW + 2;
  93.                         pos[1] = row *  blockH + 2;
  94.                         return pos;
  95.                 }
  96.                 
  97.                 override public function posToCell( x:Number, y:Number ):Array{
  98.                         var cell:Array = new Array(2);
  99.                         cell[0] = int( y / blockH );
  100.                         cell[1] = int( x / blockW );
  101.                         return cell;
  102.                 }
  103. //----------------------我是分割线,你可以叫我老分-----------------------------//
  104. //----------------------下面开始是读写器-----------------------------//        
  105.                 public function get blockH():Number
  106.                 {
  107.                         return _blockH;
  108.                 }
  109.                 
  110.                 public function get blockW():Number
  111.                 {
  112.                         return _blockW;
  113.                 }
  114.         }
复制代码

在这个类中,我们重载的cellToPos和posToCell方法是按照我们的布局规则来写的,相信不难看懂。在构造函数里我们除了接收它老爹的那几个参数外还需要多接收rowNum和colNum两个参数,拿到这两个参数后会把它们转交给generateGridInfo方法来生成表格数据。generateGridInfo是一个共有方法,在外部也可以随时通过调用之来更改表格的尺寸。在这个方法中,首先要做的自然是给继承自ItemBoard的gird属性实例化一下,之后按照表格尺寸计算出每格大小,最后调用draw方法来绘图。draw方法中必须先调用一下drawBG()重绘一下背景才能接着绘制表格线条,因为在每次重绘时都会调用到graphics.clear方法,这样就会把表格及背景一起擦去,所以每次重绘都不仅仅只重绘表格,还得连背景一起重绘一下。
      接着看addItem方法,这个方法根据传入的ItemVO参数创建一个ItemView并把其作为参数传递给grid的addItem方法,若添加成功则addItem方法会返回一个位置数组,这个数组的结构是[行,列],根据得到的这个位置再通过cellToPos方法把行列值转换为实际的x, y坐标后赋值给ItemView的x,y坐标然后添加到显示列表中去。若调用grid.addItem方法时,因为数组容量已满则会返回一个空数组,若发现返回结果是空数组就发布一个背包已满(BAG_FULL)的事件让外面知道。

 

警告框的制作
       用户对用户的操作总不可能仅限于调整物品位置吧?还需要对物品进行丢弃操作,而且物品栏装满时还得告诉用户物品栏已满,你丫的别再获取新物品了~!此时我们就需要弹出一个警告框来告知用户一些消息。
       首先,警告框必须置于显示列表最顶层,它不能被其他物体所遮挡;其次,它会被用在项目中任何一个地方,当某个窗口需要它的时候能够很方便地拿到它且不需要知道它的父类是谁。对于第二个问题,想必此时很多boys都会会心一笑,用静态的变量或方法嘛~55555……当听到这句话的时候我泪奔了,“爸,你怎么哭了?”“不,爸没哭,爸这是高兴……”总算没有辜负我一次又一次地重申静态对象的概念。行吧,看看我是怎么写警告框类的。
LifeAlert.as

  1. /**
  2.          * 简单警告框,包含警告文本以及确定、取消按钮 
  3.          * @author S_eVent
  4.          * 
  5.          */        
  6.         public class LifeAlert extends Sprite
  7.         {
  8.                 private static var showWindow:Sprite;
  9.                 private static var windowList:Array = new Array();
  10.                         
  11.                 public function LifeAlert(  )
  12.                 {}
  13.                 /**
  14.                  * 显示警告框
  15.                  * @param text                        警告文本
  16.                  * @param flag                        需要的按钮,参数间用或( | )操作符隔开可传入2个或多个参数。可选参数包括:<p>OK:确定按钮;<p>CANCEL:取消按钮
  17.                  * @param parent                 警告框弹出的父容器
  18.                  * @param closeHandler        点击按钮关闭后的事件处理函数
  19.                  */        
  20.                 public static function show( text:String, flag:uint, parent:DisplayObjectContainer, closeHandler:Function=null ):void{
  21.                         showWindow = checkIfExist(text, flag);
  22.                         if( showWindow == null ){
  23.                                 var window:AlertWindow = new AlertWindow( text, flag );
  24.                                 parent.addChild( window );
  25.                                 window.x = parent.width / 2; 
  26.                                 window.y = parent.height / 2; 
  27.                                 windowList.push( {text:text, flag:flag, window:window} );
  28.                                 if( closeHandler != null ){
  29.                                         window.addEventListener(AlertCloseEvent.CLOSE_EVENT, closeHandler);
  30.                                 }
  31.                         }else{
  32.                                 parent.setChildIndex( showWindow, parent.numChildren - 1 );
  33.                                 showWindow.visible = true;
  34.                         }
  35.                 }
  36.                 
  37.                 private static function checkIfExist( text:String, flag:uint ):AlertWindow{
  38.                         for each( var obj:Object in windowList ){
  39.                                 if( obj.text == text && obj.flag == flag )return obj.window;
  40.                         }
  41.                         return null;
  42.                 }
  43.         }
复制代码

代码虽少但是却可以让你看上好一段时间,有没有?!让我来解释一下吧。这个类是个方法类,所谓方法类就是只提供了一系列的静态方法或对象,你实例化它没有任何意义。我们只需要通过类名 + 点 + 静态方法 名来使用方法类提供的一系列public static方法即可。这个类提供了一个叫做show的共有方法供外部使用,它可以根据你传入的两个参数(具体看代码ASDoc解释,所谓ASDoc就是以斜杠加星号做起始标签,星号加斜杠作结尾标签的文本,写在函数或者变量上面的话就可以作为一个函数或者变量的说明文字,在FB,FD或者一些高级IDE中,当鼠标移至一个函数或者变量上时若它具备ASDoc信息就会在弹出框显示出来)创建一个AlertWindow对象并添加至需要添加到的父显示对象上去。在这个函数中还采取了一种避免重复创建的机制,我们会根据用户传入的text和flag这两个关键参数来判断类内部用来保存已创建的AlertWindow对象的静态数组windowList中是否存在用户需求的警告框,若有,就现成拿出来用即可。为了让静态函数show中能够拿到函数外部声明的变量,我们把它要用到的变量showWindow以及windowList,函数checkIfExist都以static关键字标注,声明成静态对象。
     接下来看看AlertWindow类,它绘制了一个警告框:
AlertWindow.as

  1. package com.life.lifeTools
  2. {
  3.         import flash.display.Sprite;
  4.         import flash.events.MouseEvent;
  5.         import flash.text.TextField;
  6.         
  7.         [Event(name="closeEvent", type="com.life.lifeTools.AlertCloseEvent")]
  8.         
  9.         public class AlertWindow extends Sprite
  10.         {
  11.                 public static const OK:uint = 1;
  12.                 public static const CANCEL:uint = 2;
  13.                 
  14.                 public function AlertWindow( text:String, flag:uint=1 )
  15.                 {
  16.                         
  17.                         this.graphics.beginFill( 0x999999 );
  18.                         this.graphics.drawRect( 0, 0, 200, 100 );
  19.                         this.graphics.endFill();
  20.                         
  21.                         var txt:TextField = new TextField();
  22.                         txt.htmlText = text;
  23.                         txt.width = txt.textWidth + 5;
  24.                         txt.height = txt.textHeight + 5;
  25.                         txt.selectable = false;
  26.                         this.addChild( txt );
  27.                         txt.x = 100 - txt.width / 2;
  28.                         txt.y = 10;
  29.                         
  30.                         var btnAry:Array = new Array();
  31.                         if( flag & OK ){
  32.                                 btnAry.push( creatBtn( "确定", OK ) );
  33.                         }
  34.                         if( flag &  CANCEL ){
  35.                                 btnAry.push( creatBtn( "取消", CANCEL ) );
  36.                         }
  37.                         var len:int = btnAry.length;
  38.                         var startX:Number; 
  39.                         if( len == 1 ){
  40.                                 startX = 65;
  41.                         }else if( len == 2 ){
  42.                                 startX = 25;
  43.                         }
  44.                         for( var i:int=0; i<len; i++ ){
  45.                                 var btn:Sprite = btnAry[i] as Sprite;
  46.                                 btn.x = startX + i * (70 + 10);
  47.                                 btn.y = 40;
  48.                                 this.addChild( btnAry[i] );
  49.                         }
  50.                 }
  51.                 
  52.                 private function creatBtn( label:String, detail:uint, color:uint = 0x999999 ):CustomButton{
  53.                         var txt:TextField = new TextField();
  54.                         txt.text = label;
  55.                         txt.width = txt.textWidth + 5;
  56.                         txt.height = txt.textHeight + 5;
  57.                         txt.selectable = false;
  58.                         var btn:CustomButton = new CustomButton();
  59.                         btn.graphics.lineStyle(1);
  60.                         btn.graphics.beginFill( color );
  61.                         btn.graphics.drawRect( 0, 0, 70, 30 );
  62.                         btn.addChild( txt );
  63.                         txt.x = 20;
  64.                         txt.y = 10;
  65.                         btn.detail = detail;
  66.                         btn.addEventListener(MouseEvent.CLICK, onClick);
  67.                         return btn;
  68.                 }
  69.                 
  70.                 private function onClick(event:MouseEvent):void{
  71.                         var detail:uint = (event.currentTarget as CustomButton).detail;
  72.                         dispatchEvent( new AlertCloseEvent("closeEvent", detail) );
  73.                         this.visible = false;
  74.                 }
  75.         }
  76. }
复制代码

上面的代码中难点有两处:
难点1.

  1. var btnAry:Array = new Array();
  2.                         if( flag & OK ){
  3.                                 btnAry.push( creatBtn( "确定", OK ) );
  4.                         }
  5.                         if( flag &  CANCEL ){
  6.                                 btnAry.push( creatBtn( "取消", CANCEL ) );
  7.                         }
复制代码

这是一种模仿FLEX中Alert组件的做法,这涉及到二进制的运算,若对二进制运算不熟悉的童鞋可以google一下看看二进制运算规则,若用户用“Alert.OK | Alert.CANCEL ”作为参数传给flag,则flag的值等于 1 | 2 等于 3,用二进制表示就是11,此时使用flag & OK 运算结果为1(十进制,二进制为01),会调用creatBtn方法创建一个文本为“确定”, detail为Alert.OK的按钮后添加到btnAry数组中去,等待一会被添加至显示列表;同理,使用flag &  CANCEL运算结果为2(十进制,二进制为10),因为结果非0,所以条件也能成立,将会创建一个文本为“取消”, detail为Alert.CANCEL的按钮后添加到btnAry数组中去。
难点2.
    在创建按钮函数中我们会创建一个CustomButton 类,此类相当简单,仅在Sprite类的基础上携带了一个detail属性用以标识按钮的类别是确定键还是取消键。

  1. public class CustomButton extends Sprite
  2.         {
  3.                 public var detail:uint;
  4.                 public function CustomButton()
  5.                 {
  6.                 }
  7.         }
复制代码

为什么这么做呢?因为我们一个警告框弹出后系统需根据用户点击的按键来确定执行什么动作,所以我们必须记录下每个按钮的标示符才行。为每个创建的CustomButton 监听鼠标点击事件,在事件处理函数中我们把当前点击的按钮标示符detail通过事件发送出去让外界监听到,为此我们需要自定义一个事件,其肩负着携带被点击按钮detail属性的任务:

  1. public class AlertCloseEvent extends Event
  2.         {
  3.                 public static const CLOSE_EVENT:String = "closeEvent";
  4.                 public var detail:uint;
  5.                 
  6.                 public function AlertCloseEvent(type:String, detail:uint, bubbles:Boolean=false, cancelable:Boolean=false)
  7.                 {
  8.                         super(type, bubbles, cancelable);
  9.                         this.detail = detail;
  10.                 }
  11.         }
复制代码

当按钮被点击后会创建一个AlertCloseEvent 事件,并且会调用this.visible把当前AlertWindow关闭。此外,我们在类的最上面写了一个元标签[Event],这能够让我们在给此类(AlertWindow)添加事件侦听时能够在代码助手中看到代码提示,更多关于元标签的使用还请感兴趣的boy自己去网上搜索下吧:
<ignore_js_op>2.jpg 
    此时再回头看看LifeAlert.show函数中的此句代码:

  1. if( closeHandler != null ){
  2.                                         window.addEventListener(AlertCloseEvent.CLOSE_EVENT, closeHandler);
  3.                                 }
复制代码

它监听了一个AlertWindow中发出的CLOSE_EVENT事件,它将会在警告框按钮被单击后抛出,此时LifeAlert类会调用用户传入的关闭处理函数closeHandler来对事件进行事件处理,当然,当closeHandler参数没有传时就不需要多此一举地监听了。
    上面说的可能有些抽象有点难以理解,下面要讲到的将会把此工具类投入实际使用中去,随着使用方法的掌握你也会慢慢理解上面的代码中包含的思想了。

 

接下来就可以直接写主应用程序了?
       如果你问我这个问题,那请你先想一想你打算给主应用文件(也可以叫做文档类)加上哪些功能,首先,读取xml配置表获取物品数据是肯定得它来做的;其次,它要创建一系列显示对象,包括背景图片、背包面板、用于全局的物品提示框(ToolTip)组件,以后若是再加上个什么人物装备栏啊商店面板什么的也都得加到主应用文件里去,而且更重要的是,你想拖动物品就必须对this,即主应用文件添加鼠标侦听。这样做的话,即使你没有用到任何物品面板,所有物品都没有打开(visible = false)时你每次点击鼠标也会被监听到,这样就会造成大量的CPU开销,我们完全不值得这样做(事实上,在我重构代码之前我也是这样做的 )。
       那么我们就想到启用一个管理员来管理所有的物品面板,只有打开它我们才会看见物品面板,才会进行物品的一系列操作,因此我们只要对它进行鼠标侦听就行了,不需要操作到物品时把此管理员的visible设为false,就不会造成不必要的额外开销,这样就减轻了主应用程序的负担,在主应用里只要做初始化数据的事情即可。
      我们把这个管理员取名为ItemManager,看看它的代码是怎样的:
ItemManager.as:

  1. public class ItemManager extends Sprite
  2.         {
  3.                 private var bag:SimpleBag;
  4.                 private var bgBoard:Board;
  5.                 
  6.                 private var gameModel:GameModel = GameModel.getInstance();
  7.                                 
  8.                 public function ItemManager( )
  9.                 {
  10.                         initView();
  11.                 }                                
  12.                 
  13.                 /**
  14.                  * 将某物品添加到包裹中 
  15.                  * @param vo        欲添加物品VO
  16.                  * 
  17.                  */                
  18.                 public function addToBag( vo:ItemVO ):void{
  19.                         bag.addItem( vo );
  20.                 }
  21.                 
  22.                 private function initView():void{
  23.                         bgBoard = new Board( 400, 290, 0x111111, 0.9, "<font color='#ffffff' size='14'>随身包裹</font>", true );
  24.                         bgBoard.y = 60;
  25.                         addChild( bgBoard );
  26.                         bag = new SimpleBag( 360, 250, 6, 8, 0x444444, 0.7 );
  27.                         bgBoard.addElement( bag, 20, 20 );
  28.                         bag.addEventListener(SimpleBag.BAG_FULL_EVENT, onBagFull)
  29.                         addChild( GameToolTip.getInstance("", 100, 0, 0, 0xffffff, 0.8) );//因为是第一次调用,需要输入参数初始化一下
  30.                         
  31.                         this.addEventListener( MouseEvent.CLICK, onBGClicked );
  32.                 }
  33. //----------------------我是分割线,你可以叫我老分-----------------------------//
  34. //----------------------下面开始是事件侦听器-----------------------------//        
  35.                 private function onBGClicked(event:MouseEvent):void{
  36.                         trace(event.target);
  37.                         
  38.                         if( gameModel.isDraging ){
  39.                                 if( event.target is ItemView || event.target is SimpleBag ){
  40.                                         var localPos:Point = bag.globalToLocal( new Point(mouseX, mouseY) );//把相对于this,即ItemManager左上角的鼠标坐标转换为相对于bag左上角的鼠标坐标
  41.                                         var cell:Array = bag.posToCell( localPos.x, localPos.y );
  42.                                         bag.grid.setCell( cell[0], cell[1], gameModel.currentDragItem );
  43.                                         bag.stopItemDrag();
  44.                                         bag.refreshView();
  45.                                 }
  46.                                 else{
  47.                                         bag.stopItemDrag();
  48.                                         LifeAlert.show( "确定丢弃该物品吗", AlertWindow.OK | AlertWindow.CANCEL, stage, onClose );
  49.                                 }
  50.                         }else if( event.target is ItemView ){
  51.                                 gameModel.currentDragItem = event.target as ItemView;
  52.                                 bag.startItemDrag();
  53.                         }
  54.                 }
  55.                 
  56.                 private function onClose(event:AlertCloseEvent):void{
  57.                         if( event.detail == AlertWindow.OK ){
  58.                                 var dropItem:ItemView = GameModel.getInstance().currentDragItem;
  59.                                 dropItem.stopDrag();
  60.                                 bag.grid.removeItem( dropItem );
  61.                                 dropItem.destory();
  62.                         }
  63.                 }
  64.                 private function onBagFull(event:Event):void{
  65.                         LifeAlert.show("物品栏已满", AlertWindow.OK, stage);
  66.                 }
  67.         }
复制代码

我们在这个类中创建了一个标题为随身包裹的面板,它是可拖动的,它内部还添加了一个6行8列的背包面板,不要忘了给背包面板侦听背包装不下时候会抛出的事件BAG_FULL的监听,最后对整个ItemManager监听鼠标点击事件。在BAG_FULL的事件监听函数onBagFull中需要做的仅是弹出一个警告框告知用户说物品栏已满即可,此警告框的父显示对象parent我们传入的参数是stage,就是把此警告框addChild到stage中,由于在警告框弹出前已经创建好了其他全部显示对象,所以当它被addChild到stage时必须在显示列表顶层,其他显示对象无法遮挡得了它,由于我们没有传入closeHandler参数,所以点击确定按钮后什么都不会发生,只会把警告框给隐藏掉。接下来要重点看看的是onBGClicked函数,你可以先写一句trace(event.target)来看看鼠标点击不同窗体时的输出值,输出值应该如下图所示:
<ignore_js_op>3.jpg 
    点击格子面板时输出的对象类型是SimpleBoard,点击格子以外黑色区域会输出的对象类型是Board,这两个都没有错,因为在初始化的时候我们的确是把包裹面板放在一个基础面板内。不过如果我们想扔掉一个道具就得点击面板外的区域,不过点击后发现无任何输出,这是为啥咧?稍候再加以说明,先继续讲onBGClicked函数中剩下的代码。判断GameModel中的拖动标记isDraging是否为true, 如果为true表示用户正在拖动物品,此时用户点下鼠标将会发生的事情有2种,一是把物体换位置,二是丢弃物品。那么我们就需要判断一下点击的对象是神马了,当用户点击目标为ItemView(互换两个物品)或SimpleBag(把物品放到另一个空格中)时就执行位置互换操作,需要注意的是,在传入参数前需要把转换一下坐标,我们使用mouseX, mouseY拿到的坐标值是相对于本容器(this)内的坐标,而SimpleBag中用以将实际坐标值转换为行列值的计算函数中需求的坐标参数是相对于它自己(Simple)的,所以使用实例名 + 点 + globalToLocal方法可以完成坐标转换的工作。如果你点的目标不是ItemView或SimpleBag,那么系统就认为你想丢弃物品,此时弹出一个警告框,此警告框我们需要它显示确定和取消两个按钮,并问你是否丢弃,给它再监听一个用户点击按钮后的事件处理函数onClose,在此函数中判断一下你点击的是否是确定键,若是,则执行丢弃物品的操作。
     解释了一大通,讲得我口干舌燥的,容我喝点口水缓解一下先……嗯……若想让点到非物品栏面板内部时不会弹出是否丢弃的提示框,可以加上一句判断

  1. if( gameModel.isDraging ){
  2.                                 if( event.target is ItemView || event.target is SimpleBag ){
  3.                                         ……
  4.                                 }
  5.                                 else if( event.target is Board ){
  6.                                       bag.stopItemDrag();
  7.                                       bag.refreshView();
  8.                                 }
  9.                                 else{
  10.                                         ……
  11.                                 }
  12.                         }else if( event.target is ItemView ){
  13.                                 ……
  14.                         }
复制代码

终于能跟主应用文件见面了
     好了,全部功能性的东西都已经讲解完毕,最后请出我们的主应用文件隆重登场~除了把我们已写好的东西加进去之外,我们还需要初始化一些数据,这些数据均记录在一个xml中:
itemsConfig.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <root>
  3.         <item id="Boots" name="靴子" type="鞋" />
  4.         <item id="Helmet" name="铁盔" type="头盔" />
  5.         <item id="Sword" name="霜冻之剑" type="剑" />
  6. </root>
复制代码

Main.as:

  1. [SWF(width="800",height="700")]
  2. public class Main extends Sprite
  3. {
  4.         private var background:Sprite = new Sprite();
  5.         private var bm:Bitmap = new Bitmap();
  6.         private var itemManager:ItemManager;
  7.         private var gameModel:GameModel = GameModel.getInstance();
  8.                 
  9.         public function Main()
  10.         {
  11.                 super();
  12.                 initData();
  13.                 initBackground();
  14.                 initViews();
  15.                         
  16.                 this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  17.         }
  18.                 
  19.         private function initData():void{
  20.                 var urlLoader:URLLoader = new URLLoader();
  21.                 urlLoader.addEventListener(Event.COMPLETE, onXMLComplete);
  22.                 urlLoader.load( new URLRequest("data/itemsConfig.xml") );
  23.         }
  24.                 
  25.         private function initBackground():void{
  26.                 background.addChild( bm );
  27.                 this.addChild( background );
  28.                 var loader:Loader = new Loader();
  29.                 loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBGComplete);
  30.                 loader.load( new URLRequest("assets/WOWBg.jpg") );
  31.         }
  32.                 
  33.         private function initViews():void{
  34.                 itemManager = new ItemManager(800, 700);
  35.                 addChild( itemManager );
  36.                 itemManager.visible = false;
  37.         }
  38.                 
  39. //----------------------我是分割线,你可以叫我老分-----------------------------//
  40. //----------------------下面开始是事件侦听器-----------------------------//
  41.                 
  42.         private function onAddedToStage(event:Event):void{
  43.                 stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
  44.         }
  45.                 
  46.         private function onBGComplete(event:Event):void{
  47.                 bm.bitmapData = ( (event.currentTarget as LoaderInfo).content as Bitmap ).bitmapData;
  48.                 bm.width = 800;
  49.                 bm.height = 700;
  50.         }
  51.                 
  52.         private function onXMLComplete(event:Event):void{
  53.                 var xml:XML = new XML( (event.currentTarget as URLLoader).data );
  54.                 for each( var x:XML in xml.item ){
  55.                         var vo:ItemVO = new ItemVO();
  56.                         vo.id = x.@id;
  57.                         vo.name = x.@name;
  58.                         vo.type = x.@type;
  59.                         gameModel.voList.push( vo );
  60.                         itemManager.addToBag( vo );
  61.                 }
  62.         }
复制代码
  1. private function onKeyDown(event:KeyboardEvent):void{
  2.                 switch( event.keyCode ){
  3.                         case Keyboard.ENTER:
  4.                                 var vo:ItemVO = new ItemVO();
  5.                                 vo.id = "Gauntlets";
  6.                                 vo.name = "力量手套";
  7.                                 vo.type = "glove";
  8.                                 gameModel.voList.push( vo );
  9.                                 itemManager.addToBag( vo );
  10.                                 break;
  11.                         case Keyboard.SPACE:
  12.                                 itemManager.visible = !itemManager.visible;
  13.                                 break;
  14.                 }
  15.         }
  16. }

复制代码这个类中没有特别难理解的东西,要做的事情就是三样:一,初始化数据,读取xml文件,把数据模型中需要用到的数据都初始化,每得到一个新的itemVO都往包裹里面添加一件物品,同时还要保存至数据模型中的volist里面去。二,初始化背景图片,为了美观且和ItemManager区别开来,我读入一个背景图片作为一个底层。三,初始化游戏中的视图,在这里我们只加了一个ItemManager,在一个完整的游戏里面应该还有游戏主角,场景什么的东东,不过在这里我们主要研究的是物品,所以就不喧宾夺主了哈。当ItemManager被添加到舞台上时我把它visible设为false,因为我觉得游戏一开始时候背包总不太可能是开着的。那怎样才能打开背包呢?我监听了一个键盘事件,是对stage侦听的哦,对别的对象侦听我不保证能顺利监听得到哦,因为stage对象不一定一开始就存在,为了防止stage为空造成flash player报错,我把对stage的访问统统放在addedToStage事件监听函数中去。我们看到,在onKeyDown方法中我设置了两个操作:1,按下ENTER回车键会获得一件叫做“力量手套”的道具;2,按下SPACE空格键会隐藏/显示物品栏。有这两个交互功能就足以测试我们的应用了。试着运行看看吧。
    当按下空格键后正常地弹出了物品栏,里面也放着我们xml里设置的两个物品,用鼠标对物品进行换位也正常,看似一切OK,不过当我们点击一个物品至物品栏外面渴望丢弃时却发现什么也没有发生。唔……这个问题刚才在ItemManager里面的鼠标监听函数里已经遇到过,当初我们在鼠标监听函数中写了一句trace(event.target),不过当点到物品栏外面时却什么也没有输出,这是为神马呢?
    回头看看ItemManager的代码,我们知道,ItemManager是继承了Sprite的,而Sprite的大小又是根据其子显示对象来动态调整的。在ItemManager中,它的子组件只有一个“随身包裹栏”面板,因此ItemManager的大小就只有此面板那么大了,所以当你鼠标点击面板外,就是点到了ItemManager外面,而我们是对ItemManager监听的鼠标点击事件,你没点在他上面自然监听不到喽。那么怎样能使用户能在ItemManager中实现丢弃物品的操作呢?答案是扩大ItemManager的面积但又保证可见区域只有面板那么大。怎样才能做到这件看似不可能的事情呢?想让我告诉你?求我呀,求我呀……咳咳,再这样下去要被群殴了……看看我改造过的ItemManager构造函数:

  1. public function ItemManager( Number=0, height:Number=0 )
  2. {
  3.         if( width != 0 && height !=0 ){
  4.                 this.graphics.beginFill(0, 0);
  5.                 this.graphics.drawRect(0, 0, width, height);
  6.                 this.graphics.endFill();        
  7.                  }
  8.         initView();
  9. }
复制代码

很多boys看到后就会心一笑,很多人则不然,让我来说明一下其中的奥秘吧。我们都用过graphics.beginFill方法加graphics.drawRect方法为一个Sprite绘制过矩形吧?你是不是发现绘制的矩形有多大Sprite的大小就会调整至多大?答案是肯定的,absolutely的,因此,为了扩大一个Sprite的大小,首选上面两个graphics方法合用的解决方案,若要让背景透明,设置beginFill的第二个参数:透明度(alpha)为0即可,至于颜色么随你喜欢,反正都是看不见的。改造完毕构造函数后去主应用程序中在ItemManager的初始化语句中传入参数,我们把ItemManager长宽设置为和舞台一样大小,即800 * 700:

  1. private function initViews():void{
  2.         itemManager = new ItemManager(800,700);
  3.         addChild( itemManager );
  4.         itemManager.visible = false;        
  5. }
复制代码

此时再点击调试按钮对应用进行调试后发现即使点击了物品栏外面也会正常输出[Object ItemManager]的字样显示你点击到的目标为一个ItemManager实例。此时也能够正常弹出“是否丢弃物品”的警告框了。
<ignore_js_op>3.jpg 
       再次按下空格键,不停点击鼠标,也会发现不再有任何去死(trace)语句输出了,这为我们节省了为一停不停地监听鼠标事件所造成的不必要的开销。
    好了,这个案例到此就告一段落了,在这个案例中我为大家介绍的不只是一个案例的制作过程,更是一种面向对象的编程思想,这是作为一名程序员所必需的思想,它能为我们的开发带来更佳的可读性及可拓展性。不过身为一个FLEX开发人员来说,用纯AS3语句写东西比直接使用一些现成的组件要费点精力,不过我短期内不打算给各位介绍Flex的东西,因为Flex是更高级别的一种flash开发SDK,对于跟着我教程一路学过来的人来说暂时还无法接受与理解Flex的一些用法和特性。这次的案例算是到案例15为止最复杂的一个了,我花了大量篇幅尽可能地解释其中的一些难点,若是列位还有什么疑问可以回帖或者留言给我,在这里奉上全部源码,虽然很不愿意收列位的银子,但是论坛中发附件默认是收一两银子的,这我无法改变呢 <ignore_js_op> src.rar (243.58 KB, 下载次数: 712) 
      我写的这个例子可拓展性很强,各位可以下载过去后拓展其功能,当然,如果我有空也会拓展一下功能并第一时间拿出来与列位共享的哈……

 

物品栏扩展——装备栏
当我们需要让物品在各窗口间穿梭时,上面所说的方法就不行了。将会存在下面几个问题:
1.其他窗口中物品排列不规则的话,cellToPos方法就不能满足我们的需求了,因为每个格子的坐标都没有什么规律可言。而且根据鼠标点击处的坐标算出当前点击的格子行列号此时也无法实现功能。
2.当某些格子对物品类型有约束,比如装备栏中每格只能摆放对应类型的装备物品,你不可以在衣服格子中摆放武器或者药水。此时,上面提到的方法中使用的基于位置的“虚拟格子”(因为不存在格子对象,我们每次点击的都只是物品栏对象而已,只不过是根据点击位置计算出当前所在“格子”在哪里)就不能满足需求。
     于是我就想出了采用“实体格”的办法来解决各窗口之间的物品拖拽问题。先看一下效果展示吧:
http://www.iamsevent.com/upload/ItemPackage.swf
或者
http://we.zuisg.com/wp-content/uploads/2011/01/Main.swf

先给出源码,稍候给出解释: <ignore_js_op> src.rar (254.52 KB, 下载次数: 975) 
      由于篇幅有限,而且做到这一步了,对于大多数兄弟来说,本案例难度也算是比较大,能坚持到做到这一步的人的实力应该也不需要详细讲解了,我现在就只讲一些大概的思路要点吧,我强烈建议你下载了源码放在IDE中查看,里面有代码注释且格式比较清晰。
    为了让每一个格子具备可设置背景、可匹配类型以及更方便排版,我们就不得不把每一个格子做成一个可接收鼠标事件的容器,那就非Sprite莫属了,其中可放一个背景图片的Bitmap,当物品放进去后直接AddChild一个ItemView即可。当然,在格子类中必须有一个type属性来记录格子类型,在物品拖放至格子中时要用到此属性来判断该物品类型是否与格子类别一致,若一致则可以放入此格。其中代码我就不贴了,篇幅有限,具体查看源代码中ItemCell.as类。
    有了ItemCell类后每个物品就有了自己的归属了,在ItemView类里面加一个属性存放它的所属格:

  1. public var cell:ItemCell;//物品所属格
复制代码

还记得之前提到的Grid类么?它是负责管理一个面板中全部的格子信息的,但是现在我们的每一个格子都成为了单独的一个ItemCell对象,其中不仅仅有物品信息,还有类别等等的属性,如此一来,原来的Grid类就有点心有余而力不足了,因此拓展Grid类为ItemGrid类,初始化的时候往里面的数组中放入的不再是简单的Object对象,而是ItemCell对象。对所有物品的排序功能也将由ItemGrid来完成,具体实现原理是借助了Array类的API:sort方法来完成的。
    由于所有格子的管理改用ItemGrid来完成了,所以需要把物品面板类ItemBoard中的gird类型改成ItemGrid。此外,在游戏中,每个面板都有属于自己的一些属性信息是和面板中存放的物品信息密切相关的,比如物品栏的“负重”属性是由其中存放物品的重量和多少来决定的;装备栏的“攻击力”,“防御力”等人物属性也是由装备的物品来决定的,因此我们还需要给ItemBoard添加一个新的public方法用来供外部调用以更新面板属性:

  1. public class ItemsBoard extends Board
  2. {
  3. public var grid:ItemGrid;
  4. ………………
  5. /**
  6. * 更新面板内属性 
  7. */                
  8. public function updateProperty():void{
  9.                 
  10. }
  11. …………
  12. }
复制代码

updateProperty方法是在ItemCell类中调用的,因为只有某一格的信息被设置过后才会更新当前格所在面板中的属性。看到ItemCell中相应源码:

  1. public class ItemCell extends Sprite
  2. {
  3. public var container:ItemsBoard;//格子所属面板
  4. public function set item( value:ItemView ):void{
  5. //如果设置item为null,检查物品栏中显示列表里是否存在物品视图,若存在则remove之
  6. if( value == null && _item != null ){
  7.         if( this.contains( _item ) ){
  8.                 removeChild( _item );
  9.         }
  10.         _item = null;
  11. }
  12. //若设置item非空,则检查物品栏中显示列表里是否存在物品视图,若不存在则add之
  13. else if( _item != value ){
  14.         _item = value;
  15.         _item.cell = this;
  16.         if( !this.contains( _item ) ){
  17.                 addChild( _item );
  18.                 _item.x = 1;
  19.                 _item.y = 1;
  20.         ……        
  21.         }
  22. }
  23. //更新物品栏所属面板的属性
  24. container.updateProperty();
  25. }
复制代码

装备面板的创建没有什么难点,你只需要知道你需要哪些格子,每个格子分别可以装备什么道具即可,另外,你还需要重载更新面板属性的方法:

  1. private function drawItemWindow():void{
  2. //初始化武器格
  3. var wCell:ItemCell = ……;                
  4. grid.cellsInfo[0][0] = wCell;
  5. addChild( wCell );
  6. //初始化铠甲格
  7. var aCell:ItemCell = ……;                        
  8. grid.cellsInfo[0][1] = aCell;
  9. addChild( aCell );
  10. ……
  11. //初始化手套格……
  12. }
  13. override public function updateProperty():void{
  14. attack = 0;
  15. defence = 0;
  16. //遍历所有物品,更新面板内各项属性值
  17. for( var i:int=0; i<grid.cellsInfo.length; i++ ){
  18.         for( var j:int=0; j<grid.cellsInfo[i].length; j++ ){
  19.                 var view:ItemView = grid.cellsInfo[i][j].item;
  20.                 if( view != null ){
  21.                         attack += view.itemVO.attack;
  22.                         defence += view.itemVO.defence;
  23.                 }
  24.         }
  25. }
  26. }
复制代码

你可以在背包栏里面也加上一个“负重”的属性,它的更新一样由重载的updateProperty方法来负责,这里就不赘述了。
     还有一处改变就是在点击一个物品后进入该物品的拖拽状态,此时跟随鼠标走的,没有必要等于当前拖拽的ItemView,我们只需要用一个绘制了拖拽物品外观的Bimap来跟随鼠标移动即可,我们称之为拖拽代理,这个类中存放了当前拖拽目标的引用:

  1. public class DragProxy extends Bitmap
  2. {
  3.         public var dataSource:ItemView; //拖动的对象
  4.                 
  5.         public function DragProxy(bitmapData:BitmapData=null, dataSource:ItemView=null, pixelSnapping:String="auto", smoothing:Boolean=false)
  6.         {
  7.                 if( bitmapData == null )bitmapData = new BitmapData( Config.CELL_SIZE, Config.CELL_SIZE, true, 0 );
  8.                 super(bitmapData, pixelSnapping, smoothing);
  9.                 this.dataSource = dataSource;
  10.         }
  11. }
复制代码

有了它之后GameModel中需要持有一个拖拽代理的实例:

  1. public class GameModel
  2. {
  3. ……                
  4. public var dragProxy:DragProxy = new DragProxy();
  5. ……
  6. }
复制代码

最后,看看ItemManager中对拖放操作的改变吧:

  1. private function onBGClicked(event:MouseEvent):void{                        
  2. if( gameModel.isDraging ){
  3.         //若当前放入格中存在物品,检查目标格与拖拽物品类型是否一致,若一致则交换位置
  4.           //注:Config.ITEM_TYPE类型为物品格,能放一切类型物品
  5.         if( event.target is ItemView ){
  6.                 var targetView:ItemView = event.target as ItemView;
  7.                 if( targetView.cell.type == gameModel.dragProxy.dataSource.itemVO.type
  8.                 ||  targetView.cell.type == Config.ITEM_TYPE ){
  9.                         //交换cell的item
  10.                         var temp:ItemCell = targetView.cell;
  11.                         gameModel.dragProxy.dataSource.cell.item = targetView;
  12.                         temp.item = gameModel.dragProxy.dataSource;
  13.                 }
  14.         }
  15.         //若当前放入格中不存在物品,检查目标格与拖拽物品类型是否一致,若一致则放入格中
  16.         else if( event.target is ItemCell ){
  17.                 var targetCell:ItemCell = event.target as ItemCell;
  18.                 if( targetCell.type == gameModel.dragProxy.dataSource.itemVO.type
  19.                         ||  targetCell.type == Config.ITEM_TYPE ){
  20.                         gameModel.dragProxy.dataSource.cell.item = null;
  21.                         targetCell.item = gameModel.dragProxy.dataSource;
  22.                 }
  23.         }
  24.         //若当前点击对象为非格子区域,则表示你想丢弃物品,弹出丢弃物品提示框
  25.         else if( event.target is ItemManager){
  26.                 LifeAlert.show( "确定丢弃该物品吗", AlertWindow.OK | AlertWindow.CANCEL, stage, onClose );
  27.         }
  28.         stopItemDrag();
  29. }else if( event.target is ItemView ){
  30.         //进行拖拽
  31.            gameModel.dragProxy.bitmapData = (event.target as ItemView).viewBitMapData;
  32.         gameModel.dragProxy.width = gameModel.dragProxy.height = Config.CELL_SIZE;
  33.         gameModel.dragProxy.dataSource = event.target as ItemView;
  34.         startItemDrag();                
  35. }
复制代码

好了,大致要点就这么多了,具体的还有一些小细节的代码修改都需要列位细细地去品味我给出的代码啦,这个应用做到这里其实还有很大的拓展空间哦~

原文地址:https://www.cnblogs.com/keng333/p/2712330.html