《游戏脚本的设计与开发》-(战棋部分)2.4 物理攻击

终于到了攻击部分了,战棋游戏中的攻击,主要分为物理攻击和法术攻击,本章就先从物理攻击讲起。物理攻击又分为普通攻击,连击(双击),以及致命攻击,再复杂一点的还有其他特殊攻击,比如我的《三国记-乱世群雄》游戏里面,张飞的三次攻击,关羽的多人攻击等特殊的技能攻击。我依然从简单开始,先来看看如何来实现一下普通攻击,攻击的过程是,1,A对B进行攻击。2,B受伤或者档格。3,如果A在B的攻击范围之内,则B会进行一次反击。4,A受伤或者档格。

上一章发布之后,隔的时间比较长了,主要是工作忙,没太多时间整理这些东西,下面我主要介绍一下代码的核心部分,最后有源码下载,大家可以对照着文章看一下,方便理解。

攻击菜单

上一章已经解决了人物移动,接下来当人物移动结束的时候,需要弹出一个攻击选择,先看看攻击菜单的制作。

ffunction LSouSouSMapMenuCtrl(x,y){
	var self = this;
	base(self,LSprite,[]);
	self.addMenu(x,y);
}
LSouSouSMapMenuCtrl.prototype.addMenu = function(x,y){
	var self = this;
	LSouSouObject.sMap.smapClick.removeClickEvent(); 
	var menuLayer = new LSprite();
	var buttonAtk = new LButtonSample1("攻击");
	buttonAtk.backgroundCorl = "red";
	buttonAtk.x = 15;
	buttonAtk.y = 15;
	menuLayer.addChild(buttonAtk);
	var buttonTactics = new LButtonSample1("策略");
	buttonTactics.x = 15;
	buttonTactics.y = buttonAtk.getHeight() + buttonAtk.y;
	menuLayer.addChild(buttonTactics);
	var buttonProps = new LButtonSample1("道具");
	buttonProps.x = 15;
	buttonProps.y = buttonAtk.getHeight()*2 + buttonAtk.y;
	menuLayer.addChild(buttonProps);
	var buttonStop = new LButtonSample1("停止");
	buttonStop.x = 15;
	buttonStop.y = buttonAtk.getHeight()*3 + buttonAtk.y;
	menuLayer.addChild(buttonStop);
	var buttonCancel = new LButtonSample1("取消");
	buttonCancel.backgroundCorl = "red";
	buttonCancel.x = 15;
	buttonCancel.y = buttonAtk.getHeight()*4 + buttonAtk.y;
	menuLayer.addChild(buttonCancel);
	var selfWidth = buttonAtk.getWidth()+30;
	var selfHeight = buttonAtk.getHeight()*5+30;
        var bar = LSouSouObject.getBar(selfWidth,selfHeight);
	menuLayer.addChild(bar);
	self.addChild(menuLayer);
	x += LSouSouObject.sMap.nodeLength;
	if(x + LSouSouObject.sMap.backLayer.x + selfWidth > LSouSouObject.sMap.SCREEN_WIDTH){
		x -= (selfWidth + LSouSouObject.sMap.nodeLength); 
	}
	if(y + LSouSouObject.sMap.backLayer.y + selfHeight > LSouSouObject.sMap.SCREEN_HEIGHT){
		y = LSouSouObject.sMap.SCREEN_HEIGHT - selfHeight - LSouSouObject.sMap.backLayer.y;
	}
	self.x = x + LSouSouObject.sMap.backLayer.x;
	self.y = y + LSouSouObject.sMap.backLayer.y;
	
	buttonAtk.addEventListener(LMouseEvent.MOUSE_UP,self.onclickAtk);
	buttonCancel.addEventListener(LMouseEvent.MOUSE_UP,self.onclickCancel);
};
LSouSouSMapMenuCtrl.prototype.onclickAtk = function(e){
};
LSouSouSMapMenuCtrl.prototype.onclickCancel = function(e){
};

下面的一行代码是上一章中移动结束事件的添加

LSouSouObject.charaSNow.addEventListener(LSouSouEvent.CHARACTER_MOVE_COMPLETE,self.onShowAttackMenu);

修改如下

LSouSouObject.charaSNow.addEventListener(LSouSouEvent.CHARACTER_MOVE_COMPLETE,self.onMoveComplete);
相应的回调函数onMoveComplete如下。
LSouSouSMap.prototype.onMoveComplete = function(){
	var self = LSouSouObject.sMap;
	LSouSouObject.charaSNow.removeEventListener(LSouSouEvent.CHARACTER_MOVE_COMPLETE,self.onMoveComplete);
	//移动结束
	if(LSouSouObject.charaSNow.belong == LSouSouObject.BELONG_SELF){
		self.showCtrlMenu();
	}else{
		//敌军AI,暂略
	}
};

