返回总目录

第九章 战斗系统(Combat System)

在SRPG中,大多数情况是指角色与角色之间的战斗。而这种战斗一般有两种模式:

  • 地图中直接战斗;

  • 有专门的战斗场景。

这两种模式的战斗在数据上没有任何区别,只有战斗动画的区别。

就像之前描述的,SRPG也是RPG,所以战斗方式和回合制RPG的战斗几乎是相同的。主要区别是RPG每个回合需要手动操作(也有自动战斗的),直到战斗结束;而SRPG是自动战斗,且多数情况只有一个回合(额外回合也是由于技能、物品或剧情需要)。且RPG多数是多人战斗,而SRPG多数是每边就一个。

我们这一章就来写一个战斗系统。


文章目录

  • 第九章 战斗系统(Combat System)
    • 四 计算战斗数据II(Calculate Combat Data II)
      • 1 分离每一次行动(Battle Action)
      • 2 准备战斗(Prepare Battle)
      • 3 计算战斗(Calculate Battle)
      • 4 说明与测试(Description and Test)

四 计算战斗数据II(Calculate Combat Data II)

在之前,我们只计算了一般情况下的战斗数据,这一节,我们来扩充它,让它可以计算其它种类的数据。


1 分离每一次行动(Battle Action)

在计算中,我们曾经:

  • public void BattleBegin()中计算了准备阶段的数据;

  • private void CalcBattle(CombatVariable atkVal, CombatVariable defVal)中计算了攻击阶段的数据。

我们将这些计算方式分离出Combat,单独建立一个类来存储这些方法。

创建类BattleAction

