zoukankan      html  css  js  c++  java
  • Unity3D 敌人AI 和 动画( Animator )系统的实例讲解

    在这个实例中,我们要做一些敌人AI的简单实现,其中自动跟随和动画是重点,我们要达到的目标如下:

    1.敌人能够自动跟随主角  

    2.敌人模型一共有四个动作:Idle(空闲) Run(奔跑) Attack(攻击) Death(死亡).

    3.要求敌人在合适的时机能够做出合适动作

    (一)自动跟随的实现

    1)首先,新建一个场景  如图,场景里至少有两个角色:  有一个敌人(刀骷髅兵) 还有一个主角(没错,就是那个胶囊体)

    2)先选择场景模型,然后在 Inspector 窗口选项 Static旁边的小三角显示出下拉菜单,确定其中 Navigation Static 被选中.  对于与场景地形无关的模型选项,则要确定没有被选中,如图所示。

                                

    Navigation 窗口的选项主要是定义地形对寻路的影响。Radius 和 Height 可以理解为寻路者的半径和高度。Max Slope 是最大坡度,超过这个坡度寻路者则无法通过。Step Height 是楼梯的最大高度 ,超过这个高度寻路者则无法通过。Drop Height表示寻路者可以跳落的高度极限。Jump Distance 表示寻路者的跳跃距离极限。
     
    3)选择菜单栏里的Window 选项里的Navigation选项   点击下面的bake   渲染完成后是这个样子  这些蓝色的区域就是能自动寻路的区域
                        
     
    4) 然后给主角写 移动控制脚本 和 镜头控制脚本  并赋给主角(胶囊体):
     
      1 public class PlayerControl : MonoBehaviour
      2 {
      3 
      4     //定义玩家的Transform
      5     public Transform m_transform;
      6     //定义玩家的角色控制器
      7     CharacterController m_ch;
      8     //定义玩家的移动速度
      9     float m_movespeed = 10.0f;
     10     //定义玩家的重力
     11     float m_gravity = 2.0f;   
     12     //定义玩家的生命
     13     public int m_life = 5;
     14 
     15     //定义摄像机的Transform
     16     Transform m_cameraTransform;
     17     //定义摄像机的旋转角度
     18     Vector3 m_cameraRotation;
     19     //定义摄像机的高度
     20     float m_cameraHeight = 1.4f;
     21     //定义小地图摄像机
     22     public Transform m_miniMap;
     23 
     24     //定义枪口的Transform m_muzzlepPoint;
     25     Transform m_muzzlePoint;
     26     //定义射击时,射线射到的碰撞层
     27     public LayerMask m_layer;
     28     //定义射中目标后粒子效果的Transform
     29     public Transform m_fx;
     30     //定义射击音效
     31     public AudioClip m_shootAudio;
     32     //定义射击间隔时间计时器
     33     float m_shootTimer = 0;
     34 
     35     
     36 
     37     // Use this for initialization
     38     void Start()
     39     {
     40         //获取玩家本身的Transform 赋给 m_transform
     41         m_transform = this.transform;
     42         //获取玩家本身的CharacterController组件 赋给 m_ch
     43         m_ch = this.GetComponent<CharacterController>();       
     44 
     45         //摄像机的控制的初始化
     46         //获取摄像机的Transform
     47         m_cameraTransform = Camera.main.transform;
     48         //定义一个三维向量用来表示摄像机位置 并把玩家的位置赋给它 设置摄像机初始位置
     49         Vector3 pos = m_transform.position;
     50         //摄像机的Y轴坐标 为 本来的坐标加上上面定义的摄像机高度
     51         pos.y += m_cameraHeight;
     52         //把修改后的摄像机坐标重新赋给m_cameraTransform
     53         m_cameraTransform.position = pos;
     54         //把主角的旋转角度 赋给 摄像机的旋转角度
     55         m_cameraTransform.rotation = m_transform.rotation;
     56         //获取摄像机的角度
     57         m_cameraRotation = m_transform.eulerAngles;        
     58 
     59         //隐藏鼠标
     60         Cursor.visible = false;
     61     }
     62 
     63     // Update is called once per frame
     64     void Update()
     65     {
     66         //如果玩家的生命小于等于0 什么也不做
     67         if (m_life <= 0)
     68         {
     69             return;
     70         }
     71 
     72         //如果玩家的生命大于0 那么调用玩家控制函数 
     73         //移动函数
     74         MoveControl();
     75         //摄像机控制函数
     76         CameraControl();
     77         //跳跃函数
     78         Jump();
     79     }
     80 
     81 
     82     //定义玩家的控制函数
     83     void MoveControl()
     84     {
     85 
     86         //定义玩家在XYZ轴上的移动量
     87         float xm = 0, ym = 0, zm = 0;
     88 
     89         //玩家的重力运动 为 减等于玩家的重力乘以每帧时间
     90         ym -= m_gravity * Time.deltaTime;
     91 
     92         //实现玩家上下左右的运动
     93         //如果按下 W键 玩家在Z轴上的量增加
     94         if (Input.GetKey(KeyCode.W))
     95         {
     96             zm += m_movespeed * Time.deltaTime;
     97         }
     98         //如果按下 S键 玩家在Z轴上的量减少  这里用else if是因为每帧只能按下相反方向的一个键
     99         else if (Input.GetKey(KeyCode.S))
    100         {
    101             zm -= m_movespeed * Time.deltaTime;
    102         }
    103         //如果按下 A键 玩家在X轴上的量减少
    104         if (Input.GetKey(KeyCode.A))
    105         {
    106             xm -= m_movespeed * Time.deltaTime;
    107         }
    108         //如果按下 D键 玩家在X轴上的量增加
    109         else if (Input.GetKey(KeyCode.D))
    110         {
    111             xm += m_movespeed * Time.deltaTime;
    112         }
    113 
    114         ////当玩家在地面上的时候 才能前后左右移动  在空中不能移动
    115         if (!m_ch.isGrounded)
    116         {
    117             xm = 0;
    118             zm = 0;
    119         }
    120 
    121         //通过角色控制器的Move()函数,实现移动
    122         m_ch.Move(m_transform.TransformDirection(new Vector3(xm, ym, zm))); 
    123         
    124 
    125     }
    126 
    127 
    128     //定义玩家的摄像机控制函数
    129     void CameraControl()
    130     {
    131 
    132         //实现对摄像机的控制
    133         //定义主角在horizon方向X轴移动的量  也就是获取主角鼠标移动的量
    134         float rh = Input.GetAxis("Mouse X");
    135         //定义主角在Vertical 方向Y轴移动的量  
    136         float rv = Input.GetAxis("Mouse Y");
    137 
    138 
    139         //旋转摄像机
    140         //把鼠标在屏幕上移动的量转化为摄像机的角度  rv(上下移动的量) 等于 角色X轴的角度    rh(水平移动的量) 等于 角色Y轴上的角度
    141         m_cameraRotation.x -= rv;
    142         //Debug.Log(rv);  向下时 rv 为正值(顺时针)   向上时 rv 为负值(逆时针)
    143         m_cameraRotation.y += rh;
    144         //Debug.Log(rh);  向右时 rh 为正值(顺时针)   向左时 rh 为负值(逆时针)
    145 
    146 
    147         //限制X轴的移动在-60度到60度之间
    148         if (m_cameraRotation.x >= 60)
    149         {
    150             m_cameraRotation.x = 60;
    151         }
    152         if (m_cameraRotation.x <= -60)
    153         {
    154             m_cameraRotation.x = -60;
    155         }
    156         m_cameraTransform.eulerAngles = m_cameraRotation;
    157 
    158         //使主角的面向方向与摄像机一致  用Vector3定义一个中间变量是因为 eularAngles 无法直接作为变量
    159         Vector3 camrot = m_cameraTransform.eulerAngles;
    160         //初始化摄像机的欧拉角为0
    161         camrot.x = 0;
    162         camrot.z = 0;
    163         //把摄像机的欧拉角 赋给 主角
    164         m_transform.eulerAngles = camrot;
    165 
    166         //使摄像机的位置与主角一致  用Vector3定义一个中间变量是因为 position 无法直接作为变量
    167         Vector3 pos = m_transform.position;
    168         //摄像机的Y轴位置 为 主角的Y轴位置加上摄像机的高度
    169         pos.y += m_cameraHeight;
    170         //把主角的位置 赋给 摄像机的位置
    171         m_cameraTransform.position = pos;
    172 
    173     }
    174 
    175 
    176     //定义玩家的Jump函数
    177     void Jump()
    178     {
    179         //当玩家在地面上的时候  玩家的i跳才有效果
    180         if (m_ch.isGrounded)
    181         {
    182             //此时玩家的重力为10
    183             m_gravity = 10;
    184             //如果按下 space键 玩家的重力变为负数  实现向上运动
    185             if (Input.GetKey(KeyCode.Space))
    186             {
    187                 m_gravity = -8;
    188             }
    189         }
    190         //此时玩家跳了起来
    191         else
    192         {
    193             //玩家的重力 为 玩家的重力10 乘以 每帧的时间
    194             m_gravity +=10f*Time.deltaTime;
    195             //如果玩家的重力大于10的话 让他等于10
    196             if (m_gravity>=10)
    197             {
    198                 m_gravity = 10f;
    199             }
    200         }
    201     
    202     }


    5)给敌人写自动追踪脚本并赋给敌人 :

     1 public class Enemy : MonoBehaviour
     2 {
     3 
     4     //定义敌人的Transform
     5     Transform m_transform;
     6     //CharacterController m_ch;
     7 
     8     //定义动画组件
     9     Animator m_animator;
    10 
    11     //定义寻路组件
    12     NavMeshAgent m_agent;
    13 
    14     //定义一个主角类的对象
    15     PlayerControl m_player;
    16     //角色移动速度
    17     float m_moveSpeed = 0.5f;
    18     //角色旋转速度
    19     float m_rotSpeed = 120;
    20     //定义生命值
    21     int m_life = 15;
    22 
    23     //定义计时器 
    24     float m_timer = 2;
    25     //定义生成点
    26     //protected EnemySpawn m_spawn;
    27 
    28 
    29     // Use this for initialization
    30     void Start()
    31     {
    32         //初始化m_transform 为物体本身的tranform
    33         m_transform = this.transform;
    34 
    35         //初始化动画m_ani 为物体的动画组件
    36         m_animator = this.GetComponent<Animator>();
    37 
    38         //初始化寻路组件m_agent 为物体的寻路组件
    39         m_agent = GetComponent<NavMeshAgent>();
    40 
    41         //初始化主角
    42         m_player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerControl>();
    43 
    44 
    45     }
    46 
    47     // Update is called once per frame
    48     void Update()
    49     {
    50         //设置敌人的寻路目标
    51         m_agent.SetDestination(m_player.m_transform.position);
    52 
    53         //调用寻路函数实现寻路移动
    54         MoveTo();        
    55 
    56         
    57     }
    58 
    59 
    60     //敌人的自动寻路函数
    61     void MoveTo()
    62     {
    63         //定义敌人的移动量
    64         float speed = m_moveSpeed * Time.deltaTime;
    65 
    66         //通过寻路组件的Move()方法实现寻路移动
    67         m_agent.Move(m_transform.TransformDirection(new Vector3(0, 0, speed)));
    68     }
    69 
    70 
    71 
    72 }


    这时,运行游戏,敌人就能自动跟随了.

     
     
    (二)敌人动画的逻辑实现
     
    1)首先,在Project面板里面create一个Animator Controller   双击它 就会发现多了一个BaseLayer面板  如下面第一个图  这个是用来控制动画的逻辑关系的    在敌人模型的动画分类里 如下图中间  选择自己需要的动画  然后拖到BaseLayer面板里面  右键标签可以创建箭头,这里为了便于讲解,选了四个动画(idle空闲  run奔跑  attack攻击   death死亡)  按照下面右图把动画标签的关系调节好.
     
               
     
    2)给敌人添加动画播放脚本  这个脚本与上面的敌人脚本不同 注释的很清楚 很容易理解
      1 public class Enemy : MonoBehaviour
      2 {
      3 
      4     //定义敌人的Transform
      5     Transform m_transform;
      6     //CharacterController m_ch;
      7 
      8     //定义动画组件
      9     Animator m_animator;
     10 
     11     //定义寻路组件
     12     NavMeshAgent m_agent;
     13 
     14     //定义一个主角类的对象
     15     PlayerControl m_player;
     16     //角色移动速度
     17     float m_moveSpeed = 0.5f;
     18     //角色旋转速度
     19     float m_rotSpeed = 120;
     20     //定义生命值
     21     int m_life = 15;
     22 
     23     //定义计时器 
     24     float m_timer = 2;
     25     //定义生成点
     26     //protected EnemySpawn m_spawn;
     27 
     28 
     29     // Use this for initialization
     30     void Start()
     31     {
     32         //初始化m_transform 为物体本身的tranform
     33         m_transform = this.transform;
     34 
     35         //初始化动画m_ani 为物体的动画组件
     36         m_animator = this.GetComponent<Animator>();
     37 
     38         //初始化寻路组件m_agent 为物体的寻路组件
     39         m_agent = GetComponent<NavMeshAgent>();
     40 
     41         //初始化主角
     42         m_player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerControl>();
     43 
     44 
     45     }
     46 
     47     // Update is called once per frame
     48     void Update()
     49     {
     50         ////设置敌人的寻路目标
     51         //m_agent.SetDestination(m_player.m_transform.position);
     52 
     53         ////调用寻路函数实现寻路移动
     54         //MoveTo();        
     55 
     56         //敌人动画的播放与转换
     57         //如果玩家的生命值小于等于0时,什么都不做 (主角死后 敌人无需再有动作)
     58         if (m_player.m_life <= 0)
     59         {
     60             return;
     61         }
     62 
     63         //获取当前动画状态(Idle Run Attack Death 中的一种)
     64         AnimatorStateInfo stateInfo = m_animator.GetCurrentAnimatorStateInfo(0);
     65 
     66         //Idle   如果角色在等待状态条 并且 没有处于转换状态  (0代表的是Base Layer)
     67         if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Idle") && !m_animator.IsInTransition(0))
     68         {
     69             //此时把Idle状态设为false  (此时把状态设置为false 一方面Unity 动画设置里面has exit time已经取消  另一方面为了避免和后面的动画冲突 )
     70             m_animator.SetBool("Idle", false);
     71 
     72             //待机一定时间后(Timer)  之所以有这个Timer 是因为在动画播放期间 无需对下面的语句进行判断(判断也没有用) 从而起到优化的作用
     73             m_timer -= Time.deltaTime;
     74 
     75             //如果计时器Timer大于0  返回 (什么也不干,作用是优化 优化 优化)
     76             if (m_timer > 0)
     77             {
     78                 return;
     79             }
     80 
     81             //如果距离主角小于3米 把攻击动画的Bool值设为true  (激活指向Attack的通道)
     82             if (Vector3.Distance(m_transform.position, m_player.m_transform.position) < 3f)
     83             {
     84                 m_animator.SetBool("Attack", true);
     85             }
     86             //如果距离主角不小于3米        
     87             else
     88             {
     89                 //那么把计时器重置为1
     90                 m_timer = 1;
     91                 //重新获取自动寻路的位置
     92                 m_agent.SetDestination(m_player.m_transform.position);
     93                 //激活指向Run的通道
     94                 m_animator.SetBool("Run", true);
     95             }
     96         }
     97 
     98 
     99         //Run   如果角色指向奔跑状态条  并且  没有处于转换状态  (0代表的是Base Layer)
    100         if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Run") && !m_animator.IsInTransition(0))
    101         {
    102             //关闭指向Run的通道
    103             m_animator.SetBool("Run", false);
    104             //计时器时间随帧减少
    105             m_timer -= Time.deltaTime;
    106             //计时器时间小于0时 重新获取自动寻路的位置  重置计时器时间为1
    107             if (m_timer < 0)
    108             {
    109                 m_agent.SetDestination(m_player.m_transform.position);
    110                 m_timer = 1;
    111             }
    112 
    113             //调用跟随函数
    114             MoveTo();
    115 
    116             //当角色与主角的距离小于等于3米时  
    117             if (Vector3.Distance(m_transform.position, m_player.m_transform.position) <= 3f)
    118             {
    119                 //清楚当前路径 当路径被清除  代理不会开始寻找新路径直到SetDestination 被调用
    120                 m_agent.ResetPath();
    121                 //激活指向Attack的通道
    122                 m_animator.SetBool("Attack", true);
    123 
    124             }
    125         }
    126 
    127 
    128         //Attack 如果角色指向攻击状态条  并且  没有处于转换状态   (0代表的是Base Layer)
    129         if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Attack") && !m_animator.IsInTransition(0))
    130         {
    131             //调用转向函数
    132             RotationTo();
    133 
    134             //关闭指向Attack的通道
    135             m_animator.SetBool("Attack", false);
    136 
    137             //当播放过一次动画后  normalizedTime 实现状态的归1化(1就是整体和全部)  整数部分是时间状态的已循环数  小数部分是当前循环的百分比进程(0-1)            
    138             if (stateInfo.normalizedTime >= 1.0f)
    139             {
    140                 //激活指向Idle的通道
    141                 m_animator.SetBool("Idle", true);
    142                
    143                 //计时器时间重置为2
    144                 m_timer = 2;
    145 
    146 
    147                 //m_player.OnDamage(1);
    148 
    149             }
    150         }
    151 
    152 
    153         //Death  如果角色指向死亡状态条  并且  没有处于转换状态   (0代表的是Base Layer)
    154         if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Death") && !m_animator.IsInTransition(0))
    155         {
    156             //摧毁这个物体的碰撞体
    157             Destroy(this.GetComponent<Collider>());
    158 
    159             //自动寻路时间被归零  角色不再自动移动
    160             m_agent.speed = 0;
    161 
    162             //死亡动画播放一遍后 角色死亡
    163             if (stateInfo.normalizedTime >= 1.0f)
    164             {
    165                 //OnDeath()
    166             }
    167 
    168 
    169         }
    170     }
    171 
    172 
    173     //敌人的自动寻路函数
    174     void MoveTo()
    175     {
    176         //定义敌人的移动量
    177         float speed = m_moveSpeed * Time.deltaTime;
    178 
    179         //通过寻路组件的Move()方法实现寻路移动
    180         m_agent.Move(m_transform.TransformDirection(new Vector3(0, 0, speed)));
    181     }
    182 
    183 
    184     //敌人转向目标点函数
    185     void RotationTo()
    186     {
    187         //定义当前角度 
    188         Vector3 oldAngle = m_transform.eulerAngles;
    189         //获得面向主角的角度
    190         m_transform.LookAt(m_player.m_transform);
    191 
    192         //定义目标的方向  Y轴方向  也就是敌人左右转动面向玩家
    193         float target = m_transform.eulerAngles.y;
    194         //转向目标的速度 等于时间乘以旋转角度
    195         float speed = m_rotSpeed * Time.deltaTime;
    196         //通过MoveTowardsAngle() 函数获得转的角度
    197         float angle = Mathf.MoveTowardsAngle(oldAngle.y, target, speed);
    198 
    199         //实现转向
    200         m_transform.eulerAngles = new Vector3(0, angle, 0);
    201     }
    202 
    203 }

    自此,一个会自动寻找主角 并 攻击 而且 有动画 的敌人就做好了

     
     
    ---未完待续---
  • 相关阅读:
    const
    ImportError: No module named google.protobuf
    ImportError: No module named google.protobuf
    多线程同步与单线程异步对比
    多线程同步与单线程异步对比
    再谈select, iocp, epoll,kqueue及各种I/O复用机制
    再谈select, iocp, epoll,kqueue及各种I/O复用机制
    poj1180
    poj3254
    poj3321
  • 原文地址:https://www.cnblogs.com/qiaogaojian/p/5920998.html
Copyright © 2011-2022 走看看