ARPG客户端战斗架构设计

来源:互联网 发布:淘宝店资质高说明什么 编辑:程序博客网 时间:2024/05/16 05:28

 原地址

  Ps:所有代码都是unity3d c#版。

       纵观现在的手游,大部分都开始模仿刀塔传奇的模式:多人自动战斗+少数可控制技能。 
       具体的实现细节不考虑,毕竟如果你用cocos2d-x和你用u3d去做可能具体实现起来是完全用了两套系统。这里只对更高层面的设计上进行讨论。

       首先我直接将每个战斗单元Player划分成AI 和 Skill两部分。一方面这样子可以简化设计,讨论重点也比较清晰。(注:这里不考虑团队AI,那样太过复杂。你可能也会疑惑为什么AI和技能会区分开来,而不是AI里面思考技能的施放,这个后面会解释)

      



        状态机管理:

       

public class StateMachine:MonoBehaviour{private Charactor player;private IAIState cur_state;private IAIState glo_state;public void setPlayer (Charactor p){player = p;}public void setCurState(IAIState s){cur_state = s;cur_state.Enter (player);}public void setGlobalState(IAIState s){glo_state = s;glo_state.Enter (player);}public void update(){if (glo_state != null) {glo_state.Execute(player);}if (cur_state != null) {cur_state.Execute(player);}}public void destorySelf(){player = null;if(cur_state != null){AIStateFactory.getInst().reclaim(cur_state);}if(glo_state != null){AIStateFactory.getInst().reclaim(glo_state);}}public void changeState(IAIState newState){player.isAIValid = false;StartCoroutine (exitOldState (newState));}IEnumerator exitOldState(IAIState newState){yield return null;if(player != null){cur_state.Exit (player);AIStateFactory.getInst().reclaim(cur_state);StartCoroutine (setNewState (newState));}}IEnumerator setNewState(IAIState newState){yield return null;if(player != null){cur_state = newState;cur_state.Enter (player);player.isAIValid = true;}}public bool isInState(AITYPE type){return cur_state.getType () == type;}public IAIState getCurState(){return cur_state;}public IAIState getGlobalState(){return glo_state;}public void OnMessage(Charactor player, Hashtable param){bool flag = false;if (cur_state != null) {flag = cur_state.OnMessage (player, param);}if(flag){return;}if (glo_state != null) {flag = glo_state.OnMessage(player, param);}}}

状态接口:

public interface IAIState{void Enter(Charactor player);void Execute(Charactor player);void Exit(Charactor player);bool OnMessage(Charactor player, Hashtable param);AITYPE getType();void reclaim();}

然后你只需要实现一个个状态就可以了。例如巡逻:

public class WanderState : IAIState{public const string changePurse = "changePurse";Charactor _player;public AITYPE getType(){return AITYPE.Wander;}public void Enter(Charactor player){player.setState (CharactorState.Battleing, false);player.startWander ();_player = player;if(player.aiStyle.isBoss){SceneEvent.getInst().addEvent(SceneEvent.CameraGoThrowDone, CameraGoThrowDone);}}void CameraGoThrowDone(){BossShowState state = AIStateFactory.getInst ().create (AITYPE.BossShow) as BossShowState;_player.changeMacheineState (state);}public void Execute(Charactor player){if(player.groupType == GroupType.enemy && !Scene.nowScene.checkInActive(player as BattleCharactor)){return;}BattleCharactor p = player as BattleCharactor;BattleCharactor enemy = Scene.nowScene.findEnemy(player.groupType, player.transform.position, (float)player.getConfig(CharactorConfig.warnDis), false);if (enemy != null) {changeToPurse(player, enemy);p.NotifyTeamToBattle(enemy);}}private void changeToPurse(Charactor player, Charactor enemy){PurseState state = AIStateFactory.getInst ().create (AITYPE.Purse) as PurseState;state.setTarget(enemy);player.changeMacheineState(state);}public void Exit(Charactor player){player.stopWander ();if(player.aiStyle.isBoss){SceneEvent.getInst().removeEvent(SceneEvent.CameraGoThrowDone, CameraGoThrowDone);}}public bool OnMessage(Charactor player, Hashtable param){string msg = param ["msg"].ToString ();Charactor enemy = param ["enemy"] as Charactor;switch (msg) {case changePurse:changeToPurse(player, enemy);return true;break;}return false;}public void reclaim(){_player = null;}}

