返回《SRPG游戏开发》导航

第十一章 地图动作与地图事件(Map Action and Map Event)

我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制。

我们这一章来完成地图上的操作,地图的操作将全部由MapAction控制。


文章目录

  • 第十一章 地图动作与地图事件(Map Action and Map Event)
    • 十 NPC操作(NPC Control)
      • 1 准备工作(Preparation)
      • 2 每帧动作(Update)
      • 3 NPC的移动(NPC Move)
        • 3.1 寻找攻击目标(Find Attack Target)
        • 3.2 寻找最近目标(Find Nearest Target)
        • 3.3 移动结束(Move End)
      • 4 NPC的攻击(NPC Attack)

十 NPC操作(NPC Control)

自动化操作NPC是SRPG游戏必须有的。

我们使用的是MapActionUpdate方法来执行这些。


1 准备工作(Preparation)

我们需要对NPC逐个操作,这就需要一个变量来告知MapAction,我们进行到了哪里。

添加字段:

        private int m_NpcIndex = 0;

而在切换阵营时,我们需要将下标重置:

        /// <summary>/// 转换回合/// </summary>protected void NextTurn(){// 重置npc下标npcIndex = 0;// 省略其它代码}

而在Update中,就如同我们的输入一样,必须满足以下条件:

  • 阵营不能是玩家;

  • 地图不能播放动画;

  • 地图不能执行事件。

所以,我们的基本方法:

        /// <summary>/// 每帧刷新/// </summary>/// <returns></returns>public override bool Update(){// 省略MapScenarioAction操作if (turn != AttitudeTowards.Player){if (mapStatus == MapStatus.Animation || mapStatus == MapStatus.Event || mapStatus == MapStatus.Menu || mapStatus == MapStatus.SubMenu){return true;}// TODO 具体NPC操作return true;}return false;}

我们对每个NPC进行的操作无外乎“移动”与“其它行动”;而“其它行动”又包含“攻击”、“对话”等等。这取决于我们的AI。我们目前并没有对AI进行编写,所以暂只考虑“移动”与“攻击”

首先,我们先建立两个空的方法:

        /// <summary>/// NPC移动,/// </summary>/// <param name="npc"></param>/// <returns></returns>public void NpcMove(MapClass npc){// TODO}/// <summary>/// NPC攻击/// </summary>/// <param name="npc"></param>public void NpcAttack(MapClass npc){// TODO}

2 每帧动作(Update)

要让NPC进行行动,必须先有NPC,所以我们要先获取它。

同时,如果阵营所有NPC行动结束,则要下一个阵营。

Update方法中,获取NPC:

                // 获取npcList<MapClass> units;if (!m_UnitDict.TryGetValue(turn, out units) || npcIndex >= units.Count){NextTurn();return true;}MapClass unit = units[npcIndex];

其次,我们让光标追随NPC(这在以后运动摄像机时,让摄像机跟随光标即可)。

                // 光标跟随if (map.mouseCursor.cellPosition != unit.cellPosition){MoveCursorCommand(unit.cellPosition, 0.5f);return true;}

最后,是我们NPC的行动,初步行动是移动,规定使用MapStatus.AttackCursor来表示是否攻击。

注意:在玩家时,MapStatus.AttackCursor表示是否显示攻击范围;而在NPC时,表示移动后是否对目标进行攻击。你也可以不使用它,而单独建立一个变量表示NPC的行动。

即NPC行动:

                // npc移动if (mapStatus != MapStatus.AttackCursor){NpcMove(unit);}// npc攻击else{NpcAttack(unit);}

3 NPC的移动(NPC Move)

