终于到了攻击部分了,战棋游戏中的攻击,主要分为物理攻击和法术攻击,本章就先从物理攻击讲起。物理攻击又分为普通攻击,连击(双击),以及致命攻击,再复杂一点的还有其他特殊攻击,比如我的《三国记-乱世群雄》游戏里面,张飞的三次攻击,关羽的多人攻击等特殊的技能攻击。我依然从简单开始,先来看看如何来实现一下普通攻击,攻击的过程是,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