上面代码,当移动结束的人物所属是我军的时候,调用showCtrlMenu函数,如下

LSouSouSMap.prototype.showCtrlMenu = function(){
	var self = this;
	self.menu = LSouSouSMapMenu.addSMenu(LSouSouObject.charaSNow.x,LSouSouObject.charaSNow.y,"ctrl");
	self.menuLayer.addChild(self.menu);
};

这样就添加了攻击菜单了,效果如下


攻击范围

点击上面的选择列表中的攻击按钮,首先应该确定攻击范围。

在arms.json兵种设定文件中,攻击范围主要是由两个属性决定,RangeAttack是攻击的范围,RangeAttackTarget是攻击时一次攻击能攻击到的人数范围,比如下面的游戏截图中,红色区域由RangeAttack决定,绿色区域由RangeAttackTarget决定。


首先来显示攻击范围,修改LSouSouSMapMenuCtrl类的onclickAtk函数,如下。

LSouSouSMapMenuCtrl.prototype.onclickAtk = function(e){
	var i,nodeChild;

	LSouSouObject.sMap.menu.parent.removeChild(LSouSouObject.sMap.menu);
	LSouSouObject.sMap.menu = null;
	var attackRange = LSouSouObject.sMap.attackRange = LSouSouObject.charaSNow.member.getRangeAttack();
	for(i=0;i<attackRange.length;i++){
		nodeChild = attackRange[i];
		LSouSouObject.sMap.attackRangeLayer.graphics.drawRect(1,"#000000",
			[nodeChild.x*LSouSouObject.sMap.nodeLength + LSouSouObject.charaSNow.x - LSouSouObject.sMap.backLayer.x,
			nodeChild.y*LSouSouObject.sMap.nodeLength + LSouSouObject.charaSNow.y - LSouSouObject.sMap.backLayer.y,
			LSouSouObject.sMap.nodeLength,LSouSouObject.sMap.nodeLength],true,"#FF0000");
	}
	LSouSouObject.sMap.menu = LSouSouSMapMenu.addSMenu(LSouSouObject.charaSNow.x,LSouSouObject.charaSNow.y,"cancel");
	LSouSouObject.sMap.menuLayer.addChild(LSouSouObject.sMap.menu);
	LSouSouObject.sMap.menu.clickEvent = function(event){
		LSouSouObject.sMap.menu.parent.removeChild(LSouSouObject.sMap.menu);
		LSouSouObject.sMap.menu = null;
		LSouSouObject.sMap.attackRangeLayer.graphics.clear();
		LSouSouObject.sMap.showCtrlMenu();
	};
	LSouSouObject.sMap.smapClick.setAtkClickEvent();
};

其中的一行

LSouSouSMapMenu.addSMenu(LSouSouObject.charaSNow.x,LSouSouObject.charaSNow.y,"cancel");

是添加一个取消按钮,用处是当点击攻击按钮之后,可以取消之后重新选择,在flash版三国记中取消按钮是被我添加到了右侧的按钮列表,如下


这样的话,用户体验上不太好,所以这次我把取消按钮添加到攻击者的位置上,取消按钮代码如下。

function LSouSouSMapMenuCancel(x,y){
	var self = this;
	base(self,LSprite,[]);
	self.clickEvent = null;
	self.addMenu(x,y);
}
LSouSouSMapMenuCancel.prototype.addMenu = function(x,y){
	var self = this;
	var menuLayer = new LSprite();
	var buttonCancel = new LButtonSample1("×");
	menuLayer.addChild(buttonCancel);
	self.addChild(menuLayer);
	self.x = x + LSouSouObject.sMap.backLayer.x + (LSouSouObject.sMap.nodeLength - buttonCancel.getWidth())*0.5;
	self.y = y + LSouSouObject.sMap.backLayer.y + (LSouSouObject.sMap.nodeLength - buttonCancel.getHeight())*0.5;
	buttonCancel.addEventListener(LMouseEvent.MOUSE_UP,self.onclickCancel);
};
LSouSouSMapMenuCancel.prototype.onclickCancel = function(e){
	var self = LSouSouObject.sMap.menu;
	if(self.clickEvent)self.clickEvent();
};

运行程序后,效果如下。



攻击

最后,就是当点击攻击的范围,并且点击的位置有敌军存在的话,就开始攻击,点击事件如下

