前言:在学习完网格导航之后,我们做一个涉及到网格导航的综合练习,涉及到前面学习的动画以及角色控制器等内容,废话不多说,开始吧
综合练习
1、路面设置障碍
2、鼠标点击路面,角色可以移动过去,同时播放动画,期间有障碍的话会躲过去,到达目的地之后角色会播放Idle的动画
3、WASD也可以控制角色的移动,同时要播放动画,不能穿过障碍,松开按键之后播放Idle的动画
4、按WASD可以终止点击鼠标后角色的自动寻路,按照WASD的控制去移动
提示:
1、射线
2、动画(Animator)
3、
自动寻路
自动寻路
终止寻路
重新开始寻路
场景搭建
创建一个新的场景,命名为“网格导航练习”
-
搭建地形
- 新建一个Plane
- Transform组件点击右上角的小齿轮>>Reset
- Scale改为(5,1,5)
-
给它一个材质
-
再克隆4个Plane,调整合适的Position
-
设置障碍物
- 创建一个Cube,Transform组件Reset
- 调整一个合适的Scale
- 添加网格导航障碍组件(网格导航代理不能移动到此位置)
-
选中Cube,Add Component>>Navigation>>Nav Mesh Obstacle
-
-
再克隆3个Cube,调整合适的Position
-
模型设置
- 把模型拖入场景
-
选中模型的导入设置文件,Model>>调整合适的Scale Factor(缩放因子)
-
点击Apply
烘焙网格导航路径
-
选中所有地形对象,在编辑器的菜单栏找到Window>>Navigation
-
Navigation面板>>Object>>Navigation Static勾选
-
Bake>>调整合适的值>>点击Bake
添加动画
- 创建Avatar(映射关系)
- 选中模型,Rig>>Animation Type:Humanoid,Avatar Definition:Create From This Model
- 点击Apply
- 给所有动画剪辑添加Avatar
- 选中需要添加Avatar的动画剪辑
- Rig>>Animation Type:Humanoid,Avatar Definition:Copy From Other Avatar
- Source:刚刚创建的模型的Avatar
- 点击Apply
-
添加动画控制器
- 双击打开,拖入Idle(原地不动)和Walk(走)的动画剪辑
-
Idle设为默认过渡线,从Idle设置过渡线到Walk,从Walk设置过渡线到Idle
-
Parameters>>添加>>Bool,重命名为“IsWalk”
-
- 设置过渡条件
-
选中Idle到Walk的过渡线,点击加号(添加过渡条件);找到IsWalk(参数变量名字),将值设为true;Has Exit Time(是否等待上一个动画剪辑播放完毕,再去播放下一个动画剪辑)不勾选
- 同理:选中Walk到Idle的过渡线,点击加号;找到IsWalk,将值设为false;Has Exit Time不勾选
-
- 设置循环
-
分别选中Idle和Walk的导入文件,将Loop Time勾选,这样动画剪辑就是循环播放
-
- 双击打开,拖入Idle(原地不动)和Walk(走)的动画剪辑
-
选中模型,Animator组件>>Controller:动画控制器
-
取消应用根动作(不然动画剪辑自身也会有一个偏移量,会造成模型和角色控制器位置的不匹配)
添加一个空物体
- 重命名为“Player”
- 把Transform组件Reset
- 把模型设为Player的子物体,Transform组件Reset
添加网格导航代理件
- 选中Player,Add Component>>Navigation>>Nav Mesh Agent
-
调整半径和高度,让其跟模型大小匹配
添加角色控制器
- 选中Player,Add Component>>Physics>>Character Controller
- 调整半径和高度,让其和网格导航代理相同
-
调整Center的位置,让其和模型匹配
-
圆柱体的是网格导航代理,胶囊体的是角色控制器
添加脚本
- 选中Player,Add Component>>New Script
- Name:PlayerMoveController
-
Create and Add
脚本PlayerMoveController代码:
- 这里只需要关注通过WSAD键控制Player移动以及通过鼠标点击地面控制Player移动即可
- 其他代码有可能需要进行相应处理,新手的话建议先不看
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;//人工智能的命名空间
public class PlayerMoveController : MonoBehaviour {
Vector3 Vec_Dir;
float hor;
float ver;
public float MoveSpeed;
public float RotaSpeed;
public float JumpSpeed;
public float MouseScrollSpeed;
float G=0.5F;
//角色控制器
CharacterController CC;
//动画控制器
Animator m_Anima;
//网格导航代理
private NavMeshAgent mNMA;
//用于得到射线检测信息
RaycastHit hit;
bool mIsWalk;
bool mIsRun;
Vector3 pos_mouse;
float fov;
Vector3 pos_cam;
//public Camera cam;
void Start () {
//得到自身的角色控制器
CC= GetComponent<CharacterController>();
//得到子物体的动画控制器
m_Anima = GetComponentInChildren<Animator>();
//cam = GetComponentInChildren<Camera>();
//得到NavMeshAgent组件
mNMA = GetComponent<NavMeshAgent>();
}
//判断角色控制器是否在地面上
public bool IsGround()
{
//创建从角色控制器中心点向下的一条射线
Ray ray = new Ray(CC.center,-Vector3.up);
//返回射线检测是否碰到物体
return Physics.Raycast(ray,CC.height/2);
}
private void FixedUpdate()
{
//角色控制器在地面上
if (IsGround())
{
//按空格跳
if (Input.GetKeyDown(KeyCode.Space))
{
Vec_Dir.y = JumpSpeed;
//跳的动画
m_Anima.SetTrigger("Jump");
}
}
}
void Update () {
//增量
hor = Input.GetAxis("Horizontal");
ver = Input.GetAxis("Vertical");
#region 鼠标控制移动
//点击鼠标左键
if (Input.GetMouseButtonDown(0))
{
//得到鼠标位置
pos_mouse = Input.mousePosition;
//调用方法:玩家跟随鼠标位置
PlayWithMousePoint(pos_mouse);
}
#endregion
//运行时会自动判断,将标志位设为false;
//使用同一个变量的时候一定要注意先后顺序
#region 通过导航管理的期望速度控制动画标志位
if (mNMA.desiredVelocity != Vector3.zero)
{
mIsWalk = true;
}
else
{
//刚进入游戏时
mIsWalk = false;
}
#endregion
#region 通过WSAD、Shift控制动画标志位以及导航
//按下WSAD
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D))
{
mIsWalk = true;
//关闭导航
mNMA.isStopped = true;
}
//松开WSAD
if (Input.GetKeyUp(KeyCode.W) || Input.GetKeyUp(KeyCode.S) || Input.GetKeyUp(KeyCode.A) || Input.GetKeyUp(KeyCode.D))
{
mIsWalk = false;
}
//按下Shift,速度乘以2
if (Input.GetKey(KeyCode.LeftShift))
{
Vec_Dir *= 2;
mIsRun = true;
}
//松开Shift
if (Input.GetKeyUp(KeyCode.LeftShift))
{
mIsRun = false;
}
#endregion
#region 通过Transform移动
//移动,转动
//transform.Translate(0, 0, ver * MoveSpeed * Time.deltaTime);
//transform.Rotate(0, hor * RotaSpeed * Time.deltaTime, 0);
#endregion
#region 通过角色控制器移动
//移动
//世界坐标
Vec_Dir = new Vector3(0, 0, ver*MoveSpeed*Time.deltaTime);
//转化成自身坐标
Vec_Dir = transform.TransformDirection(Vec_Dir);
//重力
Vec_Dir.y -= G;
CC.Move(Vec_Dir);
//转动
transform.Rotate(0, hor * RotaSpeed * Time.deltaTime, 0);
#endregion
#region ZXCV技能
//ZXCV技能
if (Input.GetKeyDown(KeyCode.Z))
{
m_Anima.SetTrigger("Q");
}
if (Input.GetKeyDown(KeyCode.X))
{
m_Anima.SetTrigger("W");
}
if (Input.GetKeyDown(KeyCode.C))
{
m_Anima.SetTrigger("E");
}
if (Input.GetKeyDown(KeyCode.V))
{
m_Anima.SetTrigger("R");
}
#endregion
//播放走、跑的动画
m_Anima.SetBool("IsWalk",mIsWalk);
//我在状态机中没有写参数IsRun,如果你写了可以把注释取消掉
//m_Anima.SetBool("IsRun",mIsRun);
}
private void LateUpdate()
{
//鼠标滑轮调整摄像机远近
fov = Camera.main.fieldOfView;
fov+= Input.GetAxis("Mouse ScrollWheel")*MouseScrollSpeed;
fov = Mathf.Clamp(fov,0.3f,1000);
Camera.main.fieldOfView = fov;
//pos_mouse= Camera.main.ScreenToViewportPoint(pos_mouse);
//pos_mouse= Camera.main.ViewportToWorldPoint(pos_mouse);
//限制摄像机位置范围
pos_cam = Camera.main.transform.position;
pos_cam.x= Mathf.Clamp(pos_cam.x, -100, 100);
pos_cam.y = Mathf.Clamp(pos_cam.y,-100,100);
Camera.main.transform.position = pos_cam;
//到屏幕边缘移动摄像机
if (pos_mouse.x <= 0) pos_cam.x--;
if (pos_mouse.x >= Screen.width) pos_cam.x++;
if (pos_mouse.y <= 0) pos_cam.y--;
if (pos_mouse.y >= Screen.height) pos_cam.y++;
}
/// <summary>
/// 玩家跟随鼠标移动
/// </summary>
/// <param name="pos_mouse">鼠标位置</param>
void PlayWithMousePoint(Vector3 pos_mouse)
{
//将鼠标点击位置转换成射线
Ray ray = Camera.main.ScreenPointToRay(pos_mouse);
//如果射线碰到了游戏对象
if (Physics.Raycast(ray, out hit))
{
//打开导航
mNMA.isStopped = false;
//将碰到的游戏对象的位置
//赋给 网格导航代理的终点位置
mNMA.destination = hit.point;
}
}
}
设置脚本参数
-
将脚本参数中的移动速度和角速度设置和网格导航代理中的移动速度和角速度一样