白桦的天空

第一次的心动,永远的心痛!
  首页  :: 新随笔  :: 联系 :: 管理

RPG游戏设计之逍遥的房间

Posted on 2008-07-02 12:29  白桦的天空  阅读(625)  评论(2编辑  收藏  举报

没做LOADING提示,所以需要等一下加载完以后才可以看到画面. 点击预览

首先看一下整个工程的结构.

由于是一个小DEMO.所以很多东西都没有抽象出来.例如角色类Role就必须得抽象出一个人物的基类出来..等

LoadImage.as 主要是加载图片,然后存为BitmapData类型以备调用.

代码:

package net.conanlwl
{
 import flash.display.Sprite;
 import flash.display.Loader;
 import flash.net.URLRequest;
 import flash.net.URLLoaderDataFormat;
 import flash.net.URLLoader;
 import flash.display.Bitmap;
 import flash.display.BitmapData;
 import flash.geom.Point;
 import flash.events.Event; 
 
 public class LoadImage extends Sprite
 {  
  //var
  private var _picSrc:String;  
  private var _bitmapdata:BitmapData;  
  
  public function LoadImage(PicSrc:String){
   _picSrc = PicSrc;    
  }
  
  public function get bitmapdata():BitmapData{
  
   return _bitmapdata;
  }  
  public function LoadPicture():void{
   var loader:Loader = new Loader();
   loader.contentLoaderInfo.addEventListener(Event.COMPLETE,onComplete);
   loader.load(new URLRequest(this._picSrc));
   function onComplete(e:Event):void{      
    _bitmapdata = new BitmapData(loader.width,loader.height,true,0x00000000);       
    _bitmapdata.draw(loader);
    //this._bitmapdata = Bitmap(loader.content).bitmapData;            
    dispatchEvent(new Event(GameConst.EVENT_PICLOADED));    
   }
  }
  
  public function dispose():void{
   _bitmapdata.dispose();
  } 
 
 }
}

当图片加载完以后,它就会广播一个事件GameConst.EVENT_PICLOADED,这样子,只要调用它的类监听到这个事件便可以使用该图片了.构造函数接受一个参数,就是图片的地址.

Role.as类就是角色类了.角色类包括人物走动,显示,跟踪以及与场景的碰撞检测.

人物的图片是由八个方向九个分步动作构成.如逍遥的图片:

所以第一步我们就必须得将这些图片切成一小张一小张.并按照纵横的顺序存于一个二维的数组里m_RoleStep[][]

所以如果我们将方向变量设为direction,步伐设为step.那么如果你想要显示某个分动作.只要指定direction与step的值,然后调用数组里相应的BitmapData对象并加入显示列表就可以显示了.

如 addChild(new Bitmap(m_RoleStep[direction][step]));

当我们加载完图片并存入了数组m_RoleStep以后,我们就可以启动计时器来进行下一步的键盘监听或者跟踪了.

由于我们的人物是可以斜着走的,而斜着走就意味着要同时按下两个键.而AS3已经取消了AS2里的Key.IsDown的方法,所以我们必须得自己来写键盘监听的帮助函数来同时监听多个按键同时的按下.KEY.as是我在网上找到的一种方法,只要指定监听的对象,然后通过KEY.IsDown(keycode)的返回值来判断某个按键是否按下了.

这是KEY.as的代码:

package net.conanlwl
{
 import flash.events.Event;
 import flash.events.KeyboardEvent;
 import flash.display.DisplayObject;
 public class KEY {
        private static  var keyObj:KEY = null;
        private static  var keys:Object;

        public static function init(_stage:DisplayObject):void {
            if (keyObj == null) {
                keys = {};
                _stage.addEventListener(KeyboardEvent.KEY_DOWN, KEY.keyDownHandler);
                _stage.addEventListener(KeyboardEvent.KEY_UP, KEY.keyUpHandler);
            }
        }
        public static function isDown( keyCode:uint ):Boolean {
            return keys[keyCode];
        }
        private static function keyDownHandler( e:KeyboardEvent ):void {
            keys[e.keyCode] = true;
        }
        private static function keyUpHandler( e:KeyboardEvent ):void {
            delete keys[e.keyCode];
        }
    }
}

由于在整个DEMO中.我们需要用到的人物坐标大多数都是人物最下面的中点,也就是站足点的中点,如碰撞检测,以及人物显示排序,所以利用两个变量_x,_y来代表人物的坐标,最后再通过setPlace()方法,将坐标还原到原始的左上角.这是setPlace()方法的实现:

private function setPlace():void{
   people.x = _x - people.width/2;
   people.y = _y - people.height;
  }

人物跟踪的思路也很简单,首先我们必须声明一个数组,followArray,然后被跟踪的对象每走一步,便把当前的状态(当前的坐标,以及方向,_x,_y,direction)存入数组,然后跟踪的对象只要读取被跟踪的对象的这个路径状态数组.并转化为自己的状态,就可以实现跟踪了,这就类似一个队列,被跟踪对象不断的往队尾推入数据,而跟踪对象则不断的从队头推出并读出数据还原状态,跟踪的距离由followDelay表示,当被跟踪的对象刚开始行走并存入数组的时候,不立即读取数据.而是等到被跟踪的对象行走了followDelay步后,才开始读取数组的第一个元素,这样子便可以使两个对象产生距离了.

碰撞检测是本DEMO的一个难点,但理解以后也并非很困难,但本方法的效率不是很高,希望高手能够提供更好的解决方案,在本DEMO中,并非是使用人物本身去与与场景做检测,而是使用一个半径为4的小圆圈来与场景碰撞,在本DEMO中,我将它定义为foot:BitmapData,它永远位于人物的立足中点,也即_x,_y.而碰撞场景也并非我们所看到的那个场景,而是由另外一个由透明颜色来描述场景的可移动范围的图片,如图所示:


图中透明的区域便是人物可移动的区域

碰撞检测所使用的方法,正是BitmapData中的hitTest()方法.由帮助文档可知hitTest()方法接受五个参数,其定义为:

public function hitTest(firstPoint:Point, firstAlphaThreshold:uint, secondObject:Object, secondBitmapDataPoint:Point = null, secondAlphaThreshold:uint = 1):Boolean
参数  firstPoint:Point — 任意坐标空间中 BitmapData 图像的左上角的位置。 在定义 secondBitmapPoint 参数时,使用了相同的坐标空间。 
 
 firstAlphaThreshold:uint — 最大的 Alpha 通道值,此点击测试将其视为不透明的。 
 
 secondObject:Object — 一个 Rectangle、Point、Bitmap 或 BitmapData 对象。 
 
 secondBitmapDataPoint:Point (default = null) — 一个点,用于定义第二个 BitmapData 对象中的一个像素位置。 仅当 secondObject 的值是 BitmapData 对象时使用此参数。 
 
 secondAlphaThreshold:uint (default = 1) — 最大的 Alpha 通道值,它在第二个 BitmapData 对象中被视为不透明的。 仅当 secondObject 的值是 BitmapData 对象,并且这两个 BitmapData 对象都为透明时使用此参数。 

返回  Boolean — 如果发生点击,则值为 true;否则为 false。
假设我们每走一步的位移为10个像素,所以我们每走一步之前就必须得先考虑一下10个像素远的地方是否为障碍物,如果是则不走这一步,如果不是则继续行走.可是我们会发现一个问题,就是当障碍物的宽度比较小,而我们人物的速度比较大的时候,如我们场景中的那一扇门,如果我们站在门后往门前的方向走,那么我们会发现,在10个像素后是没有障碍物的.如果此时我们继续走下去的话,就会出现人物穿门而过的现象了.所以为了避免这个现象,就必须除了检测10个像素处是否为障碍物之外,还必须9个像素处,8个像素处....一直到1个像素远处是否为障碍物为止.然后记录最后一次发现障碍的像素距离,便可决定下一步的位移是多少了.

如上图所示,如果人物此时的位置在A点,而走下一步以后,将走到B点.如果此时判断B点是否为障碍的话,会得到B点并非障碍的判断.但如图所知,中间有两个比步伐小的障碍存在着,所以我们还必须得判断第9个像素远的位置是否为障碍,结果还不是,结果就必须再判断8,以此类推.可以得到这样的一个判断集合(T为True即为障碍F为False为非障碍,)10F,9F,8T,7T,6T,5F,4F,3T,2F,1F,所以我们可以知道,最后一次出现障碍是在第2个像素之后,所以人物最远就只能走到2这个点上,所以我们只要将此时的速度设为2就可以了.这是HitTest的实现.参数_xv,_yv为速度的向量

private function HitTest(_xv:int,_yv:int):void{
   if(mapMask && foot)
   { 
    var tempSpeed:int = speed;
  //  for(var i:uint=1;i<=speed;i++){    
    for(var i:uint=speed;i>0;i--){   
     if(foot.hitTest(new Point(_x+tempSpeed*_xv-4,_y+tempSpeed*_yv-8),0x01,mapMask,new Point(0,0),0x01)){
      tempSpeed--;      }     
    }   
    _x += tempSpeed*_xv;
    _y += tempSpeed*_yv;   
   }
   if(_x > this.SIDE_RIGHT)_x = this.SIDE_RIGHT;
   if(_x < this.SIDE_LEFT)_x = this.SIDE_LEFT;
   if(_y > this.SIDE_DOWN)_y = this.SIDE_DOWN;
   if(_y < this.SIDE_UP)_y = this.SIDE_UP;
  }

/////////////////////////////////////////////////////////////////////

GameStage.as是地图场景的基类,主要完成对地图的背景,元件,以及碰撞地图的加载.

XiaoYaoRoom.as是逍遥房间的布局类.主要指定了场景中的某些遮挡物的坐标以及大小等.

最后回到主场景类RPGRole.as

主场景类主要就是对人物以及场景类的声明以及调用.而重点就在于显示排序上.什么是显示排序呢?如果没有显示排序的话.那么我们会发现,无论逍遥是站在灵儿的后面还是前面,都是灵儿的图片覆盖在逍遥的图片上面,那是因为我们是先对逍遥加入显示列表,然后再加入灵儿的.所以只要灵儿与逍遥的图片有重叠的话,灵儿的图片都会覆盖在逍遥的上面,也就是因为显示列表的特点.后加入列表的对象都处于最上层.所以我们必须解决这种重叠错误的问题,

那么我们排序的依据是什么呢?也就是根据什么来决定到底是要后加入显示列表,还是先加入显示列表呢?那么依据就是物件的立足点了,也就是离我们的屏幕下方越近的地方,他就必须处于显示列表的越上方,也就是y坐标越大就离我们越近,这就是为什么我们Role类中的坐标必须要用_x,_y来表示的原因了.想一下为什么不能使用人物的左上角的y坐标来进行比较排序呢?那是因为如果此时场景中有一个很矮小的人物站在后头,而有一个很高大的人物站在前面,那么此时矮小的人物左上角的y坐标有可能比前面的高大的人的y坐标还要大,如果进行排序而后加入显示列表的话,矮小的人就会反而挡住了前面的高大的人了.

综上所述,我们可以将整个游戏画面分为三大层来显示,每一层就是最底层的背景.永远处于最下面,无需进行排序.第二大层,就是动态排序层.根据人物的坐标变化而不断改变显示次序.最上一层则为悬空层,例如本DEMO中门上的横梁,虽然在某一时刻,人物的y坐标可能比横梁还要大,但横梁仍然是覆盖在人物上面,所以悬空层是在动态排序层以后,最后再显示的一个层~

通过这三个层的正确排序以后,就可以看到一个完整的游戏场景了.

提供Flex2工程的源码下载 : 点击下载

关于实例的预览方法.由于Flex的安全沙箱的关系.直接运行bin目录下的RPGRole.swf文件将会抛出安全错误异常.所以你可以通过双击运行bin目录下的RPGRole.swf文件,然后点击菜单的文件->创建播放器,将保存在bin目录下,然后运行该EXE文件便可正常预览

若有其它问题,或者更好的代码思想建议,欢迎一起讨论.