我们在这之前,先建立一个简易的AI,让NPC就近选择玩家,如果能够攻击到就攻击,不能攻击到就朝向最近的玩家移动。

        /// <summary>/// NPC移动,/// </summary>/// <param name="npc"></param>/// <returns></returns>public void NpcMove(MapClass npc){Vector3Int npcPosition = npc.cellPosition;CellData npcCell = map.GetCellData(npcPosition);selectedCell = npcCell;selectedUnit = npc;/// TODO 这里可以根据AI来判断敌人的移动/// NpcAi ai = aiModel.Get(npc.ai);/// 以下是寻找敌人进行攻击的简易AI(忽略了治疗或状态等的移动)///// 假定中立部队不可移动if (turn == AttitudeTowards.Neutral){ClearSelected();npcIndex++;return;}CellData moveToCell = npcCell;// TODO 简易AI,朝向最近目标移动或攻击。// 移动MoveMapClass(npc, moveToCell);}

在进行这个AI的编写时,我们应先知道有没有可攻击目标,所以应该先搜索移动范围内的攻击目标。

            HashSet<CellData> moveRange = new HashSet<CellData>(map.SearchMoveRange(npcCell, npc.role.movePoint, npc.role.moveConsumption));bool atk = false; // 是否有可攻击的目标

3.1 寻找攻击目标(Find Attack Target)

为了寻找攻击目标,需要满足以下条件:

  • NPC需要有武器;

  • 目标在武器攻击范围内;

  • 目标不能同阵营(假定也不能是中立);

  • 如果NPC是盟友,则目标还不能是玩家。

逐个武器进行判断:

            // 逐个武器进行判断for (int i = 0; i < npc.role.items.Length; i++){Item item = npc.role.items[i];if (item == null || item.itemType != ItemType.Weapon){continue;}// 搜索所有移动范围内的可攻击目标// <目标位置,可攻击到目标的移动位置>Dictionary<CellData, HashSet<CellData>> deferDict= new Dictionary<CellData, HashSet<CellData>>(map.cellPositionEqualityComparer);WeaponUniqueInfo info = item.uniqueInfo as WeaponUniqueInfo;foreach (CellData cell in moveRange){List<CellData> atkCells = map.SearchAttackRange(cell, info.minRange, info.maxRange, true);/// 防守者/// 1 必须是有地图对象/// 2 地图对象是地图职业/// 3 目标不能同阵营(如果是治疗,必须是同阵营或同盟)/// 4 目标不能是中立IEnumerable<CellData> defers = atkCells.Where(c => c.hasMapObject&& c.mapObject.mapObjectType == MapObjectType.Class&& (c.mapObject as MapClass).role.attitudeTowards != turn&& (c.mapObject as MapClass).role.attitudeTowards != AttitudeTowards.Neutral);/// 如果是盟友,还不应包含玩家if (npc.role.attitudeTowards == AttitudeTowards.Ally){defers = defers.Where(c => (c.mapObject as MapClass).role.attitudeTowards != AttitudeTowards.Player);}foreach (CellData c in defers){if (!deferDict.ContainsKey(c)){deferDict[c] = new HashSet<CellData>(map.cellPositionEqualityComparer);}deferDict[c].Add(cell);}}if (deferDict.Count > 0){// 寻找最近的目标CellData targetCell = null;int minDist = int.MaxValue;foreach (CellData cell in deferDict.Keys){int dist = CalcCellDist(cell.position, npcPosition);if (dist < minDist){targetCell = cell;minDist = dist;}}// 寻找最近的可攻击到目标的位置minDist = int.MaxValue;foreach (CellData cell in deferDict[targetCell]){int dist = CalcCellDist(cell.position, npcPosition);if (dist < minDist){moveToCell = cell;minDist = dist;}}// 设置攻击目标targetUnit = targetCell.mapObject as MapClass;atk = true;npc.role.EquipWeapon(item as Weapon);}// 如果能攻击到目标if (atk){break;}}

3.2 寻找最近目标(Find Nearest Target)