using UnityEngine;namespace DR.Book.SRPG_Dev.CombatManagement
{public enum BattleActionType{Unknow,Prepare,Attack,MageAttack,Heal,// 其它自定义类型}public abstract class BattleAction : ScriptableObject{private string m_Message = "Unknow battle message.";public string message{get { return m_Message; }protected set { m_Message = value; }}public abstract BattleActionType actionType { get; }public abstract CombatStep CalcBattle(Combat combat, CombatVariable atkVal, CombatVariable defVal);public abstract bool IsBattleEnd(Combat combat, CombatVariable atkVal, CombatVariable defVal);public sealed override string ToString(){return m_Message;}}
}

而在Combat中,我们只需要调用这个类就可以了。

为了能更好的控制它们,首先需要存储它们,在Combat中添加:

        public BattleAction[] m_BattleActions;private Dictionary<BattleActionType, BattleAction> m_BattleActionDict = new Dictionary<BattleActionType, BattleAction>();private void Awake(){if (m_BattleActions != null && m_BattleActions.Length > 0){for (int i = 0; i < m_BattleActions.Length; i++){if (m_BattleActions[i] == null){continue;}BattleAction action = m_BattleActions[i];if (m_BattleActionDict.ContainsKey(action.actionType)){Debug.LogWarningFormat("Battle Action {0} is exist. OVERRIDE.", action.actionType.ToString());}m_BattleActionDict[action.actionType] = action;}}unit0 = new CombatUnit(0);unit1 = new CombatUnit(1);steps = new List<CombatStep>();}

2 准备战斗(Prepare Battle)

public void BattleBegin()中,我们之前是计算了战斗开始的准备阶段。

为了分离它们,我们先建立PrepareAction

using UnityEngine;namespace DR.Book.SRPG_Dev.CombatManagement
{using DR.Book.SRPG_Dev.Models;[CreateAssetMenu(fileName = "CombatPrepareAction.asset", menuName = "SRPG/Combat Prepare Action")]public class PrepareAction : BattleAction{public override BattleActionType actionType{get { return BattleActionType.Prepare; }}public override CombatStep CalcBattle(Combat combat, CombatVariable atkVal, CombatVariable defVal){CombatUnit atker = combat.GetCombatUnit(0);CombatUnit defer = combat.GetCombatUnit(1);// 防守者是否可反击bool canDeferAtk = false;if (defer.role.equipedWeapon != null){Vector3Int offset = defer.mapClass.cellPosition - atker.mapClass.cellPosition;int dist = Mathf.Abs(offset.x) + Mathf.Abs(offset.y);WeaponUniqueInfo defInfo = defer.role.equipedWeapon.uniqueInfo;// 如果在反击范围内if (dist >= defInfo.minRange && dist <= defInfo.maxRange){canDeferAtk = true;}}// 根据速度初始化攻击者与防守者if (canDeferAtk){if (atker.speed < defer.speed){CombatUnit tmp = atker;atker = defer;defer = tmp;}}// 更新信息this.message = "战斗开始";atkVal = new CombatVariable(atker.position, atker.hp, atker.mp, true, atker.durability, CombatAnimaType.Prepare);defVal = new CombatVariable(defer.position, defer.hp, defer.mp, canDeferAtk, defer.durability, CombatAnimaType.Prepare);// 准备阶段CombatStep firstStep = new CombatStep(atkVal, defVal);return firstStep;}public override bool IsBattleEnd(Combat combat, CombatVariable atkVal, CombatVariable defVal){return false;}}
}

这和之前计算过程唯一的区别就是新增了message,它是战斗中显示的文字,不是必须的。

而在public void BattleBegin()中,由于已经分离了方法,所以修改后:

        /// <summary>/// 开始战斗/// </summary>public void BattleBegin(){if (!isLoaded){Debug.LogError("Combat -> StartBattle: please load combat unit first.");return;}if (stepCount > 0){Debug.LogError("Combat -> StartBattle: battle is not end.");return;}BattleAction action;if (!m_BattleActionDict.TryGetValue(BattleActionType.Prepare, out action)){Debug.LogError("Combat -> StartBattle: BattleActionType.Prepare is not found, check the code.");return;}// 准备阶段CombatStep firstStep = action.CalcBattle(this, default(CombatVariable), default(CombatVariable));steps.Add(firstStep);if (!action.IsBattleEnd(this, firstStep.atkVal, firstStep.defVal)){CalcBattle(firstStep.atkVal, firstStep.defVal);}}

3 计算战斗(Calculate Battle)

private void CalcBattle(CombatVariable atkVal, CombatVariable defVal)中,我们之前只有计算普通攻击的方法。而现在,由于计算数据的多样化,我们需要判断使用哪种计算方式。

在游戏中,我们计算数据的方式由武器决定(当然你可以添加其它参数控制):

        /// <summary>/// 获取行动方式(如何计算战斗数据)/// </summary>/// <param name="weaponType"></param>/// <returns></returns>private BattleActionType GetBattleActionType(WeaponType weaponType){// TODO 由于没有动画支持,所以并没有其他武器// 你可以添加其他武器到这里switch (weaponType){case WeaponType.Sword://case WeaponType.Lance://case WeaponType.Axe://case WeaponType.Bow:return BattleActionType.Attack;//case WeaponType.Staff://if ( 如果法杖是治疗 )//{//    return BattleActionType.Heal;//}//else if ( 法杖是其它等 )//{//    return BattleActionType.自定义类型;//}//case WeaponType.Fire://case WeaponType.Thunder://case WeaponType.Wind://case WeaponType.Holy://case WeaponType.Dark://    return BattleActionType.MageAttack;default:return BattleActionType.Unknow;}}

我们根据武器类型判断了执行的计算方法,你也可以在物品或技能中加入参数,选择使用哪种计算方法。

这样,我们计算方法:

        /// <summary>/// 计算战斗数据/// </summary>private void CalcBattle(CombatVariable atkVal, CombatVariable defVal){CombatUnit atker = GetCombatUnit(atkVal.position);BattleActionType actionType = GetBattleActionType(atker.weaponType);BattleAction action;if (!m_BattleActionDict.TryGetValue(actionType, out action)){Debug.LogErrorFormat("Combat -> StartBattle: BattleActionType.{0} is not found, check the code.",actionType.ToString());return;}CombatStep step = action.CalcBattle(this, atkVal, defVal);steps.Add(step);// 如果战斗没有结束,交换攻击者与防守者if (!action.IsBattleEnd(this, step.atkVal, step.defVal)){if (step.defVal.canAtk){CalcBattle(step.defVal, step.atkVal);}else{// 如果防守方不可反击defVal = step.defVal;defVal.action = true;if (!action.IsBattleEnd(this, defVal, step.atkVal)){CalcBattle(step.atkVal, defVal);}}}else{// TODO 如果死亡,播放死亡动画(我把死亡动画忘记了)// if (step.defVal.isDead) 播放死亡动画}}

在之前的private void CalcBattle(CombatVariable atkVal, CombatVariable defVal)中,我们计算了普通攻击方式的计算,还需要移植到新的方式中。

创建AttackAction

using UnityEngine;namespace DR.Book.SRPG_Dev.CombatManagement
{[CreateAssetMenu(fileName = "CombatAttackAction.asset", menuName = "SRPG/Combat Attack Action")]public class AttackAction : BattleAction{public sealed override BattleActionType actionType{get { return BattleActionType.Attack; }}public override CombatStep CalcBattle(Combat combat, CombatVariable atkVal, CombatVariable defVal){CombatUnit atker = combat.GetCombatUnit(atkVal.position);CombatUnit defer = combat.GetCombatUnit(defVal.position);atkVal.animaType = CombatAnimaType.Attack;// 真实命中率 = 攻击者命中 - 防守者回避int realHit = atker.hit - defer.avoidance;// 概率是否击中int hitRate = UnityEngine.Random.Range(0, 100);bool isHit = hitRate <= realHit;if (isHit){bool crit = false; // TODO 是否爆击int realAtk = atker.atk;///// TODO 触发伤害技能// 这里写触发技能后伤害变化(比如武器特效等),// 或者触发某些状态(比如中毒等)//if (crit){realAtk *= 2; // 假定爆击造成双倍伤害}// 掉血 = 攻击者攻击力 - 防守者防御力// 最少掉一滴血int damageHp = Mathf.Max(1, realAtk - defer.def);if (damageHp > defVal.hp){damageHp = defVal.hp;}defVal.hp = defVal.hp - damageHp; atkVal.crit = crit;defVal.animaType = CombatAnimaType.Damage;// 更新此次攻击信息this.message = string.Format("{0} 对 {1} 的攻击造成了 {2} 点伤害{3}。",atker.role.character.info.name,defer.role.character.info.name,damageHp,crit ? "(爆击)" : string.Empty);if (defVal.isDead){this.message += string.Format(" {0}被击败了。", defer.role.character.info.name);}}else{defVal.animaType = CombatAnimaType.Evade;// 更新此次躲闪信息this.message = string.Format("{1} 躲闪了 {0} 的攻击。",atker.role.character.info.name,defer.role.character.info.name);}// 只有玩家才会减低耐久度if (atker.role.attitudeTowards == AttitudeTowards.Player){// 攻击者武器耐久度-1atkVal.durability = Mathf.Max(0, atkVal.durability - 1);}// 攻击者行动过了atkVal.action = true;CombatStep step = new CombatStep(atkVal, defVal);return step;}public override bool IsBattleEnd(Combat combat, CombatVariable atkVal, CombatVariable defVal){// 防守者死亡if (defVal.isDead){return true;}// 如果防守者行动过了if (defVal.action){//CombatUnit atker = GetCombatUnit(atkVal.position);//CombatUnit defer = GetCombatUnit(defVal.position);// TODO 是否继续攻击,必要时需要在 CombatVariable 加入其它控制变量// 比如,触发过技能或物品了// atker.role.skill/item 包含继续战斗的技能或物品// defer.role.skill/item 包含继续战斗的技能或物品//if ( 触发继续战斗 )//{//    // return false;//}return true;}return false;}}
}

4 说明与测试(Description and Test)

我们在之前已经完全分离了计算方式,你可以自己建立各种各样的计算方法。

你也可以添加额外的变量来计算它们,例如状态属性:石化,睡眠,沉默等等(在Role中添加状态属性,在《FE4》中有对应法杖与特效武器)。

而我把BattleAction继承自ScriptableObject是因为,你可以在每个地图能够使用不同的计算方式(比如不同地图使用不同的BattleActionType.Attack的Asset)。

这以上种种由创造力策划决定。

说道这里,我们来测试它们是否工作正常:

  • 创建Assets:

    • 图 9.5 BattleAction Assets
  • 添加组件并保存Prefab:

    • 图 9.6 MapGraph Inspector
  • 最后运行游戏,查看动画播放是否正常,输出信息是否正常。


SRPG游戏开发(四十二)第九章 战斗系统 - 四 计算战斗数据II(Calculate Combat Data II)相关推荐

  1. SRPG游戏开发(四十)第九章 战斗系统 - 二 计算战斗数据(Calculate Combat Data)

    返回总目录 第九章 战斗系统(Combat System) 在SRPG中,大多数情况是指角色与角色之间的战斗.而这种战斗一般有两种模式: 地图中直接战斗: 有专门的战斗场景. 这两种模式的战斗在数据上 ...

  2. 陈力:传智播客古代 珍宝币 泡泡龙游戏开发第十二讲:盒子的定位方式

    陈力:传智播客古代 珍宝币 泡泡龙游戏开发第十二讲:盒子的定位方式 摘要:通过前节<第十一讲:浮动>学习了贵阳网站建设中的DIV+CSS中盒子模型和浮动进行介绍.框模型是CSS的基础,本文 ...

  3. SRPG游戏开发(四十一)第九章 战斗系统 - 三 战斗动画(Combat Animation)

    返回总目录 第九章 战斗系统(Combat System) 在SRPG中,大多数情况是指角色与角色之间的战斗.而这种战斗一般有两种模式: 地图中直接战斗: 有专门的战斗场景. 这两种模式的战斗在数据上 ...

  4. 【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一)

    这是答应大家的讲解骨骼动画的文章的N部曲的第二篇.这篇文章里,我们对现行的三种模型动画技术进行了概述,然后对X文件构成进行了详细的剖析,最后放出了骨骼动画的第一个示例程序,载入了<诛仙>中 ...

  5. 【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一)

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8832812 作者:毛星云(浅墨 ...

  6. 【Visual C 】游戏开发笔记十二 游戏输入消息处理 一 键盘消息处理

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 本系列文 ...

  7. 动画骨骼【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一)...

    间时紧张,先记一笔,后续优化与完善. 本系列文章由zhmxy555(毛星云)编写,载转请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/detail ...

  8. 【Visual C++】游戏开发笔记十二 游戏输入消息处理(一) 键盘消息处理

    相信大家都熟悉<仙剑奇侠传98柔情版>的人机交互方式,用的仅仅是键盘.在那个物质并不充裕的时代,一台配置并不高的电脑,一款名叫<仙剑奇侠传>的游戏,却能承载一代人对梦想的追逐. ...

  9. SRPG游戏开发(十)第五章 颜色映射与职业动画 - 二 颜色组(Color Chart)

    返回目录 第五章 颜色映射与职业动画 二       颜色组(Color Chart) 颜色组是保存许多颜色的一个容器,可以在Swapper中直接创建List<Color>或Color[] ...

最新文章

  1. 用JavaScript创建神经网络的有趣教程,一定要让你知道!
  2. 如何优雅的使用 Angular 表单验证
  3. 【珍藏版】Linux最强总结来啦!
  4. foxmail邮件加载失败重试_java retry(重试) spring retry, guava retrying 详解
  5. 《软件设计师》——数据结构和算法基础
  6. 华为鸿蒙编程:如何显示网络图片
  7. Axure tabstrip and different control
  8. 35. 搜索插入位置011(二分查找)
  9. [整理]解析Json需要设置Mime
  10. PowerPoint的巧妙使用就可造就一场经济而又专业的知识竞赛场面
  11. java小游戏跳棋_Java跳棋小游戏源代码
  12. 兴达易控Profinet转TCP以太网模块
  13. php的include once,php include_once的使用方法详解
  14. pythonapi是什么意思_API是什么意思?API文档又是什么意思?
  15. 查看ip地址 通过域名
  16. ubuntu设置MySQL密码
  17. html中repeat的作用,background-repeat属性怎么用
  18. NSGA 2 学习笔记 -- crowding distance
  19. 【c语言】编程实现分段函数(适合初学者)
  20. 如何使用HTTPS加密保护网站?

热门文章

  1. 三星N7100 Galaxy Note2 Root权限获取详细教程
  2. 关于软件测试的个人见解
  3. 魔兽世界服务端源码各个重要文件详细情况说明——魔兽世界开服
  4. 」北京有名恁地一个 水浒传
  5. C++实现文件保存为ANSI/UTF-8/UCS-2文件
  6. 传统企业数字化现状和问题
  7. 有效沟通(余世维)讲座笔记
  8. 仙剑三功略(常见问题解答)
  9. python柱形图的横坐标重叠在一起_如何让柱形图重叠在一起?
  10. 基于JAVAWEB的新疆特产销售系统