在这个实例中,我们要做一些敌人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) 然后给主角写 移动控制脚本 和 镜头控制脚本  并赋给主角(胶囊体):
 
 public class PlayerControl : MonoBehaviour
{ //定义玩家的Transform
public Transform m_transform;
//定义玩家的角色控制器
CharacterController m_ch;
//定义玩家的移动速度
float m_movespeed = 10.0f;
//定义玩家的重力
float m_gravity = 2.0f;
//定义玩家的生命
public int m_life = ; //定义摄像机的Transform
Transform m_cameraTransform;
//定义摄像机的旋转角度
Vector3 m_cameraRotation;
//定义摄像机的高度
float m_cameraHeight = 1.4f;
//定义小地图摄像机
public Transform m_miniMap; //定义枪口的Transform m_muzzlepPoint;
Transform m_muzzlePoint;
//定义射击时,射线射到的碰撞层
public LayerMask m_layer;
//定义射中目标后粒子效果的Transform
public Transform m_fx;
//定义射击音效
public AudioClip m_shootAudio;
//定义射击间隔时间计时器
float m_shootTimer = ; // Use this for initialization
void Start()
{
//获取玩家本身的Transform 赋给 m_transform
m_transform = this.transform;
//获取玩家本身的CharacterController组件 赋给 m_ch
m_ch = this.GetComponent<CharacterController>(); //摄像机的控制的初始化
//获取摄像机的Transform
m_cameraTransform = Camera.main.transform;
//定义一个三维向量用来表示摄像机位置 并把玩家的位置赋给它 设置摄像机初始位置
Vector3 pos = m_transform.position;
//摄像机的Y轴坐标 为 本来的坐标加上上面定义的摄像机高度
pos.y += m_cameraHeight;
//把修改后的摄像机坐标重新赋给m_cameraTransform
m_cameraTransform.position = pos;
//把主角的旋转角度 赋给 摄像机的旋转角度
m_cameraTransform.rotation = m_transform.rotation;
//获取摄像机的角度
m_cameraRotation = m_transform.eulerAngles; //隐藏鼠标
Cursor.visible = false;
} // Update is called once per frame
void Update()
{
//如果玩家的生命小于等于0 什么也不做
if (m_life <= )
{
return;
} //如果玩家的生命大于0 那么调用玩家控制函数
//移动函数
MoveControl();
//摄像机控制函数
CameraControl();
//跳跃函数
Jump();
} //定义玩家的控制函数
void MoveControl()
{ //定义玩家在XYZ轴上的移动量
float xm = , ym = , zm = ; //玩家的重力运动 为 减等于玩家的重力乘以每帧时间
ym -= m_gravity * Time.deltaTime; //实现玩家上下左右的运动
//如果按下 W键 玩家在Z轴上的量增加
if (Input.GetKey(KeyCode.W))
{
zm += m_movespeed * Time.deltaTime;
}
//如果按下 S键 玩家在Z轴上的量减少 这里用else if是因为每帧只能按下相反方向的一个键
else if (Input.GetKey(KeyCode.S))
{
zm -= m_movespeed * Time.deltaTime;
}
//如果按下 A键 玩家在X轴上的量减少
if (Input.GetKey(KeyCode.A))
{
xm -= m_movespeed * Time.deltaTime;
}
//如果按下 D键 玩家在X轴上的量增加
else if (Input.GetKey(KeyCode.D))
{
xm += m_movespeed * Time.deltaTime;
} ////当玩家在地面上的时候 才能前后左右移动 在空中不能移动
if (!m_ch.isGrounded)
{
xm = ;
zm = ;
} //通过角色控制器的Move()函数,实现移动
m_ch.Move(m_transform.TransformDirection(new Vector3(xm, ym, zm))); } //定义玩家的摄像机控制函数
void CameraControl()
{ //实现对摄像机的控制
//定义主角在horizon方向X轴移动的量 也就是获取主角鼠标移动的量
float rh = Input.GetAxis("Mouse X");
//定义主角在Vertical 方向Y轴移动的量
float rv = Input.GetAxis("Mouse Y"); //旋转摄像机
//把鼠标在屏幕上移动的量转化为摄像机的角度 rv(上下移动的量) 等于 角色X轴的角度 rh(水平移动的量) 等于 角色Y轴上的角度
m_cameraRotation.x -= rv;
//Debug.Log(rv); 向下时 rv 为正值(顺时针) 向上时 rv 为负值(逆时针)
m_cameraRotation.y += rh;
//Debug.Log(rh); 向右时 rh 为正值(顺时针) 向左时 rh 为负值(逆时针) //限制X轴的移动在-60度到60度之间
if (m_cameraRotation.x >= )
{
m_cameraRotation.x = ;
}
if (m_cameraRotation.x <= -)
{
m_cameraRotation.x = -;
}
m_cameraTransform.eulerAngles = m_cameraRotation; //使主角的面向方向与摄像机一致 用Vector3定义一个中间变量是因为 eularAngles 无法直接作为变量
Vector3 camrot = m_cameraTransform.eulerAngles;
//初始化摄像机的欧拉角为0
camrot.x = ;
camrot.z = ;
//把摄像机的欧拉角 赋给 主角
m_transform.eulerAngles = camrot; //使摄像机的位置与主角一致 用Vector3定义一个中间变量是因为 position 无法直接作为变量
Vector3 pos = m_transform.position;
//摄像机的Y轴位置 为 主角的Y轴位置加上摄像机的高度
pos.y += m_cameraHeight;
//把主角的位置 赋给 摄像机的位置
m_cameraTransform.position = pos; } //定义玩家的Jump函数
void Jump()
{
//当玩家在地面上的时候 玩家的i跳才有效果
if (m_ch.isGrounded)
{
//此时玩家的重力为10
m_gravity = ;
//如果按下 space键 玩家的重力变为负数 实现向上运动
if (Input.GetKey(KeyCode.Space))
{
m_gravity = -;
}
}
//此时玩家跳了起来
else
{
//玩家的重力 为 玩家的重力10 乘以 每帧的时间
m_gravity +=10f*Time.deltaTime;
//如果玩家的重力大于10的话 让他等于10
if (m_gravity>=)
{
m_gravity = 10f;
}
} }

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