如果没有攻击目标,我们需要寻找最近距离的目标。

            // 如果没有可攻击到的目标if (!atk){// 寻找最近的敌对角色Vector3Int nearPos = Vector3Int.zero;int minDist = int.MaxValue;foreach (KeyValuePair<AttitudeTowards, List<MapClass>> kvp in m_UnitDict){// 不能是自己和中立if (kvp.Key == npc.role.attitudeTowards || kvp.Key == AttitudeTowards.Neutral){continue;}// 如果是盟友,还应忽略己方if (npc.role.attitudeTowards == AttitudeTowards.Ally && kvp.Key == AttitudeTowards.Player){continue;}foreach (MapClass unit in kvp.Value){int dist = CalcCellDist(unit.cellPosition, npcPosition);if (dist < minDist){minDist = dist;nearPos = unit.cellPosition;}}}// 假定只走一半的移动力,走到离目标最近的点float movePoint = npc.role.cls.info.movePoint / 2f;foreach (CellData cell in moveRange.Where(c => c.g <= movePoint)){int dist = CalcCellDist(cell.position, nearPos);if (dist <= minDist){minDist = dist;moveToCell = cell;}}}

3.3 移动结束(Move End)

不要忘记在移动结束时,我们要判断是否有攻击目标,并改变状态。

        /// <summary>/// 移动结束回调/// </summary>/// <param name="mapClass"></param>/// <param name="endCell"></param>private void MapClass_OnMovingEnd(CellData endCell){// 省略部分代码// 如果是玩家if (turn == AttitudeTowards.Player){selectedUnit.role.OnMoveEnd(endCell.g); // 减去移动消耗ShowMapMenu(true);}// npcelse{// 如果npc没有攻击目标,就待机并下一个npcif (targetUnit == null){HoldingMapClass(selectedUnit);npcIndex++;}else{mapStatus = MapStatus.AttackCursor;}}}

4 NPC的攻击(NPC Attack)

NPC的攻击,和玩家的攻击只有在攻击结束后有区别,其它没有区别。

所以方法不变:

        /// <summary>/// NPC攻击/// </summary>/// <param name="npc"></param>public void NpcAttack(MapClass npc){AttackMapClass(npc, targetUnit);}

战斗动画结束后:

        /// <summary>/// 战斗结束回调/// </summary>/// <param name="combatAnima"></param>/// <param name="inMap"></param>private void MapClass_OnCombatAnimaStop(CombatAnimaController combatAnima, bool inMap){// 省略部分代码if (unit0.role.isDead){TriggerEvents(MapEventConditionType.RoleDeadCondition, () =>{ClearSelected();OnMapClassDead(unit0);UIManager.views.CloseView();if (turn != AttitudeTowards.Player){npcIndex++;}});}else if (unit1.role.isDead){TriggerEvents(MapEventConditionType.RoleDeadCondition, () =>{HoldingMapClass(unit0);OnMapClassDead(unit1);UIManager.views.CloseView();if (turn != AttitudeTowards.Player){npcIndex++;}});}else{HoldingMapClass(unit0);UIManager.views.CloseView();if (turn != AttitudeTowards.Player){npcIndex++;}}}