你可以看到,如果你需要一个新的状态,那么就实现该AI状态的所有接口。所有的转化都是状态本身自己管理,与其他任何模块都无关。AI是完全独立的,需要交互的部分也是通过OnMessage自己处理,不与任何模块强耦合。


2.技能

       这里首先解释一下为什么技能不是AI思考的结果。因为Dota的技能太过复杂。状态机实现AI有一个缺点,就是状态不能太多。如果太多,处理起来会非常麻烦。所以我们只有一个攻击状态机,而怎么攻击,则是由技能模块处理。就相当于变成了两步走战略,我先思考是否攻击。当我决定攻击后,我才思考具体怎么攻击。这好处我们说了,减少状态,坏处就是如果你需要让单元先根据攻击的不同情况再决定是否攻击,那么这是做不到的。但大部分时候,其实并不会有这样的需求,或者说有一些可以特殊处理的方式。总之这是权衡下的结果。

        技能模块除了以上功能之外,还需要包含Buff,目标选择规则,特效,特效播放规则,伤害结算规则,技能施放规则,弹道。

       <a href="http://www.lxway.com/45528801.htm" alt=" target=" _blank"="" style="box-sizing: border-box; text-decoration: none; color: rgb(51, 122, 183); background-color: transparent;">

       如此详细而且确定的将这些模块划分出来,是因为灵活性以及解耦。不然对于刀塔传奇那种复杂的技能机制来说,几乎就是需要大量的特殊处理,让你的代码全部杂糅一块。为了让你更好的理解划分依据,我们来依次实现几个技能,然后思考这些模块是怎么被分离出来的。

       

        首先我希望实现白虎的技能,白虎第一个回合出来就使用了加速,然后下一个动作就是射箭。然后影魔则是普攻两次,影牙连续三次。毫无疑问,你可以意识到技能施放规则需要被分离。因为不同英雄会有不同的规则,分离出来SkillRule,将所有规则放入这个类中是顺利成章的事情。

        接下来具体思考技能本身。白虎加速给其他单位增加了Buff, Buff是游戏里面常用的设计,用Buff的方式处理技能有很多好处。几乎所有的战斗游戏都有Buff。那么我们就可以分离出Buff模块去实现白虎这种给队友增益效果。另外减益效果也是用Buff实现。

       

public class Buff{protected Dictionary<string, string> buffTemp;protected BattleCharactor WhoGiveBuff;public BuffType type;/ * player * target * */CommonBattleInfo info;DateTime startTime;public Buff(){}public void setBuff(Dictionary<string, string> temp, BattleCharactor who, CommonBattleInfo i){buffTemp = temp;WhoGiveBuff = who;info = i;}public virtual void reclaim(){buffTemp = null;WhoGiveBuff = null;info = null;}public virtual void Enter(BattleCharactor player){startTime = DateTime.Now;}public virtual void reset(BattleCharactor player){startTime = DateTime.Now;}public virtual void Excute(BattleCharactor player, CommonBattleInfo i){}public virtual void Exit(BattleCharactor player, bool isAutoStop){}public bool checkBuffExpire(){if(buffTemp["time"] == "-1"){return false;}TimeSpan subTime = DateTime.Now.Subtract (startTime);if(subTime.TotalSeconds >= float.Parse(buffTemp["time"])){return true;}return false;}public bool checkOverrideSelf(){if(buffTemp["selfoverride"] == "2"){return true;}else{return false;}}public string getOverrideType(){return buffTemp["OverrideType"];}public CommonBattleInfo getCommonBattleInfo(){return info;}public buffTimeType getTimeType(){return (buffTimeType)int.Parse(buffTemp ["timeType"]);}public int getWeight(){return int.Parse(buffTemp["weight"]);}public bool isDebuffer(){if(buffTemp["debuff"] == "1"){return false;}return true;}public bool checkNeedRemoveHalo(long instId){if(buffTemp["halo"] == "1" && WhoGiveBuff.instId == instId){return true;}return false;}}


         然后我们又思考到给谁增加buff的问题,例如白虎的射箭,就是让中箭的人晕眩。而影魔的影牙,则是身前某一距离为中心,半径为多少范围内的敌人都受伤害。所以讲目标选择规则抽象出来也是必要的。

       

