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 }

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

 
 
---未完待续---

 

posted @ 2016-09-29 19:25  乔高建  阅读(27233)  评论(0编辑  收藏  举报