概要

本篇文章主要介绍的有:
1.在RTS,RPG类游戏中如何划分地图:格子(Grid)、路点(Waypoint)、导航网格(Navmesh)
2.各类寻路算法的优劣:A星,IDA星,DIJKSTRA等
3.业界如何实现AI攻击前后摇与攻击效果和攻击动画的匹配
4.在我的项目中我是如何实现攻击前后摇并且使它与攻击动画匹配的
参考资料:
如何划分地图:寻路建模的三种方式比较
A星,IDA星寻路算法
NavMesh插件教程
攻击前后摇


1.自动寻路

1.1 如何划分地图

划分地图共有3种方法:格子(Grid)、路点(Waypoint)、导航网格(Navmesh)
三种方法各有优劣 总体来说:

  • 实现复杂度:导航网格 > 格子 > 路点
  • 内存和计算开销3:格子 > 导航网格 > 路点
  • 表达精确性:导航网格 > 格子 > 路点
    格子与路点实现起来都比较简单.
    格子开销大,路点开销小.
    格子实际走起路来非常不平滑,而路点既需要人工参与,同时其局限性比较大.
    而对比起来,虽然NavMesh导航网格实现起来比较复杂,但是由于其优秀的内存开销能力以及路径的精确性、灵活性和平滑性,再加上如今已经有现成的NavMesh可以用,业界普遍使用NavMesh导航网格来划分地图.

1.2 各类寻路算法优劣

这里着重讲一下A星和IDA星:

  • 这两个算法的效率都与估价函数的准确度挂钩.
  • A星是是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。
  • A星是基于BFS的启发式搜索,而IDA星是基于迭代加深搜索的启发式搜索.
  • 当A星的空间需求非常大时,则考虑用IDA星,因为IDA星基于的迭代加深搜索本质是DFS搜索,所以对空间的需求不大,但对时间方面的需求较大

2.攻击前后摇的实现

2.1业界如何实现攻击前后摇

1.为目标位置添加碰撞体(格斗类,动作类游戏)

  • 之前风靡全国的拳皇系列,它的人物攻击判定就是使用的矩形碰撞盒。而且就Unity而言,我想到的是根据动画,每帧更新碰撞框的位置。
  • 现在的怪物猎人,他会根据你攻击巨型怪兽的不同部位来计算伤害和特效。

2.利用动画帧事件来进行伤害判定和特效生成(MOBA游戏中的攻击无弹道类英雄)
这种方式适用于对击打判定精度要求不太高的游戏。

  • 流行的MOBA游戏,英魂之刃,混沌与秩序之英雄战歌,虚荣
  • LOL,DOTA2.

这些游戏都有一个特点,那就是近战英雄的攻击,只会判定打没打到,而不是打到了哪个部位。(远程英雄是发射子弹的,这里不谈),如果还应用第一种方式来判定伤害的话,无疑会造成性能上的浪费,因为我们没必要判定打到了哪里。

所以我们不需要再多余的给人物某个部位添加碰撞体,这将毫无意义。

我们需要做的只是将计算伤害的时间点把握好,添加帧事件,当这个动画播放到这里的时候,就执行这个事件。

这也同时解决了攻击前摇,后摇,硬直等一系列的问题。只要自己在代码层面上安排好,这些都不是问题。

当然,像英雄联盟中,那些有弹道的英雄,如大多数ADC,都不是采用这样的方法来实现,他们应该也是通过碰撞体来实现的.

2.2自己如何实现攻击前后摇

2.2.1思路

  • 这里,我用到了两个函数:AnimatorStateInfo的.IsName()和.normalizedtime().
  • 第一个是检查当前播放的动画是不是怪物攻击的动画,如果是,才能说明怪物在攻击,实现了怪物不在攻击时必定不会触发这个脚本的功能.
  • 第二个是检查当前播放的动画播放到什么时间了,有了这个我就可以判断在当前时间怪物的攻击是否落下,如果落下,我就在这个时间段的一小段范围内,触发这个脚本,并且设置人物的无敌时间为这一小段时间,大概是0.1s,很短,所以实际玩起来是基本感觉不到的.
  • 这样的优点是怪物的攻击落下时间非常严格,也非常直接,不会有延迟的情况.而且人物也不会多次受击,各方面都比较完美.

2.2.2代码

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Gamekit3D
{
    public class MyApplyDamager : MonoBehaviour 
    {


        public int amount;
        public LayerMask damagedLayers;
        public Collider MyCollider;
        public float BeforetheAttackRoll = 0.7f; //攻击前摇时间
        private void OnTriggerStay(Collider other) //碰撞体判断是否与其他碰撞体发生碰撞
        {

            if ((damagedLayers.value & 1 << other.gameObject.layer) == 0)
                return;
            Animator MyAni = GetComponentInParent<Animator>();// 得到AI当前的动画组件
            AnimatorStateInfo stateinfo = MyAni.GetCurrentAnimatorStateInfo(0); //得到AI当前的动画信息
            bool OK = stateinfo.IsName("ChomperAttack"); //判断当前动画是否为AI攻击动画
            float time=stateinfo.normalizedTime; //表示当前动画播放进度
            //如果该动画是攻击动画且动画进度在攻击落下瞬间左右
            if (OK && stateinfo.normalizedTime>=BeforetheAttackRoll-0.05f && stateinfo.normalizedTime<=BeforetheAttackRoll+0.05f)
            {

                MyCollider = other;
                applyDamage();//对另一碰撞体发送受击信息
            }

        }
        public void applyDamage()
        {
            Damageable d = MyCollider.GetComponentInChildren<Damageable>();
            if (d != null && !d.isInvulnerable)
            {
                //发送受击信息
                Damageable.DamageMessage message = new Damageable.DamageMessage
                {
                    damageSource = transform.position,
                    damager = this,
                    amount = amount,
                    direction = (MyCollider.transform.position - transform.position).normalized,
                    throwing = false
                };
                d.ApplyDamage(message);
            }
        }
    }

    public class MyHelpBoxAttribute : PropertyAttribute
    {

    }

#if UNITY_EDITOR
    [CustomPropertyDrawer(typeof(HelpBoxAttribute))]
    public class MyHelpBoxDrawer : PropertyDrawer
    {
        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return Mathf.Max(EditorGUIUtility.singleLineHeight * 2,
                EditorStyles.helpBox.CalcHeight(new GUIContent(property.stringValue), Screen.width) +
                EditorGUIUtility.singleLineHeight);
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            EditorGUI.HelpBox(position, property.stringValue, MessageType.Info);
        }
    }
#endif
}