public class TargetSelect{/ * player * target * targetRuleTemp * */protected CommonBattleInfo info;public TargetSelectType type;public TargetSelect(){}public void setInfo(CommonBattleInfo i){info = i;}public virtual Vector3 getActionFaceToTarget(){return Vector3.zero;}public virtual List<BattleCharactor> getActionTarget(){return null;}public virtual BattleCharactor getMainTarget(){return null;}public virtual void reclaim(){info = null;}}


        技能必然会搭配特效,影魔的影牙就是一个特效。典型的还有白虎的大招。我们立马也看到这两个特效的区别。一个是单纯播放一个特效,一个是每个目标身上播放特效,所以我们可以将特效播放规则和特效模块分离出来。

       

public class EffectPlayRule{protected SkillAction action;public EffectPlayType type;public EffectPlayRule(){}public void setAction(SkillAction a){action = a;}public virtual void play(){}protected void playEffect(Vector3 pos, Hashtable effectParam){CommonBattleInfo info = action.getCommonBattleInfo ();BaseEffect effect = EffectFactory.createEffect (info.effectTemp, effectParam);effect.transform.position = pos + effect.off;if(effect.type == EffectType.particle){effect.transform.parent = Scene.DontDestoryLayer;}else{effect.transform.parent = Scene.nowScene.transform;}effect.play ();Scene.nowScene.addEffectToContainer (effect);float interval = action.getDamageInterval ();if(interval > 0){info.player.addContinouseSkillEffect(effect, action.instId);}BattleReplay.RecordEffect (info.effectTemp ["id"], effectParam, effect.transform.position);}public virtual void reclaim(){action = null;}}


       接下来你应该想到的就是伤害结算。不同的技能伤害结算方式不同。

     

public class TargetDamage{/ * player * target * skillTemp * targetRuleTemp * damageTemp * preResult * nowResult * 可能不全 * */protected CommonBattleInfo info;protected List<BattleCharactor> allTarget;protected BattleCharactor mainTarget;public DamageType type;public TargetDamage(){}public void setInfo(CommonBattleInfo i, List<BattleCharactor> all, BattleCharactor main){info = i;allTarget = all;mainTarget = main;}public virtual DamageResult actionDamaged(){return null;}public virtual void reclaim(){info = null;allTarget = null;mainTarget = null;}public CommonBattleInfo getCommonBattleInfo(){return info;}}


        最后就是弹道,这个就不细说了。



       大体上的思路就是这样,具体需要注意的事项有:

      1.解耦并不容易,当你将模块划分之后,你可能发现你的模块1需要从模块2里获得一些信息,而你并没有预留这种接口。这是非常常见的情况。我这里建议大家建立一个公用的数据类模块,这样这个数据将会再所有模块中传递使用,所有的改变也都发生在这个数据类内部,这样就能解决这种问题。

       2.当你发现你需要特殊处理某种需求的时候,尽量想到一种普适性的解决方案而不是单纯的去特殊if else  ,不然当第二个这种特殊情况出现的时候,你可能只能破罐子破摔了。在第一个出现的时候就要想办法解决。

      3.刀塔传奇里面有很多非常让人赞叹的细节。多思考如果你去实现会怎么实现这种技能或者这种表现形式,多思考。

0 0
原创粉丝点击