上一节中已经介绍了RPG游戏中地图怎么实现,在RPG游戏的地图中通常有各种遮挡,比如人物站在房屋的后面的时候,房子应该遮挡住人物,这就涉及到各种建筑物和人物的排序显示。另外,上一节中我为了测试地图,已经添加了一个简单的人物类Character,这个类是我从其他游戏中拷贝过来的,本次除了介绍地图上的遮挡之外,也会详细介绍一下一个人物类的具体实现方法,包括它的动作改变,方向变换以及行走。
下面首先来说说遮挡,第一种做法是把各个建筑物分离出来,如下面图中的建筑物,
如果经过处理,按照一定的顺序显示到地图上,就应该是下面的效果。
如果要实现上图中的效果,就需要把人物和建筑物放到同一层,并且在人物移动的时候,实时的对地图上所有的建筑物和人物进行排序。
另外,关于地图的遮挡,还有一种较为简单的做法,就是把建筑物层和人物层分离开来,将建筑层放到人物层的上面,然后把建筑物变为半透明,比如下面的图片。
这样,如果将它放到人物层的上方,那么人物移动到相应的位置的时候,就会出现下图种的效果。
可以看到,当人物走到房子的后面的时候,被遮挡住的部分变成了半透明,有不少游戏都采用了这种做法,喜欢玩RPG游戏的朋友们是不是很熟悉?如何把这个透明层添加到游戏中呢,在上一节种已经介绍了地图的图片如何加到游戏中,这个透明层可以用同样的方法来实现,我就不累赘了,稍后会放出代码下载,大家可以自己看一下。
接下来,我来详细说明一下如何来实现一个人物类Character,一个人物有多种动作,比如下面的图。
这只是一个人物动作的一部分,因为人物有站立,走动,跑动,坐下,每个动作都有八个方向,再加上战斗的时候,有攻击,防御,摔倒等等,如果这些图片全部都一次性读取的话,那么图片读取的时间会很长,尤其是网页开发。很明显,这样做是很不明智的,所以要想快速的显示一个人物,我们应该只读取需要显示的人物动作图片,即使是上面的图,虽然是一个跑步的动作,但是因为有八个方向,也不需要一次性读取,所以我们可以把上面的图片进行再分解,也就是说,把每个动作的八个方向都再次进行分解成八张图片,比如下面,是一组向下走动的图片:
那么,这样看起来是不是已经够了?当然不是,因为每进入一张地图之后,地图上可能同时出现几个或者几十个人物,为了让游戏更快速的显示,通常的做法是先选用一张特殊的图片来代替,然后再读取正确的图片,当图片读取完之后,再把原来的图片替换,其实这种做法我已经在之前的《游戏脚本的设计与开发》-(战棋部分)2.2 军队降临战场一文中介绍过了,在这里我再来简单说明一下,下面的Action类可以显示人物的一个动作,代码中的chara-default是预先读取好的一张图片,先显示然后再进行相应图片的读取,而之所以用LAnimationTimeline,是因为要显示的人物都是动态的,LAnimationTimeline可以很方便的播放一组图片的动画。
function Action(index,action,direction){
var self = this;
base(self,LSprite,[]);
var list = LGlobal.divideCoordinate(5120,240,1,16);
var data = new LBitmapData(LMvc.datalist["chara-default"],0,0,320,240);
self.anime = new LAnimationTimeline(data,list);
self.anime.speed = 1;
self.addChild(self.anime);
loader = new LLoader();
loader.parent = self;
loader.addEventListener(LEvent.COMPLETE,self.loadOver);
loader.load(LMvc.IMG_PATH+"character/"+index+"/"+action+"-"+direction+".png","bitmapData");
}
Action.prototype.loadOver = function(event){
var self = event.target.parent;
self.anime.bitmap.bitmapData = new LBitmapData(event.currentTarget,0,0,320,240);
};
之所以把人物的动作单独做成一个类,是因为这样的话,我就不需要事先读取人物的所有动作,连判断都省了,要显示某个动作的话,我只需要new一个Action类的实例并传入相应的参数,然后这个Action就会自动显示默认的图片,并进行图片的读取,省去了大量的判断和处理。
下面来看Character类的实现,先来看下面的代码。
var CharacterAction = {
STAND:"stand",
MOVE:"move"
};
var CharacterDirection = {
DOWN:"down",
LEFT:"left",
RIGHT:"right",
UP:"up",
LEFT_DOWN:"left_down",
RIGHT_DOWN:"right_down",
LEFT_UP:"left_up",
RIGHT_UP:"right_up"
};
function Character(index,w,h){
var self = this;
base(self,LSprite,[]);
self.index = index;
self.list = {};
self.to = new LPoint(self.x,self.y);
self.roads = [];
self.layer = new LSprite();
self.addChild(self.layer);
self.layer.x = w*0.5-320*0.5;
self.layer.y = h*0.5-240*0.5 - 50;
self.w = w;
self.h = h;
self.step = self.moveStep = 4;
self.moveBevelStep = self.moveStep*Math.sin(45*Math.PI/180);
self.moveBevelStep = (self.moveBevelStep*100 >>> 0)/100;
self.directionList = {
"-1,-1":CharacterDirection.LEFT_UP,
"-1,0":CharacterDirection.LEFT,
"-1,1":CharacterDirection.LEFT_DOWN,
"0,-1":CharacterDirection.UP,
"0,1":CharacterDirection.DOWN,
"1,-1":CharacterDirection.RIGHT_UP,
"1,0":CharacterDirection.RIGHT,
"1,1":CharacterDirection.RIGHT_DOWN
};
self.addEventListener(LEvent.ENTER_FRAME,self.onframe);
self.setActionDirection(CharacterAction.STAND,CharacterDirection.DOWN);
}
首先CharacterAction里存放动作,CharacterDirection里存放方向。
而人物类Character的构造器,我来解释一下各个变量的功能。
1,layer是一个LSprite对象,主要是为了调整人物图片的相对位置。
2,在上一节的例子当中,人物在水平和竖直上的移动速度与斜角上的移动速度是不一样的,因为做法是x轴和y轴上移动的速度为v,当斜角移动的时候,我直接让人物在x轴和y轴上同时进行移动,矩形的对角线长度当然是大于边长的,这样斜角上移动的速度很明显就变大了,所以这次设定了moveStep和moveBevelStep两个速度,moveBevelStep是通过moveStep计算而来的斜角移动时的速度。
3,to变量是移动目标的坐标,当人物的当前坐标和移动目标的坐标不一样的时候,会进行移动的处理。
4,roads变量是人物的移动路径,当自动寻路完成之后,会把返回的路径传给它,人物会根据这个路径做相应的移动处理。
5,list变量是为了储存人物的各个动作而建立的。
6,directionList变量是为了在人物移动时,计算人物的移动方向而建的。
7,setActionDirection函数是设定人物的动作,具体看接下来的说明。
Character.prototype.setActionDirection = function(action,direction){
var self = this;
if(self.action == action && self.direction == direction)return;
var key = action+"-"+direction;
if(!self.list[key]){
self.list[key] = new Action(self.index,action,direction);
self.layer.addChild(self.list[key]);
}
if(self.actionObject){
self.actionObject.visible = false;
}
self.actionObject = self.list[key];
self.actionObject.visible = true;
self.action = action;
self.direction = direction;
};
Character.prototype.changeAction = function(action){
var self = this;
self.setActionDirection(action,self.direction);
};
Character.prototype.changeDirection = function(direction){
var self = this;
self.setActionDirection(self.action,direction);
};
changeAction函数和changeDirection函数分别通过调用setActionDirection函数来改变人物的动作和方向,setActionDirection函数中首先判断当前动作和要设定的动作是否相同,如果相同则不做任何处理,否则判断动作是否已经加载,如果没有加载则新建Action实例。
Character.prototype.setCoordinate = function(x,y){
var self = this;
self.x = self.to.x = x;
self.y = self.to.y = y;
};
setCoordinate函数直接设定人物的坐标。
Character.prototype.getTo = function(){
var self = this;
return [self.to.x/self.w >>> 0,self.to.y/self.h >>> 0];
};
Character.prototype.setTo = function(){
var self = this;
var road = self.roads.shift();
self.to.x = road.x*self.w;
self.to.y = road.y*self.h;
};
上面两个函数分别是获取和设定人物的移动目标的坐标,最后来看看人物的移动处理,代码如下。
Character.prototype.move = function(){
var self = this;
if(self.x == self.to.x && self.y == self.to.y)return;
if(self.x != self.to.x && self.y != self.to.y){
self.step = self.moveBevelStep;
}else{
self.step = self.moveStep;
}
var mx = self.getValue(self.x , self.to.x),my = self.getValue(self.y , self.to.y);
self.x += self.step*mx;
self.y += self.step*my;
var cx = self.getValue(self.x , self.to.x),cy = self.getValue(self.y , self.to.y);
if(mx != cx || my != cy){
if(self.roads.length == 0){
self.x = self.to.x;
self.y = self.to.y;
self.changeAction(CharacterAction.STAND);
self.parent.parent.parent.controller.mapMove();
return;
}
var next = self.roads[0];
var nx = self.getValue(self.to.x , next.x),ny = self.getValue(self.to.y , next.y);
if(mx != nx || my != ny){
self.x = self.to.x;
self.y = self.to.y;
}
if(self.roads.length > 0){
self.setTo();
}
}
self.setMoveDirection(mx,my);
self.parent.parent.parent.controller.mapMove();
};
Character.prototype.onframe = function(event){
var self = event.target;
self.move();
};
Character.prototype.setMoveDirection = function(x,y){
var self = this;
var direction = self.directionList[x+","+y];
self.setActionDirection(CharacterAction.MOVE,direction);
};
当人物发生移动的时候,要设定移动速度,当人物只在x轴方向上或者只在y轴方向上移动的时候,把速度设定为moveStep,同时在两个方向上移动时,也就是斜角方向上移动时,把速度设定为moveBevelStep。
然后,根据计算好的速度一个坐标一个坐标的进行移动,当移动完一个坐标之后,判断是否继续移动或者停止,最后调用setMoveDirection函数设定相应的移动方向,最后调用地图控制器的mapMove函数,让人物保持在地图的中央。
OK,经过上面的一番折腾,地图中的遮挡和人物移动相关的处理就差不多了,测试连接如下:http://lufylegend.com/demo/test/lsharp/rpg-lshape-02/index.html
效果如下:
最后,给出本次的代码下载:
https://github.com/lufylegend/lsharp/archive/3.2.zip
※源码运行说明:源码中不含lufylegend.js引擎代码,请自己到官方下载引擎
关于RPG的开发到目前为止,还没有放到L#的脚本中,下次我会把上面RPG开发脚本化,然后接着接着介绍一次性在地图中添加多个人物,人物的排序,或者还可以再加上点人物对话的相关处理,请期待下次更新。
《游戏脚本的设计与开发》系列文章目录
http://blog.csdn.net/lufy_legend/article/details/8888787