 public class Enemy : MonoBehaviour
{ //定义敌人的Transform
Transform m_transform;
//CharacterController m_ch; //定义动画组件
Animator m_animator; //定义寻路组件
NavMeshAgent m_agent; //定义一个主角类的对象
PlayerControl m_player;
//角色移动速度
float m_moveSpeed = 0.5f;
//角色旋转速度
float m_rotSpeed = ;
//定义生命值
int m_life = ; //定义计时器
float m_timer = ;
//定义生成点
//protected EnemySpawn m_spawn; // Use this for initialization
void Start()
{
//初始化m_transform 为物体本身的tranform
m_transform = this.transform; //初始化动画m_ani 为物体的动画组件
m_animator = this.GetComponent<Animator>(); //初始化寻路组件m_agent 为物体的寻路组件
m_agent = GetComponent<NavMeshAgent>(); //初始化主角
m_player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerControl>(); } // Update is called once per frame
void Update()
{
//设置敌人的寻路目标
m_agent.SetDestination(m_player.m_transform.position); //调用寻路函数实现寻路移动
MoveTo(); } //敌人的自动寻路函数
void MoveTo()
{
//定义敌人的移动量
float speed = m_moveSpeed * Time.deltaTime; //通过寻路组件的Move()方法实现寻路移动
m_agent.Move(m_transform.TransformDirection(new Vector3(, , speed)));
} }

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

 
 
(二)敌人动画的逻辑实现
 
1)首先,在Project面板里面create一个Animator Controller   双击它 就会发现多了一个BaseLayer面板  如下面第一个图  这个是用来控制动画的逻辑关系的    在敌人模型的动画分类里 如下图中间  选择自己需要的动画  然后拖到BaseLayer面板里面  右键标签可以创建箭头,这里为了便于讲解,选了四个动画(idle空闲  run奔跑  attack攻击   death死亡)  按照下面右图把动画标签的关系调节好.
 
           
 