LSouSouSMapClick.prototype.onAtkClickUp = function(e){
	trace("onAtkClickUp is click");
	var mx = e.selfX;
	var my = e.selfY;
	var intX = ((mx - LSouSouObject.sMap.backLayer.x)/LSouSouObject.sMap.nodeLength) >>> 0;
	var intY = ((my - LSouSouObject.sMap.backLayer.y)/LSouSouObject.sMap.nodeLength) >>> 0;
	var nodeStr;
	var nodeArr;
	var _characterS;
	var list = LSouSouObject.sMap.enemylist;
	for(i=0;i _characterS.x + LSouSouObject.sMap.backLayer.x && 
			mx < _characterS.x + LSouSouObject.sMap.backLayer.x + LSouSouObject.sMap.nodeLength && 
			my > _characterS.y + LSouSouObject.sMap.backLayer.y && 
			my < _characterS.y + LSouSouObject.sMap.backLayer.y + LSouSouObject.sMap.nodeLength){
			LSouSouObject.sMap.menu.parent.removeChild(LSouSouObject.sMap.menu);
			LSouSouObject.sMap.menu = null;
			LSouSouObject.sMap.attackRangeLayer.graphics.clear();
			LSouSouObject.charaSNow.targetCharacter = _characterS;
	trace("LSouSouObject.charaSNow.targetCharacter="+LSouSouObject.charaSNow.targetCharacter);
			//攻击
			LSouSouObject.charaSNow.setAttackNumber();
			LSouSouObject.charaSNow.attackCalculate();
		}
	}
};

上面的代码,当点击到敌军的时候,首先调用LSouSouCharacterS类的setAttackNumber函数,然后调用attackCalculate函数,setAttackNumber函数是决定攻击次数,比如双击等多次攻击的判定是在这个函数中决定的,本次不考虑多次攻击,所以setAttackNumber函数中的攻击次数是默认为1的,如下

LSouSouCharacterS.prototype.setAttackNumber = function(){
	var self = this;
	//双击判定 暂略

	self.attackNumber = 1;
	self.attackIndex = 0;
	self.targetCharacter.attackIndex = 0;
	self.targetCharacter.attackNumber = 1;
};

attackCalculate函数,包含攻击之前的特技等判断,暂时也不考虑,如下

LSouSouCharacterS.prototype.attackCalculate = function(){
	var self = this;
	
	//特技判定 暂略

	//物理攻击
	self.toAttackStart();
};

然后调用toAttackStart函数,开始物理攻击,代码如下。

LSouSouCharacterS.prototype.toAttackStart = function(){
	var self = this;
	trace("self.targetCharacter="+self.targetCharacter);
	if(!self.targetCharacter)return;
	if(self.x > self.targetCharacter.x){
		self.action = LStaticSouSouCharacterS.ATK_LEFT;
	}else if(self.x < self.targetCharacter.x){
		self.action = LStaticSouSouCharacterS.ATK_RIGHT;
	}else if(self.y > self.targetCharacter.y){
		self.action = LStaticSouSouCharacterS.ATK_UP;
	}else{
		self.action = LStaticSouSouCharacterS.ATK_DOWN;
	}
	self.action_mode = LStaticSouSouCharacterS.MODE_ATTACK;
	self.addEventListener(LSouSouEvent.ANIMATION_COMPLETE,self.attackOver);
};

attackOver函数是当攻击结束时调用的,代码如下。

LSouSouCharacterS.prototype.attackOver = function(){
	var self = this;
	var charas,i;
	var target = self.targetCharacter;
	var checkBelong = LSouSouObject.sMap.belong_mode;
	self.removeEventListener(LSouSouEvent.ANIMATION_COMPLETE,self.attackOver);
	self.attackIndex++;
	//双击或双击以上攻击 暂略

	//对方没有混乱,兵力大于0,并且可以反击
	if(self.targetCharacter.member.getTroops() > 0
		 && self.targetCharacter.attackIndex < self.targetCharacter.attackNumber){
		self.action = LStaticSouSouCharacterS.DOWN + self.direction;
		for(i=0;i<self.targetArray.length;i++){
			charas = self.targetArray[i];
			if(charas.action_mode == LStaticSouSouCharacterS.MODE_STOP){
				charas.action = LStaticSouSouCharacterS.DOWN + charas.direction;
			}else{
				charas.action = LStaticSouSouCharacterS.MOVE_DOWN + charas.direction;
			}
		}
		trace("self.targetCharacter.targetCharacter="+self.targetCharacter.targetCharacter);
		trace("LSouSouCharacterSAI="+LSouSouCharacterSAI.atAttackRect(self.targetCharacter,self.locationX(),self.locationY()));
		if(self.targetCharacter.targetCharacter && 
			LSouSouCharacterSAI.atAttackRect(self.targetCharacter,self.locationX(),self.locationY())){
			self.targetCharacter.attackCalculate();
		}else{
			self.action_mode = LStaticSouSouCharacterS.MODE_STOP;
			self.targetCharacter.targetCharacter = null;
			self.targetCharacter = null;
		}
	}else{
		for(i=0;i<self.targetArray.length;i++){
			charas = self.targetArray[i];
			if(charas.action_mode == LStaticSouSouCharacterS.MODE_STOP){
				charas.action = LStaticSouSouCharacterS.DOWN + charas.direction;
			}else{
				charas.action = LStaticSouSouCharacterS.MOVE_DOWN + charas.direction;
			}
		}
		if(self.belong == LSouSouObject.charaSNow.belong){
			self.action_mode = LStaticSouSouCharacterS.MODE_STOP;
			self.action = LStaticSouSouCharacterS.DOWN + this.direction;
			self.targetCharacter.targetCharacter = null;
			self.targetCharacter = null;
		}else{
			self.action = LStaticSouSouCharacterS.MOVE_DOWN + this.direction;
			self.targetCharacter.action = LStaticSouSouCharacterS.DOWN + self.targetCharacter.direction;
			self.targetCharacter.action_mode = LStaticSouSouCharacterS.MODE_STOP;
			self.targetCharacter.targetCharacter = null;
			self.targetCharacter = null;
		}
	}
};

