《游戏脚本的设计与开发》-(RPG部分)3.2 地图遮挡和人物行走

上一节中已经介绍了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

本章就讲到这里,欢迎继续关注我的博客

转载请注明:转自lufy_legend的博客http://blog.csdn.net/lufy_legend

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值