2)给敌人添加动画播放脚本  这个脚本与上面的敌人脚本不同 注释的很清楚 很容易理解
 public class Enemy : MonoBehaviour
{ //定义敌人的Transform
Transform m_transform;
//CharacterController m_ch; //定义动画组件
Animator m_animator; //定义寻路组件
NavMeshAgent m_agent; //定义一个主角类的对象
PlayerControl m_player;
//角色移动速度
float m_moveSpeed = 0.5f;
//角色旋转速度
float m_rotSpeed = ;
//定义生命值
int m_life = ; //定义计时器
float m_timer = ;
//定义生成点
//protected EnemySpawn m_spawn; // Use this for initialization
void Start()
{
//初始化m_transform 为物体本身的tranform
m_transform = this.transform; //初始化动画m_ani 为物体的动画组件
m_animator = this.GetComponent<Animator>(); //初始化寻路组件m_agent 为物体的寻路组件
m_agent = GetComponent<NavMeshAgent>(); //初始化主角
m_player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerControl>(); } // Update is called once per frame
void Update()
{
////设置敌人的寻路目标
//m_agent.SetDestination(m_player.m_transform.position); ////调用寻路函数实现寻路移动
//MoveTo(); //敌人动画的播放与转换
//如果玩家的生命值小于等于0时,什么都不做 (主角死后 敌人无需再有动作)
if (m_player.m_life <= )
{
return;
} //获取当前动画状态(Idle Run Attack Death 中的一种)
AnimatorStateInfo stateInfo = m_animator.GetCurrentAnimatorStateInfo(); //Idle 如果角色在等待状态条 并且 没有处于转换状态 (0代表的是Base Layer)
if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Idle") && !m_animator.IsInTransition())
{
//此时把Idle状态设为false (此时把状态设置为false 一方面Unity 动画设置里面has exit time已经取消 另一方面为了避免和后面的动画冲突 )
m_animator.SetBool("Idle", false); //待机一定时间后(Timer) 之所以有这个Timer 是因为在动画播放期间 无需对下面的语句进行判断(判断也没有用) 从而起到优化的作用
m_timer -= Time.deltaTime; //如果计时器Timer大于0 返回 (什么也不干,作用是优化 优化 优化)
if (m_timer > )
{
return;
} //如果距离主角小于3米 把攻击动画的Bool值设为true (激活指向Attack的通道)
if (Vector3.Distance(m_transform.position, m_player.m_transform.position) < 3f)
{
m_animator.SetBool("Attack", true);
}
//如果距离主角不小于3米
else
{
//那么把计时器重置为1
m_timer = ;
//重新获取自动寻路的位置
m_agent.SetDestination(m_player.m_transform.position);
//激活指向Run的通道
m_animator.SetBool("Run", true);
}
} //Run 如果角色指向奔跑状态条 并且 没有处于转换状态 (0代表的是Base Layer)
if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Run") && !m_animator.IsInTransition())
{
//关闭指向Run的通道
m_animator.SetBool("Run", false);
//计时器时间随帧减少
m_timer -= Time.deltaTime;
//计时器时间小于0时 重新获取自动寻路的位置 重置计时器时间为1
if (m_timer < )
{
m_agent.SetDestination(m_player.m_transform.position);
m_timer = ;
} //调用跟随函数
MoveTo(); //当角色与主角的距离小于等于3米时
if (Vector3.Distance(m_transform.position, m_player.m_transform.position) <= 3f)
{
//清楚当前路径 当路径被清除 代理不会开始寻找新路径直到SetDestination 被调用
m_agent.ResetPath();
//激活指向Attack的通道
m_animator.SetBool("Attack", true); }
} //Attack 如果角色指向攻击状态条 并且 没有处于转换状态 (0代表的是Base Layer)
if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Attack") && !m_animator.IsInTransition())
{
//调用转向函数
RotationTo(); //关闭指向Attack的通道
m_animator.SetBool("Attack", false); //当播放过一次动画后 normalizedTime 实现状态的归1化(1就是整体和全部) 整数部分是时间状态的已循环数 小数部分是当前循环的百分比进程(0-1)
if (stateInfo.normalizedTime >= 1.0f)
{
//激活指向Idle的通道
m_animator.SetBool("Idle", true); //计时器时间重置为2
m_timer = ; //m_player.OnDamage(1); }
} //Death 如果角色指向死亡状态条 并且 没有处于转换状态 (0代表的是Base Layer)
if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Death") && !m_animator.IsInTransition())
{
//摧毁这个物体的碰撞体
Destroy(this.GetComponent<Collider>()); //自动寻路时间被归零 角色不再自动移动
m_agent.speed = ; //死亡动画播放一遍后 角色死亡
if (stateInfo.normalizedTime >= 1.0f)
{
//OnDeath()
} }
} //敌人的自动寻路函数
void MoveTo()
{
//定义敌人的移动量
float speed = m_moveSpeed * Time.deltaTime; //通过寻路组件的Move()方法实现寻路移动
m_agent.Move(m_transform.TransformDirection(new Vector3(, , speed)));
} //敌人转向目标点函数
void RotationTo()
{
//定义当前角度
Vector3 oldAngle = m_transform.eulerAngles;
//获得面向主角的角度
m_transform.LookAt(m_player.m_transform); //定义目标的方向 Y轴方向 也就是敌人左右转动面向玩家
float target = m_transform.eulerAngles.y;
//转向目标的速度 等于时间乘以旋转角度
float speed = m_rotSpeed * Time.deltaTime;
//通过MoveTowardsAngle() 函数获得转的角度
float angle = Mathf.MoveTowardsAngle(oldAngle.y, target, speed); //实现转向
m_transform.eulerAngles = new Vector3(, angle, );
} }

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

 
 
---未完待续---

Unity3D 敌人AI 和 动画( Animator )系统的实例讲解的更多相关文章

  1. 时光煮雨 Unity3D实现2D人物动画② Unity2D 动画系统&资源效率