上面代码中,有攻击者还是反击者等判断,toAttackStart和attackOver函数,只是攻击开始和攻击结束的处理,至于攻击过程,掉血还是档格等,是在攻击动作的第三个动作时开始处理的,LSouSouCharacterS的贞函数中有下面的代码。

	if(self.action_mode == LStaticSouSouCharacterS.MODE_ATTACK){
		self.toAttackTargets();
	}

就是说当人物处在攻击动作时,调用toAttackTargets函数,相关代码如下。

LSouSouCharacterS.prototype.toAttackTargets = function(){
	var self = this,charas,_charalist,i;
	if(!self.targetCharacter)return;
	if(self.actionIndex == 2){
		_charalist = {};
		if(self.belong == LSouSouObject.BELONG_SELF || self.belong == LSouSouObject.BELONG_FRIEND){
			for(i=0;i<LSouSouObject.sMap.enemylist.length;i++){
				charas = LSouSouObject.sMap.enemylist[i];
				if(!charas.visible)continue;
				_charalist[charas.locationX() + "," + charas.locationY()] = charas;
			}
		}else{
			for(i=0;i<LSouSouObject.sMap.ourlist.length;i++){
				charas = LSouSouObject.sMap.ourlist[i];
				if(!charas.visible)continue;
				_charalist[charas.locationX() + "," + charas.locationY()] = charas;
			}
			for(i=0;i<LSouSouObject.sMap.friendlist.length;i++){
				charas = LSouSouObject.sMap.friendlist[i];
				if(!charas.visible)continue;
				_charalist[charas.locationX() + "," + charas.locationY()] = charas;
			}
		}
		self.targetArray = [];
		var node,nodeArr,rangeAttackTarget = self.member.getRangeAttackTarget();
		for(i=0;i= charas.member.getTroops()){
			hertValue = charas.member.getTroops();
			//获取经验值计算 暂略
		}
		//特技修正 暂略
		//减HP 暂略
		charas.action = LStaticSouSouCharacterS.HERT;
		
		//减HP显示 
		var numberShow = new LSouSouSMapNumber(-hertValue);
		numberShow.x = charas.x + (charas.getWidth() - numberShow.getWidth())*0.5;
		numberShow.y = charas.y + (charas.getHeight() - numberShow.getHeight())*0.5;
		LSouSouObject.sMap.charaLayer.addChild(numberShow);
	}else{
		//档格
		if(charas.member.getIndex() == self.targetCharacter.member.getIndex()){
			charas.targetCharacter = self;
		}
		if(self.x > charas.x){
			charas.action = LStaticSouSouCharacterS.BLOCK_RIGHT;
		}else if(self.x < charas.x){
			charas.action = LStaticSouSouCharacterS.BLOCK_LEFT;
		}else if(self.y > charas.y){
			charas.action = LStaticSouSouCharacterS.BLOCK_DOWN;
		}else{
			charas.action = LStaticSouSouCharacterS.BLOCK_UP;
		}
	
	}
	//升级判定 暂略
}

下面是攻击效果

测试连接如下

http://lufylegend.com/demo/test/lsharp/11/game/index.html

以上,本章就先讲这么多了,下一章会讲一下我方,敌方和友方回合之间的切换,以及敌军和友军方面简单的攻击AI

本章为止的源码如下,不包含lufylegend.js引擎源码,请自己到官网下载

http://lufylegend.com/demo/test/lsharp/11/11.rar

※源码运行说明:需要服务器支持,详细请看本系列文章《序》和《第一章》

《游戏脚本的设计与开发》系列文章目录

http://blog.csdn.net/lufy_legend/article/details/8888787

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

转载请注明:转自lufy_legend的博客http://blog.csdn.net/lufy_legend
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页