SRPG游戏开发(六十一)第十一章 地图动作与地图事件 - 十 NPC操作(NPC Control)相关推荐

  1. SRPG游戏开发(二)第一章 FE4部分技术简述

    返回目录 第一章 FE4部分技术简述 本章节主要记录在开发FE4时,分析Rom的内容.我们从进入游戏后所见的顺序进行简述,详细的内容到开发时再谈论. 一    不再阐述的常用系统 这个部分的系统在所有 ...

  2. SRPG游戏开发(三)第二章 创建项目

    返回目录 第二章 创建项目 本章开始我们来创建我们的项目,导入用到的素材. 一 创建项目 打开Unity3D,点击New按钮.这时我们看到创建项目的设置界面. 图 2 - 1创建项目 Project ...

  3. SRPG游戏开发(六十三)第十一章 地图动作与地图事件 - 十二 完善地图信息与测试(Perfect MapEventInfo and Testing)

    返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...

  4. SRPG游戏开发(六十)第十一章 地图动作与地图事件 - 九 触发事件与切换回合(Trigger Events and Change Turn)

    返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...

  5. SRPG游戏开发(五十六)第十一章 地图动作与地图事件 - 五 移动、攻击与待机(Move, Attack and Holding)

    返回总目录 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一章来完成地图上的操作,地图的操 ...

  6. SRPG游戏开发(五十二)第十一章 地图动作与地图事件 - 一 初始化地图动作(Initialize Map Action)

    返回总目录 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一章来完成地图上的操作,地图的操 ...

  7. SRPG游戏开发(五十三)第十一章 地图动作与地图事件 - 二 地图剧情(Map Plot)

    返回总目录 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一章来完成地图上的操作,地图的操 ...

  8. SRPG游戏开发(六十四)间章 第十一点五章 总结(Summary)

    返回<SRPG游戏开发>导航 间章 第十一点五章 总结(Summary) 这一章,是对第十章与第十一章的一个补充性质的文章. 文章目录 间章 第十一点五章 总结(Summary) 一 说明 ...

  9. 【麦可网】Cocos2d-X跨平台游戏开发学习笔记---第二十一课:Cocos2D-X网格特效1-3

    [麦可网]Cocos2d-X跨平台游戏开发---学习笔记 第二十一课:Cocos2D-X网格特效1-3 ================================================ ...

最新文章

  1. 【数字信号处理】傅里叶变换性质 ( 共轭对称与共轭反对称图像示例 | 实序列中共轭对称是偶对称 | 实序列中共轭反对称是奇对称 )
  2. HBase在大搜车金融业务中的应用实践
  3. 排序 (2)快速排序
  4. 在阿里云Serverless K8S集群上部署Spark任务并连接OSS(详细步骤)
  5. CODEVS 3269 混合背包
  6. 华为新系统鸿蒙和ios,鸿蒙系统呼之欲出 华为上线新功能和苹果iOS相比如何
  7. python和java哪个好找工作-Python和java哪个就业前景好些?
  8. 数据--第31课 - 树的存储结构
  9. oracle 字段对错,oracle 两表之间字段赋值错误解析
  10. linux安装搜狗错误,centos安装sogou出错
  11. NOI / 1.3编程基础之算术表达式与顺序执行——12:计算球的体积
  12. ckeditor java 使用教程_Java上CKEditor集成指南
  13. 中国有句俗语叫“三天打鱼两天晒网”。某人从1990年1月1日起开始“三天打鱼两天晒网”, 问这个人在以后的某一天中是“打鱼”还是“晒网”?(考虑中间有几个闰年)
  14. DOM元素 DAY17
  15. 游戏人物属性如何设计
  16. 创建React + Ts项目
  17. continue在c语言中什么作用,continue在c语言中什么意思?
  18. SharedPreferences和SQlite数据库
  19. 2019软科计算机中国排名,权威发布:2019软科中国最好学科排名
  20. 键盘录入 写入文件 quit时 结束

热门文章

  1. 结合DVWA的反射型XSS浅析
  2. 反射内存 延时_PCI5565反射内存网技术的应用研究
  3. 华为、小米轮番超越三星激动人心,且莫沸腾,三星和苹果优势仍在
  4. 探索C++与Live555实现RTSP服务器的艺术
  5. Android原生UI开发框架 《Jetpack Compose入门到精通》最全上手指南
  6. WMSYS.WM_CONCAT标识符无效问题
  7. XSS————2、XSS bypass
  8. 现代密码学:信息安全基础
  9. 《惢客创业日记》2018.10.12(周五) 声音主播背后的“天龙八步”(一)
  10. 用matlab绘制克莱因瓶,克莱因瓶怎么装水动态四维模拟图 克莱因瓶为什么装不满怎么倒水...