    系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...

  2. “分而治之”,一种AI和动画系统的架构

    译者注:随着国内游戏研发水平的不断提高,对画面品质的不断提升,同时大量手游使用Unity和Unreal 4等成熟的工具开发,动作状态机已经不是什么陌生的概念了.我们在项目开发时也大量使用了动作状态机. ...

  3. 时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画

    系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...

  4. 【转】利用Behavior Designer制作敌人AI

    http://www.unity.5helpyou.com/3112.html 本篇unity3d教程,我们来学习下利用Behavior Designer行为树插件来制作敌人AI,下面开始! Beha ...

  5. Unity3D 创建动态的立方体图系统

    Unity3D 创建动态的立方体图系统 这一篇主要是利用上一篇的Shader,通过脚本来完成一个动态的立方体图变化系统. 准备工作如下: 创建一个新的场景.一个球体.提供给场景一个平行光,准备2个立方 ...

  6. Unity3D ShaderLab 模拟精灵动画

    Unity3D ShaderLab 模拟精灵动画 在上一篇,介绍了通过Shader 模拟纹理运动,那么更深一步讲,我们也可以把帧动画的精灵纹理运动通过shader实现. 虽然大家都是在游戏脚本中做更高 ...

  7. 实例讲解Linux系统中硬链接与软链接的创建

    导读 Linux链接分两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link).默认情况下,ln命令产生硬链接.硬链接与软链接的区别从根本上要从Inode节点说 ...

  8. [Unity3D]Unity4全新的动画系统Mecanim

    Unity4.X添加一个新的动画系统,以取代原有的3.X旧的动画系统,全新的动画系统Mecanim是官方推荐,它使我们能够写更少的代码实现连续动画. 效果图 Unity3.X中动画系统播放动画 使用播 ...

  9. Unity 动画系统 Animation 和 Animator的小实例

    本文结合一个很简单的动画demo,分别采用2种方法,来对比Animation和Animator的使用方式: 方法1:单独使用Animation 方法2:Animation结合Animator 动画De ...

随机推荐

  1. 今日例子border

    border这个属性在页面上的使用率还是很高,例如我们需要理解的盒模型就需要对border有个 比较深的理解,如果你会盒模型,但对border没有深的理解,那只能说你只是知道盒模型,而 不是懂得盒模型 ...

  2. ftp如何预览图片 解决方案

    下载使用 server-U ,开启 HTTP 服务,输入 http://ip:端口 后,登录ftp账号密码,可选使用 基于java的应用 web client 或 FTP Voyager JV,来预览 ...

  3. IOS适配

    • Default.png(图片尺寸为320x480):显示在非Retina-3.5英寸屏幕上(iPhone3G\iPhone3GS,屏幕分辨率为320x480) • Default@2x.png(图 ...

  4. Drupal7_2:安装drupal

    Drupal7_2:安装drupal 分类: Drupal72012-10-30 01:06 1074人阅读 评论(0) 收藏 举报 假设你已经搭建好了所需的必备环境,接下来就参照以下几步,快速安装一 ...

  5. Codeforces Round #384 (Div. 2)D - Chloe and pleasant prizes 树形dp

    D - Chloe and pleasant prizes 链接 http://codeforces.com/contest/743/problem/D 题面 Generous sponsors of ...

  6. nodejs 的安全

    1.connect中间件csrf 原理:在express框架中csrf 是通过connect 模块的中间件来解决的.其原理是在前端构造一个隐藏的表单域“_csrf” ,后端生成一个值,作为该表单域,然 ...

  7. eclipse 代码提示时闪退问题

    解决办法:在eclipse.ini里面最下面加上这句话 -Dorg.eclipse.swt.browser.DefaultType=mozilla

  8. 【Cocos2d-Js基础教学(2)类的使用和面向对象】

    类的使用和面向对象 大家都知道在cocos2d-x 底层是C++编写的,那么就有类的概念和继承机制. 但是在JS中,是没有类这个概念的,没有提供类,没有C++的类继承机制. 那么JS是通过什么方式实现 ...

  9. 老鼠跑猫叫主人惊醒c++观察者模式实现

    这个题目算是比较经典的观察者模式了,老鼠作为一个Subject,主动发出跑的动作,紧跟着猫由于老鼠的跑而发出叫声,主人也被惊醒,在这里猫跟主人都是被动的,是观察者角色,代码实现如下: class CS ...

  10. [转]大型 JavaScript 应用架构中的模式

    目录 1.我是谁,以及我为什么写这个主题 2.可以用140个字概述这篇文章吗? 3.究竟什么是“大型”JavaScript应用程序? 4.让我们回顾一下当前的架构 5.想得长远一些 6.头脑风暴 7. ...