Unity
Unity是一个游戏引擎,而游戏引擎就是专门做游戏的软件,它提供了很多现成的功能供我们使用,让开发游戏事半功倍。
学习游戏引擎,我们需要:
- 学习引擎用于开发的主要语言(如C#)
- 学习引擎的软件操作
- 学习引|擎提供的API和核心系统
入门
在入门篇,我们将学习Unity界面相关、工作原理、脚本基础和一些基础的组件及API、核心系统*。
基础界面
在介绍基础界面前,我们先来学习一个窗口布局的方法:
在Inspector上方的lay out选项中,可以选择布局方式。其中2 by 3是常用的布局方式,最下方的Reset All Layouts是恢复默认布局的方法。
接下来介绍几个常用的窗口界面。
窗口
Hierarchy层级
我们可以在Hierarchy窗口中创建或拖入各种游戏对象。其中,越在下方的物体越后渲染,显示的位置在更上层。
Scene场景
我们可以在Scene窗口中查看和设置所有游戏对象。
其中,点击场景轴向中的中间方块可以切换正交模式和透视模式。
Scene窗口侧边的工具栏快捷键依次对应:Q、W、E、R、T、Y
在移动时,按住Ctrl键可以一个小单位的移动,点击辅助线显示旁边的磁铁键后可以一个大单位的移动。
Scene窗口的代码相关如下:
首先要引用命名空间:
1 | using UnityEngine.SceneManagement; |
- 加载场景
1 | SceneManager.LoadScene("场景名字"); |
这样加载场景有一个缺点:Unity会先删除当前场景上的所有对象,再去加载下一个场景的相关信息,这样会很卡顿。
因此,我们可以通过场景异步加载来解决这个问题:
异步加载场景有两个方法。
- 通过事件回调函数异步加载
1 | AsyncOperation ao = SceneManager.LoadSceneAsync(""); |
- 通过协程异步加载
1 | void Start() |
其中,使用DontDestroyOnLoad()这个方法是因为加载场景时会把当前场景上没有特别处理的对象都删除,所以为了不影响协程中逻辑的实现我们不删除它。
另外,由于异步加载场景,我们还可以在这期间实现一些其他逻辑,比如,更新进度条。
- 得到当前所在的场景
1 | SceneManager.GetActiveScene(); |
Game游戏
Game游戏窗口就是玩家能看到的画面内容。
Project工程
Project窗口中显示的是所有的游戏资源。
在Project窗口中还能导入导出预制体。操作如下:在Project窗口中右键 -> Import / Export Package。
Inspector检查以及辅助特性
可以在Inspector检查窗口中查看场景中游戏对象关联的C#脚本信息。
另外,Inspector只能显示public类型的变量,如果想在这里面编辑private和protect类型的变量,可以给他们加上强制序列化字段特性:
1 | [] |
不想让公共的显示(字典、自定义类型不行),也可以加上特性:
1 | [] |
还有一种特性可以让自定义类型被访问(字典不行):
1 | [] |
注意: 运行时改变Inspector窗口中的变量在运行结束时不会被保存。如果想保存,可以在运行过程中选择想保存的脚本右上角三个点,点击Copy Component;在停止运行时,点击Paste Component Values。
另外,还可以为变量添加一些辅助特性:
- 分组说明特性:可以为成员分组
1 | [] |
- 悬停注释:可以为变量添加说明
1 | [] |
- 间隔特性:让两个字段间出现间隔
1 | [] |
- 修饰数值的滑条范围
1 | [] |
- 多行显示字符串
1 | // 参数:显示的行数,不写参数默认显示3行 |
- 滚动条显示字符串
1 | // 参数:最小值和最大值,不写参数默认是超过3行显示滚动条 |
- 为变量添加快捷方式
1 | // 参数1:显示按钮名 |
- 能在Inspector窗口中执行
1 | [] |
Console控制台
用于查看调试信息的窗口。报错、警告、测试打印都可以显示在其中。
我们可以在代码中给自己写一些调试信息:
1 | Debug.Log(); // 普通打印 |
工具栏
File
Build Settings打包:
- Scenes In Build:打包的场景。放在第一个的是默认场景
- Platform:切换打包到的平台,切换后按下方的switch按钮
- Player Setting:玩家设置
Edit
- 伪z轴游戏:Project Settings -> Graphics -> Transparency Sort Mode -> Custom Axis(x = 0 , y = 1, z = -0.26)
- 换编辑器:Preferences -> External Tool
Window
- Asset Store:资源商店
- Genera:6大核心窗口
Help
- 看Unity版本:About Unity
工作原理
Unity的工作机制本质就是利用反射动态的创建GameObject对象并且关联各种C#脚本对象在其上。脚本不同呈现的形态也不同。
脚本基础
脚本的创建规则是:类名与文件名必须一致
MonoBehavior 基类
MonoBehavior是Unity引擎提供的核心基类,所有需要挂载到GameObject上的脚本都需要继承这个类。它提供了和 Unity 生命周期、场景、游戏物体交互的所有核心功能。
注意:
- 继承了
Mono的脚本不能new,只能挂载 - 继承了
Mono的脚本不要写构造函数,因为不能new,写了也没有意义
重要成员和方法
重要成员
- 获取依附的游戏对象
1 | this.gameObject; |
还可以通过这样得知这个游戏对象的名字等信息:
1 | this.gameObject.name; |
- 获取依附的游戏对象的位置信息
1 | this.transform.position; // 位置 |
- 控制脚本是否激活
1 | this.enabled = false; |
重要方法
- 得到自己身上挂载的单个脚本
1 | this.GetComponent<Lesson1>(); |
如果获取失败,就是没有对应的脚本,返回null。
这个方法也适用于collision的语法糖,如下:
1 | collision.GetComponent<Lesson1>(); |
- 得到自己身上挂载的多个脚本
1 | Lesson1[] array = this.GetComponent<Lesson1>(); |
- 得到子对象挂载的脚本
这个方法也会默认找自己身上是否挂载该脚本
1 | // 参数:不传默认false,表示如果子对象失活是否找这个对象上的脚本 |
- 得到父对象挂载的脚本
这个方法也会默认找自己身上是否挂载该脚本
1 | this.GetComponentInParent<Lesson1>(); // 单个 |
- 尝试获取脚本(更安全)
1 | if(this.TryGetComponent<Lesson1>()) |
重要内容
延迟函数
延迟函数就是会延时执行的函数,我们可以自己设定延时要执行的函数和具体延时的时间。
- 延迟函数
1 | // 参数1:字符串, 函数名 |
注意:延迟函数没有办法传入参数。如果非要传入,可以包裹一层,像这样:
1 | Invoke("DelayDoSomething", 5); |
- 延迟重复执行函数
1 | // 参数1:字符串,函数名 |
- 取消延迟函数
有两种类型的取消方法,其一是取消该脚本上所有的延时函数执行:
1 | CancelInvoke(); |
还可以指定函数名来进行取消:
1 | CancelInvoke(""); |
- 判断是否有延迟函数
1 | if( IsInvoking("") ) |
值得注意的是:
| 情景 | 延迟函数是否可以继续执行 |
|---|---|
| 脚本依附对象失活或脚本失活 | 可以 |
| 脚本依附对象销毁或脚本移除 | 不可以 |
多线程
Unity支持多线程,只是新开线程无法访问Unity相关对象的内容,它的作用是可以把复杂算法的内容放进多线程中计算。(注意:Unity中的多线程要记住关闭)
协同程序(协程)
协程是一种轻量级的线程。它和线程的最大的区别是:
- 协程:新开一个协程是在原线程上开启,进行逻辑分时分布执行。
- 线程:新开一个线程是独立的一个管道,和主线程并行执行。
协程主要的使用场景有:
- 异步加载文件
- 异步下载文件
- 场景异步加载
- 批量创建时防止卡顿
继承MonoBehavior的类都可以开启协程函数,以下是关于协程的使用:
第一步:声明协程函数
声明协程函数有2个关键点,一是返回值为IEnumerator类型及其子类,二是函数通过 yield return 返回值进行返回。
1 | IEnumerator MyCoroutine(int i, string str) |
第二步:开启协程函数
1 | // 参数:协程函数 |
第三步:关闭协程
关闭协程分为:关闭所有协程和关闭指定协程。关闭所有协程:
1 | StopAllCoroutines(); |
关闭指定协程:
1 | Coroutine c1 = StartCoroutine( MyCoroutine(1, "123") ); |
另外,yield return返回的内容含义如下:
| 含义 | 写法 | 执行时间 |
|---|---|---|
| 暂停一帧, 下一帧执行 | yield return 数字 / null; |
后面的逻辑在 Update 和 LateUpdate 之间执行 |
| 等待指定秒后执行 | yield return new WaitForSeconds(1f); |
后面的逻辑在 Update 和 LateUpdate 之间执行 |
| 等待下一个固定物理帧更新时执行 | yield return new WaitForFixedUpdate(); |
后面的逻辑在 FixedUpdate 和 碰撞检测相关函数 之后执行 |
| 等待摄像机和GUI渲染完成后执行(主要是 截图时来使用) | yield return new WaitForEndOfFrame(); |
后面的逻辑在 LateUpdate 之后的渲染相关处理完毕后执行 |
| 跳出协程 | yield break; |
协程开启后,组件和物体销毁后,协程不执行;物体失活协程不执行,组件失活协程执行。
当然,也有不继承Mono的类,它们一般是单例模式类(管理)或者数据结构类(储存)。
生命周期函数
生命周期函数就是一些固定名字的函数。在这些函数依附的GameObject对象从出生到消亡的过程中,会通过反射自动调用。
注意:
生命周期函数的访问修饰符一般为private和protected
因为不需要在外部自己调用生命周期函数,都是Unity自己帮助我们调用的。
以下是生命周期函数中的函数:
- Awake:对象出生时调用。一个对象只调用一次,得到脚本等操作时就需要写在其中。
- OnEnable:依附的游戏对象每次被激活时调用。
- Start:在Awake之后调用(第一次帧更新之前)。用于初始化信息,一个对象只会调用一次。
- FixedUpdate:物理帧更新。固定间隔时间执行,间隔时间可以在Edit -> project setting -> Time中设置
- Update:逻辑帧更新。每帧执行。
- LateUpdate:每帧执行,在Update之后执行。
- OnDisable:依附的游戏对象每次被失活时调用。
- OnDestroy:对象被销毁时调用。
另外,生命周期函数是支持继承和多态的,例如:
1 | protected override void Awake() |
基础组件及API
gameObject 游戏对象
成员变量
- 名字
1 | this.gameObject.name |
- 是否激活
1 | this.gameObject.activeSelf |
- 是否是静态
1 | this.gameObject.isStatic |
- 层级
1 | this.gameObject.layer |
- 标签
1 | this.gameObject.tag |
- 位置信息
1 | this.gameObject.transform |
这一条也可以省略的写作:this.transform
这之中能得到更加详细的信息,详情参见:Unity插件之Transform | MeiMeiBlog
静态方法
- 创建自带几何体
1 | GameObject.CreatPrimitive(PrimitiveType.Cube); |
- 查找对象
注意:查找对象时无法找到失活的对象;在场景中存在多个满足条件的对象时,我们无法准确找到想要的
先看查找单个对象,查找单个对象有两种方法:
一是通过对象名查找,
1 | GameObject.Find("立方体") |
这个查找效率比较低下,因为他会在场景中所有对象中去找。没找到会返回null
还可以通过tag查找,
1 | GameObject.FindGameObjectWithTag("Player") |
查找多个对象:
1 | GameObject.FindGameObjectsWithTag("Player") |
- 实例化对象(克隆对象)
1 | GameObject.Instantiate(Obj); |
另外,关于这个方法有两种用法。
用法一:创建一会儿会消失
1 | //参数1:要实例化的对象 |
用法二:创建之后要跟着父对象移动
1 | //参数1:要实例化的对象 |
- 删除对象(脚本)
1 | //参数1:要删除的对象或者脚本 |
这种方法还有一种重载:
1 | // 参数2:延迟几秒钟删除 |
- 过场景不移除
默认情况下,在切换场景时,场景中的对象都会被自动删除掉。如果你希望某个对象过场景时不被移除,就可以使用下面这个API。
1 | // 参数:不想被删除的gameObject对象 |
如果你继承了MonoBehavior其实可以不用写GameObject一样可以使用,因为这些方法是Unity里面的Object基类提供给我们的,所以可以直接用(Mono继承了Object)。
成员方法
- 创建空物体
1 | GameObject obj = new GameObject(); |
- 为对象添加脚本
1 | Test test1 = obj.AddComponent<Test1>(); |
- 标签比较
标签比较有两种方式:
1 | if(this.gameObject.CompareTag("Player")) |
还可以:
1 | if(this.gameObject.tag == "Player") |
- 设置激活失活
1 | // 参数:false失活 true激活 |
- 修改层级
1 | obj.layer = LayerMask.NameToLayer("UI"); |
Time 时间
- 时间缩放
1 | Time.timeScale = 0; // 时间停止 |
- 帧间隔时间
作用:用于计算位移,路程 = 时间 * 速度
1 | // 游戏暂停时就不动 -> 帧间隔时间 |
- 游戏开始到现在的时间
作用:用来计时
1 | // 受scale影响 |
- 物理帧间隔时间
1 | // 受scale影响 |
- 帧数
1 | Time.frameCount |
Input 输入
所有关于输入相关的热键可以在:Edit - Project Settings - Input Manager 中查询
鼠标
- 鼠标在屏幕位置
1 | Input.mousePosition |
返回值只有x和y,因为屏幕是2D的
- 检测鼠标输入
鼠标输入分为长按和短按还有中键滚动,先来看短按相关(0左键 1右键 2中键)
1 | // 按下一瞬间 进入一次 |
还有长按相关:
1 | // 按住按键不放时 一直进入 |
中键滚动时会返回值y( -1向下 0不动 1往上)
1 | Input.mouseScroll |
键盘
- 键盘按下
1 | Input.GetKeyDown(KeyCode.W) |
这种方法还有一种重载,
1 | // 参数:传入字符串(只能小写) |
- 键盘抬起
1 | Input.GetKeyUp(KeyCode.W) |
- 键盘长按
1 | Input.GetKey(KeyCode.W) |
默认轴
默认轴就是Unity内部帮我们设定好的坐标轴。
- 键盘AD键(控制左右移动)
1 | Input.GetAxis("Horizontal") |
- 键盘WS键(控制上下移动)
1 | Input.GetAxis("Vertical") |
- 鼠标横向移动
1 | Input.GetAxis("Mouse X") |
- 鼠标竖向移动
1 | Input.GetAxis("Mouse Y") |
值得一提的是,GetAxis方法是在-1 ~ 1之间渐变的,会出现小数。而GetAxisRaw方法和GetAxis方法使用一致,不过返回的都是-1、0、1这样的整数。
- 是否有任意键或鼠标长按
1 | if(Input.anyKey) |
- 是否有任意键或鼠标按下
1 | if(Input.anyKeyDown) |
手柄
- 得到连接的手柄的所有按钮的名字
1 | string[] strs = Input.GetJoystickNames() |
- 某一个手柄键按下
1 | Input.GetButtonDown("Jump") |
- 某一个手柄键抬起
1 | Input.GetButtonUp("Jump") |
- 某一个手柄键长按
1 | Input.GetButton("Jump") |
移动设备
- 触摸点数
1 | Input.touchCount |
还可以得到某个触摸点
1 | Touch t1 = Input.touches[0] |
然后得到它的其他信息:
1 | t1.position // 位置 |
- 是否启用多点触控
1 | Input.multiTouchEnabled = false |
陀螺仪(重力感应)
- 是否开启陀螺仪
1 | Input.gyro.enabled = true |
- 重力加速度向量
1 | Input.gyro.gravity |
- 旋转速度
1 | Input.gyro.rotationRate |
- 陀螺仪当前的旋转四元数
可以用这个角度信息来控制场景上的一个3D物体受到重力影响。实现手机怎么动他就怎么动的功能。
1 | Input.gyro.attitude |
Screen 屏幕
静态属性
- 当前屏幕分辨率
1 | Resolution r = Screen.currentResolution |
进而可以得到当前屏幕分辨率的宽和高:
1 | r.width // 宽 |
- 屏幕窗口当前的宽高
1 | Screen.width // 宽 |
- 屏幕休眠模式
1 | Screen.sleepTimeout = SleepTimeout.NeverSleep |
- 运行时是否全屏模式
1 | Screen.fullScreen = true |
- 窗口模式
1 | Screen.fullScreenMode = FullScreenMode.Windowed; // 窗口模式 |
- 移动设备屏幕转向
1 | Screen.autorotateToLandscapeLeft = true; // Home键在左 |
- 指定屏幕显示方向
1 | Screen.orientation = ScreenOrientation.Landscape |
- 设置屏幕分辨率
1 | // 参数3:是否全屏 |
Camera 相机
参数相关
- Clear Flags:背景选择
- skybox:天空盒
- Solid Color:颜色填充(在Background中选择颜色)
- Depth only:只画该层
- Dont Clear:不移除,覆盖渲染
- Culling Mask:选择性渲染部分层级,可以指定只渲染对应层级对象
- Projection
- Perspective 透视模式
- FOV Axis:视场角轴:决定了光学仪器的视野范围
- Field of view:视口大小
- Physical Camera:物理摄像机。勾选后可以选择焦距、传感器尺寸、透视移位等等
- orthographic:正交摄像机(一般用于2D游戏)
- Perspective 透视模式
- Clipping Planes:裁剪平面距离
- Viewport Rect:视口范围,主要用于双摄像机游戏,0~1相当于宽高百分比
- x:x轴起点
- y:y轴起点
- w:宽度
- h:高度
- Depth:渲染顺序上的深度
- Rendering Path:渲染路径
- Target Texture:纹理渲染。可以用来制作小地图
- Occlusion Culling:剔除遮挡
- HDR:是否允许高动态范围渲染
- MSAA:是否允许抗锯齿
- Allow Dynamic Resolution:是否允许动态分辨率呈现
- Target Display:用于哪个显示器。主要用来开发由多个屏幕的平台游戏
小地图的制作思路:
- 新建一个摄像机,将Rotation改为90 0 0
- 在Project窗口中新建一个Render Texture,拖拽到Target Texture上
- Occlusion Culling:是否启用剔除遮挡
代码相关
- 获取摄像机(场景上必须有一个tag为
MainCamera的摄像机)
1 | Camera.main.name |
- 获取摄像机的数量
1 | Camera.allCamerasCount |
- 得到所有摄像机
1 | Camera[] allCamera = Camera.allCameras; |
- 摄像机剔除前处理的委托函数
1 | Camera.onPreCull += (c) => |
- 摄像机渲染前处理的委托
1 | Camera.onPreRender += (c) => |
- 摄像机渲染后处理的委托
1 | Camera.onPostRender += (c) => |
- 设置深度
1 | Camera.main.depth = 10; |
坐标转换
- 世界坐标 转 屏幕坐标
1 | Camera.main.WorldToScreenPoint(this.tranform.position); |
转换过后,x和y对应的就是屏幕坐标,z对应的是这个3D物体离我们的摄像机有多远。
- 屏幕坐标 转 世界坐标
1 | Vector3 v = Input.mousePosition; |
改变z的原因:不改默认为0。如果不改:转换过去的世界坐标系的点 永远是一个点(视口相交的焦点);如果改变:转换过去的世界坐标系的点 相对于摄像机前方多少的单位横截面上的世界坐标点
- 世界转视口
1 | Camera.main.WorldToViewportPoint |
- 视口转世界
1 | Camera.main.ViewportToWorldPoint |
- 视口转屏幕
1 | Camera.main.ViewportToScreenPoint |
- 屏幕转视口
1 | Camera.main.ScreenToViewportPoint |
- 屏幕转射线
1 | // 参数:鼠标当前的位置 |
核心系统
Unity的核心系统有3个:物理系统、光源系统、音效系统。详情参见以下文章:
Unity之物理系统 | MeiMeiBlog
Unity之光源系统 | MeiMeiBlog
Unity之音效系统 | MeiMeiBlog
基础
Unity基础部分主要介绍3D数学、资源动态加载、和一些调试工具。
3D数学
Mathf和Math
Math是C#中封装好的用于数学计算的工具类,它位于System命名空间中。
Mathf是Unity中封装好的用于数学计算的工具结构体,它位于UnityEngine命名空间中。
它们的相关方法几乎一样,不过Mathf中不仅包含Math中的方法,还多了一些适用于游戏开发的方法,所以我们在进行Unity游戏开发时,都使用Mathf。
常用方法
- Π - PI
1 | Mathf.PI |
- 正无穷大
1 | Mathf.Infinity |
- 取绝对值
1 | Mathf.Abs(-10) |
- 向上取整
1 | float f = 1.3f; |
- 向下取整
1 | Mathf.FloorToInt(1.6f) |
- 夹紧函数
加紧函数的作用是:小于最小取最小;大于最大取最大;位于中间取自身
1 | //参数1:目标计算值 |
- 获取最大(小)值
1 | Mathf.Max(1, 2, 3, 4) // 里面是变长参数 |
- 一个数的n次幂
1 | Mathf.Pow(2, 4) |
- 四舍五入
1 | Mathf.RoundToInt(1.3f) |
- 返回一个数的平方根
1 | Math.Sqrt(4) |
- 判断一个数是否是2的n次方
1 | Math.IsPowerOfTwo(4) // 返回值是bool值 |
这个方法常用来判断2进制
- 判断正负数
1 | // 返回值是float,1 -> 正数,-1 -> 负数 |
- 插值运算
1 | result = Mathf.Lerp(start, end, t) |
这个方法有2个重要的用法,都是涉及到变化的。这两个用法如下:
用法1:
每帧改变result的值——变化速度先快后慢,位置无限接近,但是不会得到end位置
1 | start = Mathf.Lerp(start, 10, Time.deltaTime); |
用法2:
每帧改变t的值——变化速度匀速,位置每帧接近,当t>=1时,得到end结果
1 | time += Time.deltaTime; |
三角函数
- 弧度和角度相互转换
1 | // 弧度转角度 |
- 三角函数和反三角函数
1 | // 三角函数 |
坐标系
| 名称 | 原点 | 轴向 |
|---|---|---|
| 世界坐标系 | 世界的中心点 | 红绿蓝xyz |
| 物体坐标系 | 物体的中心点 | 左手法则 |
| 屏幕坐标系 | 左下角 | 右边x上边y |
| 视口坐标系 | 原点轴向与3.一致 | 左下角(0, 0) 右上角(1, 1) |
坐标转换相关:
- 世界 & 本地 互相转换:
Transform - 屏幕 & 世界 互相转换:
Camera - 世界 & 视口 相互转换:
Camera - 视口 & 屏幕 相互转换:
Camera
向量
Vector3 主要是用来表示三维坐标系中的一个点或是一个向量。
- 申明
1 | Vector3 v = new Vector3(); |
创建了一个Vector3类的实例v,并调用Vector3的无参构造函数来初始化这个实例,xyz都为0,也可以只传xy,z默认为0。
- 常用的向量
1 | Vector3.zero //000 |
- 两点表示一个向量(终点 - 起点)
1 | Vector3 AB = B - A; |
- 向量的模长
1 | AB.magnitude |
- 单位向量
1 | AB.normalized |
- 计算两个点之间的距离的方法
1 | Vector3.Distance(v1, v2); |
- 加、减、数乘、除
使用运算符重载可以使向量相加减,数乘,除,就是每个向量对应位置相加减,示例如下:
1 | Vector3 v1 = new Vector3(1, 1, 1); |
输出结果如下:
1 | (3, 3, 3) // + |
- 点乘
点乘的数学公式是:A·B = xa * xb + ya * yb + za * zb,得到的结果是一个标量。几何意义表示投影长度。
我们可以根据这个结果判断对象方位:
1 | float dotResult = Vector3.Dot(this.transform.forward, |
如果dotResult >= 0,则目标点在我前方,否则在我后方。
还可以根据这个推导公式算出夹角:
1 | // 步骤 |
一个更简单的方法:
1 | Vector3.Angle(this.transform.forard, target.position - this.transform.position); |
- 叉乘
1 | Vector3.Cross(A.position, B.position); |
叉乘结果垂直于AB平面,如果叉乘结果的y > 0,证明B在A右侧;否则B在A的左侧。
- 插值运算
插值运算的核心是:在两个已知值之间,按照一定的比例计算出中间值。在Unity中,我们常常用插值运算来做平滑过渡。
知识点一:线性插值
以下是线性插值的公式:
1 | Vector3 Lerp(Vector3 a, Vector3 b, float t); |
a:起始坐标;b:目标坐标;t:插值因子,即是控制从起点到终点的比例。取值范围通常是[0, 1]:t = 0→ 返回a(起始点);t = 1→ 返回b(目标点);t = 0.5→ 返回a和b的正中间点。
公式:结果 = 起始值 + (目标值 - 起始值) × 插值比例
- 先快后慢。每帧改变
start位置,位置无限接近,但不会得到end位置
1 | A.position = Vector3.Lerp(A.position, target.position, Time.deltaTime); |
- 匀速。每帧改变
t,当t >= 1时得到结果
1 | private Vector3 nowTarget; |
知识点二:球形插值
1 | C.position = Vector3.Slerp(Vector3.right * 10, Vector3.forward * 10, time); |
知识点三:线性和球形插值的区别
线性:直接平移,直线轨迹。用于跟随移动,摄像机跟随。
球形:直线旋转,弧形轨迹。用于曲线运动,模拟太阳运动弧线。
四元数
在Unity中,描述物体旋转主要有两种方式:
欧拉角(Euler Angles):用X、Y、Z三个轴的旋转角度,更容易理解,但存在“万向节死锁:的问题(旋转到特定角度会丢失一个自由度,无法旋转)
四元数(Quaternion):用四维向量描述旋转,无万向节死锁,是 Unity 内部计算旋转的标准方式(物体的
transform.rotation就是四元数类型)。
因此,我们需要学习用四元数来表示物体的旋转。
四元数的计算比较复杂,我们只需要调用Unity为我们封装好的方法即可。以下是常用方法:
- 声明
1 | // 参数1:旋转度数 |
- 四元数和欧拉角的相互转换
欧拉角 -> 四元数
1 | Quaternion q = Quaternion.Euler(60, 0, 0); // 参数就是欧拉角 |
四元数 -> 欧拉角
1 | q.eulerAngles |
- 单位四元数
1 | Quaternion.identity |
- 插值运算
四元数中的插值运算Lerp和Slerp差不多,建议用Slerp(效果更好)
- 先快后慢
1 | A.Transform.rotation = Quaternion.Slerp(A.transform.rotation, target.rotation, Time.deltatime); |
- 匀速变化
1 | start = B.transform.rotation; |
- 朝向指定方向转动
1 | // A的目标方向是B |
Resources 资源动态加载
Resources 是 Unity 中用于动态加载本地资源的核心方式,适合加载需要在运行时按需读取的资源(如预制体、图片、音频等)。
使用时,我们可以在Project - Assets文件夹下创建一个Resources文件夹。需要通过 Resources相关API动态加载的资源需要放在其中。这个文件夹下所有文件都会被打包出去且会被压缩加密,想要在读取的时候只能使用Resources相关API。
Resources资源加载有两种方式:同步加载和异步加载。同步加载更简单,但会卡;异步加载不卡,但代码稍复杂。小图片之类的就可以用同步加载,大图片就可以用异步加载。
Resources 同步加载
- 预制体对象
1 | // 参数:想要加载的预设体 |
- 音效资源
1 | public AudioSource audios; |
- 文本资源
1 | TextAsset ta = Resources.Load("") as TextAsset; |
还可以得到文本内容:
1 | ta.text |
得到字节数据组:
1 | ta.bytes |
- 图片资源
1 | private Texture tex; |
另外,如果资源同名我们可以通过指定资源的类型来加载,如下:
1 | Resources.Load("", typeof()); |
也可以加载指定名字的所有资源:
1 | object[] objs = Resources.LoadAll("Tex/TextJPG"); |
Resources 异步加载
异步加载有两种方法,第一种是通过异步加载中的完成事件监听使用加载的资源:
1 | void Start() |
另一种通过协程加载更常用:
1 | void Start() |
Resources 资源卸载
Resources加载一次资源之后就一直存放在内存中作为缓存,第二次加载时发现缓存中存在该资源,会直接取出来使用。
所以多次重复加载不会浪费内存,但是会浪费性能(每次加载都会去查找取出)。
我们可以通过手动释放缓存中的资源:
- 卸载指定资源
1 | Resources.UnloadAsset(); |
注意: 该方法不能释放 GameObject 对象,因为他会用于实例化对象。他只能用于一些不需要实例化的内容。
- 卸载未使用的资源
1 | Resources.UnloadUnusedAssets(); |
注意: 一般在过场景时和GC一起使用
Linerenderer 画线功能
Linerenderer是Unity提供的一个用于画线的组件,使用它我们可以在场景中绘制线段。
一般可用于:绘制攻击范围、武器红外线等等
参数相关
- Scene Tools:有两个按钮。左侧是编辑点、右侧是添加点
无编辑模式:
- Simplify Preview:简化预览
- Tolerance:宽容度。偏离值越大偏差越大
编辑点模式:
- Show Wireframe:显示线框
- Subdivide Selected:细分选项。选择两个或多个相邻点时该按钮启用,会在相邻点之间插入一个新点
添加点模式:
Input:输入模式
- Mouse Position:鼠标位置
- Physics Raycast:基于物理射线
- Layer Mask:哪些层级检测射线
- Min VertexDistance:最小顶点距离
- Offset:偏移量
Loop:终点起点是否自动相连
Positions:线段的点
Width:线段宽度曲线调整
Color:颜色变化
Corner Vertices:角顶点。增加这个值可以让线角看起来更圆
End Cap Vertices:终端顶点
Alignment:对齐方式
- View:视点。线段对着摄像机
- Transform Z:线段面向其z轴
Texture Mode:纹理模式
- Stretch:拉伸。沿整条线映射纹理一次
- Tile:平铺。不停的重复纹理
- Distribute Per Segment:分配执行
- Repeat Per Segment:重复显示
Shadow Bias:阴影偏移
Generate Lighting Data:生成光源数据
Use World Space:是否使用世界坐标系
Materials:线使用的材质球
Lighting:光照影响
- Cast Shadows:是否开启阴影
- Receive Shadows:接收阴影
Probes:光照探针
- Light Probes:光探测器模式
- Reflection Probes:反射探测器模式
Additional Settings:附加设置
- Motion Vectors:运动矢量
- Camera Motion Only:使用相机运动来跟踪运动
- Per Object Motion:特定对象来跟踪运动
- Force No Motion:不跟踪
- Dynamic Occludee:动态遮挡剔除
- Sorting Layer:排序图层
- Order in Layer:此线段在排序图层中的顺序
- Motion Vectors:运动矢量
代码相关
- 动态添加一个线段
1 | GameObject line = new GameObject(); |
- 首尾相连
1 | lineRenderer.loop = true; |
- 设置开始结束宽
1 | lineRenderer.startWidth = 0.02f; |
- 设置开始结束颜色
1 | lineRenderer.startColor = Color.white; |
- 设置材质
1 | private Material m; |
- 设置点
1 | // 设置点的个数 |
- 是否使用世界坐标系
1 | lineRenderer.useWorldSpace = false; |
- 让线段受光影响
1 | lineRenderer.generateLightingData = true; |
Gizmos 辅助绘制
Gizmos(辅助线 / 辅助绘制) 是非常实用的工具,常用于在 Scene 视图中可视化调试游戏对象的位置、范围、检测区域等。
注意:Gizmos绘制时使用的是世界坐标
Gizmos使用时也有关键的生命周期方法:
- 每帧触发
1 | OnDrawGizmos() |
- 仅当挂载脚本的物体被选中时触发
1 | OnDrawGizmosSelected() |
注意:OnDrawGizmos()执行时间在Awake()之前。
以下是Gizmos常用的方法:
- 球体
实心球:
1 | // 参数1:球体中心坐标(世界空间) |
边缘线球:
1 | Gizmos.DrawWireSphere(Vector3 center, float radius) |
- 立方体
实心立方体:
1 | // 参数1:立方体中心坐标 |
边缘线立方体:
1 | Gizmos.DrawWireCube(Vector3 center, Vector3 size) |
- 线段
1 | // 参数1:线段起点 |
- 胶囊体
1 | // 参数1:胶囊体中心 |
- 颜色
1 | Gizmos.color = new Color(1, 0, 0, 0.5f) |
核心
Unity核心部分将学习2D/3D动画相关、角色控制器、以及导航寻路系统。
2D图片相关
图片导入设置
Unity支持以下几种图片格式:BMP、TIF、JPG、PNG、TGA、PSD等等。其中,最常用的图片格式是:JPG、PNG、TGA。
图片导入后,可以在Inspector窗口对它进行相关参数的设置,分别有纹理类型、纹理形状、高级设置以及平台设置。以下对其一一讲解。
Texture Type 纹理类型
设置纹理类型主要是表明这张图片的主要用途。
Default:默认纹理
- sRGB(Color Texture):默认勾选。因为人眼看颜色变化不是线性变化的,通过这个算法可以矫正
- Alpha Source:指定如何生成纹理的Alpha通道
- None:导入纹理都没有Alpha
- Input Texture Alpha:输入导入图片的Alpha
- From Gray Scale:从导入图片的RGB值的平均值生成Alpha
- Alpha Is Transparency:启用后可以避免边缘上的过滤瑕疵
Normal map:法线贴图格式(用这种方法可以在消耗性能更小的情况下得到更好的表现效果)
- Create From Grayscale:启用后,可以从灰度高度贴图创建法线贴图
- Bumpiness:控制凹凸程度,值越大凹凸感越强
- Filtering:如何计算凹凸值
- Create From Grayscale:启用后,可以从灰度高度贴图创建法线贴图
Editor GUI and Legacy GUI:一般在编辑器或者GUI上使用的纹理
Sprite(2D and UI):2D游戏或者UGUI使用的格式
- Sprite Mode:图像中提取精灵图形的方式
- Single:单张
- Pixels Per Unit:世界空间中的一个单位对应多少像素
- MeshType:网格类型。只有Single和Multiple有。
- Full Rect:创建四边形,将精灵显示在四边形上
- Tight:基于像素Alpha值来生成网格
- Extrude Edges:使用滑动条确定生成的网格中精灵周围流出的区域大小
- Pivot:轴心点
- Generate Physics Shape:启用后,会自动更具精灵轮廓生成默认物理形状
- Multiple:多张图
- Polygon:网格精灵模式
- Single:单张
- Sprite Editor:编辑Sprite
- Sprite Mode:图像中提取精灵图形的方式
Cursor:自定义光标
Cookie:光源剪影格式
- Light Type:应用的光源类型
- Spotlight:聚光灯类型,需要边缘纯黑色纹理
- Directional:方向光,平铺纹理
- Point:点光源,需要设置为立方体形状
- Light Type:应用的光源类型
Lightmap:光照贴图格式
Single Channel:纹理只需要单通道的格式
- Channel:希望将纹理处理为Alpha还是Red通道
Texture Shape 纹理形状
纹理形状设置就是在设置模型贴图和制作天空盒两种模式中切换。
- 2D:最常用。模型和GUI元素会使用
- Cube:立方体。主要用于天空盒和反射探针
- Mapping:如何将纹理投影到游戏对象上
- Auto:根据纹理信息设置
- 6 Frames Layout:包含标准立方体贴图布局之一排列的六个图像
- Latitude-Longitude Layout:将纹理映射到3D维度、经度
- Mirrored Ball:将纹理映射到类似球体的立方体贴图上
- Concolution Type:纹理的过滤类型
- None:无过滤
- Specular:反射探针
- Diffuse:光照探针
- Fixup Edge Seams:解决低端设备上之间立方体贴图过滤错误
- Mapping:如何将纹理投影到游戏对象上
Advanced 高级设置
高级设置是设置纹理的一些尺寸规则、读写规则、MipMap相关设置。
这里提到了一个概念——MipMap。Mipmap指的是开启后Unity会根据信息生成n张不同分辨率的图片,在场景中根据我们离该模型的距离选择合适尺寸的图片用于渲染,提升渲染效率。
- Non_Power of 2:如果纹理尺寸非2的幂次方如何处理
- None:纹理尺寸大小保持不变
- To nearest:将纹理缩放到最接近2的幂的大小
- To larger:将纹理缩放到最大尺寸大小值的2的幂的大小
- To smaller:将纹理缩放到最小尺寸大小值的2的幂的大小
- Read/Write Enabled:启用可以使用Unity中提供的一些方法从纹理中获取到读取
- Streaming Mipmaps:启用则可以使用纹理串流。用于减少Unity对于纹理所需的内存总量
- Generate Mip Maps:允许生成MipMap
- Border Mip Maps:启用可避免颜色向外渗透到极低MIP级别的边缘
- Mip Map Filtering:优化图像质量的过滤方法
- Box:随着尺寸减小,级别更加平滑
- Kaiser:随着Mipmap尺寸大小下降而使用的锐化算法
- Mip Maps Preserve Coverage:Mipmap的Alpha通道在Alpha测试期间保留覆盖率
- Fadeout Mip Maps:级别递减时使Mipmap淡化为灰色
平台设置
纹理在最终打包时在不同平台的尺寸、格式、压缩方式。他非常重要,因为它影响了包大小和读取性能方面的问题。
- Max Size:导入的纹理的最大尺寸
- Resize Algorithm:当纹理尺寸大于指定的Max Size时,使用的缩小算法
- Mitchell:米切尔算法。默认选择
- Bilinear:双线插值算法。适合细节很重要的图片
- Format:纹理格式。一般选择Automatic即可,具体要选择的时候需要查Unity官方表格
- Compression:纹理的压缩类型
- Use Crunch Compression:使用Crunch压缩,压缩时间长,解压速度快
- Compression Quality:压缩质量。质量越高一位置纹理越大,压缩时间越长
- Split Alpha Channel:分离Alpha通道(一张图片分成RGB图片,一张Alpha图片),节约内存。
- Override ETC2 fallback:不支持ETC2压缩的设备上使用的格式
Sprite 精灵图片
Sprite(精灵) 是 2D 游戏 / UI 里可被程序控制的图像对象,区别于普通图片,它自带位置、旋转、缩放、颜色、动画等属性,是 2D 开发的核心显示单元。
接下来将介绍与他相关的常用工具以及组件。
Sprite Editor 精灵编辑器
Sprite Editor是一个常用Sprite资源编辑工具。它的主要作用是:编辑、图集中提取元素、设置精灵边框、设置九宫格、设置轴心(中心)点等等。
注意:Sprite Editor 精灵编辑器只在2D工程生效,如果建的工程是一个3D工程,需要手动安装一个2D Sprite包(Window -> Package Manager -> 2D Sprite)。
想要打开Sprite Editor,我们需要:选中要编辑的图片,在Inspector窗口将它的Texture Type设置为Sprite(2D and UI)
在Sprite Editor窗口的上方,可以选择模式切换菜单,具体如下:
Sprite Editor 默认模式
在默认模式中常常用来设置图片的九宫格的四条边和轴心点。以下是参数相关:
- Name:名字
- Position:在图片中的偏移位置和宽高
- Border:边框,用于设置九宫格的4条边
- Pivote:轴心点
- Pivote Unit Mode:轴心点单位模式
- Normalized:标准化模式,(0~1代表百分之多少的位置)
- Pixels:像素模式
- Custom Pivot:自定义轴心点
Custom Outline 自定义边缘线
当我们需要精确控制 Sprite 边缘形状时(比如像素风角色、字体),常常用到这个模式,他可以帮我们自定义 Sprite 的渲染轮廓,用于优化文字 / 复杂图形的边缘显示,或制作描边效果。
它的参数显示在上方:
- Snap:将控制点贴近在最近的像素
- Outline Tolerance:轮廓点的复杂性和准确性。0~1值越大轮廓点越多,越准确
- Generate:生成网格轮廓
另外,点击图片还可以加点减点。
Custom Physics Shape 自定义物理形状
这个模式的主要作用是决定碰撞判断区域。
Secondary Textures 次要纹理设置
这个模式的主要作用是为图片添加特殊效果,可以将其他纹理和该精灵图片关联,着色器可以得到这些辅助纹理,然后做一些效果处理。
值得注意的是,当我们的图片资源是图集时,需要把Sprite Mode设置为Multiple。
在分割图片时,可以选择多种方式:
Automatic 自动分割
- Pivot:单张图片轴心点位置
- Custom Pivot:自定义轴心点
- Method:如何处理现有对象
- Delete Existing:替换掉已经选择的任何矩形
- Smart:尝试创建新矩形同时保留或调整现有矩形
- Safe:添加新矩形而不更改任何已经存在的矩形
Grid By Cell Size 按单元格大小分割
- Pixel Size:单元格宽高
- Offset:偏移位置
- Padding:和边缘的距离
- Keep Empty Rects:是否保留空矩形
Grid By Cell Count 按行列切割
- Column & Row:行列数
另外,在上方还有个按钮:
- Trim:自动修剪透明区域
当然,当我们的图片资源是多边形时,我们还可以选择Polygon 多边形编辑。参数差不多,这里不做介绍。
Sprite Renderer 精灵渲染器
Sprite Renderer 精灵渲染器是用于渲染2D图片的组件。
参数相关
- Sprite:渲染的精灵图片
- Color:着色
- Flip:水平或竖直翻转精灵图片
- Draw Mode:绘制模式,当尺寸变化时的缩放方式
- Simple:简单模式。缩放时整个图片一起缩放
- Sliced:切片模式。9宫格切片模式,十字区域缩放,四个角不变化。(注意:需要把精灵的网格类型设置为Full Rect)
- Tiled:平铺模式。将中间部分进行平铺而不是缩放。(注意:需要把精灵的网格类型设置为Full Rect)
- Continuous:当尺寸变化时,中间部分将均匀平铺
- Adaptive:当尺寸变化时,类似Simple模式,当更改尺寸达到Stretch Value时,中间才开始平铺
- Mask Interaction:与精灵遮罩交互时的方式
- None:不与场景中任何精灵遮罩交互
- Visible inside Mask:精灵遮罩覆盖的地方可见,而遮罩外部不可见
- visible Outside Mask:精灵遮罩外部的地方可见,而遮罩覆盖处不可见
- Sprite Sort Point:计算摄像机和精灵之间距离时,使用精灵中心还是轴心点
- Material:材质。默认不受光照影响,若想受光照影响,可以选择Default-Diffuse
- Additional Settings:高级设置
- Sorting Layer:排序层选择
- Order in Layer:层级排序号,数值越大显示越前
代码相关
- 添加脚本
1 | SpriteRenderer sr = obj.AddComponent<SpriteRenderer>; |
颜色、反转、size都能通过sr.出来改变
- 改变上面的图片
先来看单张图片:
1 | sr.sprite = Resources.Load<Sprite>("dead1"); |
也可以改变图集:
1 | Sprite[] sprs = Resources.LoadAll<Sprite>("Robot"); // 把所有叫做Robot的资源全部加载 |
Sprite Creator 精灵创造者
Sprite Creator是一个用于编辑Sprite的工具。它的主要作用是利用其上的多边形工具创造出各种多边形,主要用于2D图片的替代资源。
使用方法很简单,在Project窗口右键 -> Creat -> Sprites
- Square 方形
- Triangle 三角形
- Diamond 菱形
- Hexagon 六边形
- Circle 圆形
- Polygon 多边形
Sprite Mask 精灵遮罩
Sprite Mask是一个重要组件。它的主要作用是对精灵图片进行遮罩来制作特殊功能。一般配合Sprite使用(选择Sprite上的Mask Interaction上的类型)。
参数相关:
- Sprite:遮罩图片
- Alpha Cutoff:如果Alpha是半透明的,可以手动确定所显示区域的分界点
- Custom Range:自定义遮罩范围。按照排序层来划分,[back, front)
Sorting Group 排序分组
Sorting Group是一个重要的层级管理组件。主要作用:对多个精灵图片进行分组排序。Unity会将同一个排序组里的精灵图片一起排序,就好像他们是单个游戏对象一样。
注意:
- 子排序组,先排子对象,再按父对象和别人一起排。
- 如果把这个对象做成一个预设体,要注意若存在多个该对象,要修改层级来保证按顺序排列。
Sprite Atlas 精灵图集制作
Sprite Atlas是一个资源打包工具。主要作用是减少DrawCall,提高性能。
在打图集前,我们需要引入精灵包装器,引入后可以通过Unity自带图集工具生成图集。具体操作如下:Editor -> Project Setting -> Editor -> Sprite Packer
Sprite Packer中的模式:
- Disabled:非2D项目中的默认设置,不会打包图集
- Enabled For Builds(Legacy Sprite Packer):构建时打包图集,编辑模式不打包
- Always Enabled(Legacy Sprite Packer):构建时 + 编辑模式运行时打包图集
- 其余2种模式可以类比上面两种理解
Legacy Sprite Packer是传统打包模式的意思,多了一个间隔距离的设置。其中,Padding Power中的数字代表2的n次方
接着,我们就可以来打图集了。在Project中右键 -> Create -> Sprite Atlas
参数相关
讲解参数时我们按照图集类型分类:
Master 主图集
- Include in Build:选中可以在当前构建中包含图集
- Allow Rotation:允许旋转。可以提高图集密度。注意:UI图集需要禁用此选项
- Tight Packing:使用图片轮廓打包而不是矩形。可以提高图集密度
- Padding:各图片间的间隔像素
Variant 变体类型的图集
变体图集的主要作用是以主图集为基础,对它进行缩放,产生一个新的图集副本。如果想使用变体图集中的内容,只需要勾选变体图集的Include in build选项,而主图集禁用此选项即可。
- Master Atlas:关联的主图集
- Include in Build:选中可以在当前构建中包含图集
- Scale:缩放因子
代码相关
- 加载图集资源
1 | SpriteAtlas spriteAtlas = Resources.Load<SpriteAtlas>("MyAtlas"); |
- 得到单张图
1 | sr.sprite = spriteAtlas.GetSprite("dead1"); |
2D物理系统
2D物理系统包含刚体、碰撞器、物理材质、恒定力以及效应器相关的内容。
这些内容可以参考这篇文章:
Unity之物理系统 | MeiMeiBlog
SpriteShape 精灵形状
SpriteShape 是 Unity 提供的2D 地形 / 轮廓绘制工具。
导入:Window -> Package Manager -> 2D SpriteShape -> Install
使用:Project中某一文件夹 -> 右键Create -> 2D -> Sprite Shape Profile
Sprite Shape Profile 精灵形状概述文件
Sprite Shape Profile 精灵形状概述文件是地形的一个基础文件,是对地形的描述。
在使用时,我们只需要将创建的Sprite Shape Profile文件拖入场景,可以点击编辑来编辑这个图片资源的形状。
以下是他的参数相关:
- Use Sprite Borders:是否使用精灵边框,用于九宫格拉伸
- Texture:用于填充实心部分的纹理
- Offset:纹理偏移量
- Angle Ranges:角度范围
- Start:起始角度
- End:结束角度
- Order:Sprite相交时的优先级,优先级越高显示越前
- Sprites:指定角度范围内的的精灵列表
- Corners:指定角显示的精灵图片
另外,还有两个相关的重要组件:
- Sprite Shape Renderer 精灵形状渲染器
该控件主要是控制图片的材质、颜色以及和其它Sprite交互时的排序等等信息。以下是他的参数:
- Color:颜色
- Mask Interaction:遮罩相互作用规则设置
- Fill Material:填充材质
- Edge Material:边缘材质
- Sprite Shape Controller 精灵形状控制器
不启用编辑状态的参数:
- Profile:使用的精灵形状概述文件
- Detail:精灵形状的质量
- Is Open Ended:是否是开放的
- Adaptive UV:自适应UV,如果开启,会自动帮助我们判断是平铺还是拉伸(开启:宽度够 -> 平铺,宽度不够 -> 拉伸;不开启:平铺)
- Optimize Geometry:优化三角形数量,勾选后会最小化精灵图片中的三角形数量
- Enable Tangents:是否启用切线计算功能
- Corner Threshold:角阈值,当拐角处的角度达到这个阈值时将使用角图片
- Stretch UV:是否拉伸UV
- Pixel Per Unit:禁用拉伸UV时才有,Unit单位对应多少像素,较高的值回较少纹理的大小
- Worldspace UV:根据世界控件的UV填充纹理
启用编辑状态后的参数:
- Tangent Mode:切线模式
- 顶点模式:点两侧不构成曲线
- 切线模式:点两侧构成曲线,并且可以控制切线弧度
- 左右切线模式:点两侧不构成曲线,并且可以分别控制左右两侧切线弧度
- Position:选中点的局部坐标位置
- Height:控制点左右两侧精灵图片的高度
- Corner:是否使用角度图片
- Disabled:不使用
- Automatic:自动
- Sprite Variant:选择使用的精灵图片
- Snapping:是否开启捕捉设置控制点
精灵图片一般会配合碰撞器使用。一般使用边界碰撞器(Edge Collider 2D)、多边形碰撞器(Polygon Collider 2D)配合复合碰撞器(Composite Collider 2D)
Tilemap 瓦片地图
Tilemap一般称之为瓦片地图或平铺地图,主要用于快速编辑2D游戏中的场景,通过复用资源的形式提升地图的多样性。工作原理:一张张小图排列组合为一张大地图。
和SpriteShape的异同:
共同点:都用于制作2D游戏的场景或地图
不同点:
1. SpriteShape可以让地形有弧度,Tilemap不行。
2. Tilemap可以快捷制作有伪”z”轴的地图,SpriteShape不行。
导入:Window -> Package Manager -> 2D Tilemap Editor -> Install
创建和编辑瓦片地图
- 创建瓦片地图:
- 将要用的瓦片资源全都导入调色板面板
- Hierarchy窗口右键 -> 2D Object -> 选择瓦片类型 创建画板
- 在调色板面板用工具选择要用的瓦片,直接在Scene窗口绘制
注意:
- 编辑等距瓦片时要设置根据自定义轴排序: Edit -> Project Settings -> Graphics -> Transparency Sort Mode -> Custom Axis(
x = 0 , y = 1, z = -0.26);再把Tilemap Renderer组件上的Mode 改成Individual - 改变伪“z”轴的另一个方法:在瓦片调色板界面勾选
Can Cange Z Position,然后通过+或-来控制资源的位置 - 应用场景:
- 矩形瓦片:横版游戏地图
- 六边形瓦片:策略游戏地图
- 等距瓦片:有伪”z”轴的2D游戏
伪”z”轴的2D游戏注意:
1. 地面不能加碰撞盒,因为他是一个纵深的游戏。可以把人物的重力改为0来解决人物下落问题。
2. 把周围墙体的Collider Type选为Grid。
3. 跳跃问题:可以给游戏玩家添加一个根物体,跳跃时玩家相对于父物体向上移动,并且一直有向下的速度,当y = 0时,认为玩家落地。
瓦片
瓦片是Tilemap的最小单位。创建瓦片资源有两种方法:方法1:Assets -> Create -> 2D -> Tiles;方法2:Window -> 2D -> Tile Palette 瓦片调色板窗口 -> Create New Palette 编辑名字、创建 -> 将资源拖入到窗口中选择要保存的路径
瓦片有很多种类型,接下来一一讲解:
Rule Tile 规则瓦片
规则瓦片可以定义不同方向是否存在连接图片的规则,当满足这个规则时,会使用我们设置的默认图片,让我们更快捷的进行地图编辑。
- Default Sprite:默认图片
- Default GameObject:默认游戏对象
- Default Collider:默认碰撞器规则
- Tiling Rules:平铺规则
Animated Tile 动画瓦片
动画瓦片是可以指定序列帧,产生可以播放序列帧动画的瓦片。
- Number Of Animated Sprite:动画由多少张图构成
- Minimum(Maximum) Speed:最小(大)播放速度
- Start Time:开始播放的时间
- Start Frame:从哪一帧开始播放
Tile Palette 瓦片调色板
打开:Window -> 2D -> Tile Palette
参数相关
Create New Palette
- Name:瓦片调色器名称
- Grid:瓦片的网格布局
- Rectangle:矩形瓦片
- Hexagon:六边形瓦片
- Isometric:等距瓦片。单元格为菱形
- Isometric Z as Y:等距瓦片并且Unity将单元格Z轴转化为局部Y坐标
- Cell Size:瓦片绘制到单元格的大小
- Automatic:基于瓦片资源的精灵大小自动设置
- Manual:自定义大小
上方工具栏(从右至左)
- 选择工具
- 移动工具
- 画笔工具。使用选取器后自动切换到画笔工具,单个填充
- 框填工具。范围填充
- 选取器工具。选取填充样本
- 橡皮擦工具。也可以在使用画笔工具时按住Shift
- 填充工具。批量填充
笔刷
- Default Brush:默认笔刷。当瓦片地图是等距瓦片地图时,可以改变z轴。
- Coordinate Brush:坐标笔刷。可以实时看到格子坐标
- GameObject Brush:游戏对象笔刷。可以在场景中选择和擦除游戏对象(一定是GameObject类型)
- Group Brush:组合笔刷。可以设置参数,当点击一个瓦片样式时,会自动取出一个范围内的瓦片。
- Gap:间隙。遇到几个空格子会停下来
- Limit:限制。最多吸取几个格子(从0开始)
- Random Brush:随机笔刷。可以随机画出瓦片
- Line Brush:线性笔刷。决定起点和终点画一条线出来。
- Line Start Active:起点是否激活。勾选后会再次以上一次画的起点为起点。
- Fill Gaps:补充线段。把线补得更饱满。
- Line Start:开始起点。
- Tint Brush:着色笔刷。可以给瓦片着色,瓦片的颜色锁要开启(选择要改变的瓦片,在Inspector窗口切换Debug模式修改Flags改为None)
- Tint Brush(Smooth):光滑着色笔刷。给瓦片渐变着色(要把Hierarchy窗口中的Tilemap上的Tilemap Renderer上的材质球改成TinedTilemap.shader)
- 自定义笔刷
操作相关
- 单击瓦片:选择瓦片
- 在多个瓦片上移动:选择多个瓦片
- Alt+左键拖动 / 滚动键拖动:平移
- 旋转滚动键:放大、缩小
瓦片地图关键脚本
瓦片地图上有3个关键脚本,分别是Grid、Tilemap以及Tilemap Renderer。
参数相关
Grid
- Cell Size:网格单元格的大小
- Cell Gap:网格之间的间隔大小
- Cell Layout:网格单元的形状和排列
- Rectangle:矩形
- Hexagon:六边形
- Isometric:等距布局,单元格为菱形
- Isometric Z as Y:等距布局,但Unity将单元格z轴转换为局部y坐标
- Cell Swizzle:将XYZ单元格坐标重新排序
Tilemap
- Animation Frame Rate:Unity播放瓦片动画的速率
- Color:瓦片色调颜色
- Tile Anchor:瓦片的锚点偏移
- Orientation:瓦片地图上瓦片的方向
Tilemap Renderer
- Sort Order:设置所选瓦片地图上的瓦片排序方向
- Mode:渲染器的渲染模式
- Chunk:按位置对瓦片进行分组,并将瓦片精灵一起批处理进行渲染,性能较好
- Individual:单独渲染每个瓦片,会考虑他们的位置和排列顺序,会让瓦片精灵和场景中其他渲染器或自定义排序轴进行交互
- Detect Chunk Culling:渲染器如何剔除瓦片地图的边界
- Chunk Culling Bounds:当选择手动设置剔除拓展边界时,可以在这里填写拓展的值
代码相关
准备工作:
1 | using UnityEngine.Tilemaps; |
重要API:
- 清空瓦片地图
1 | map.ClearAllTiles(); |
- 获取指定坐标格子
1 | TileBase tmp = map.GetTile(Vector3Int.zero); |
- 设置瓦片
1 | // 设置单张瓦片: |
- 删除瓦片
1 | map.SetTile(new Vector3Int(0, 2, 0), null); |
- 替换瓦片:所有使用替换前瓦片的都会被替换
1 | map.SwapTile(tmp, tileBase); |
- 坐标转换相关
屏幕坐标 -> 世界坐标
1 | Vector3 v = Input.mousePosition; |
其中,注意到改变了z的值,改变z的原因是:不改默认为0。如果不改,转换过去的世界坐标系的点 永远是一个点(视口相交的焦点);改变后,转换过去的世界坐标系的点相对于摄像机前方多少的单位横截面上的世界坐标点。
世界坐标 -> 格子坐标
1 | grid.WorldToCell(obj.position); |
瓦片地图碰撞器
为挂载Tilemap Renderer脚本的对象添加Tilemap Collider 2D脚本,会自动添加碰撞器。
注意:为了节约性能,可以只选择要与人物碰撞的地方设置碰撞器。
注意:想要生成碰撞器的瓦片Collider Type类型要进行设置。优化:可以添加一个Composite Collider 2D并把Tilemap Collider 2D脚本上的应用复合碰撞器勾选上,添加后会增加一个Rigidbody 2D,注意要把Body Type选为静态。
模型相关
模型导入设置
模型的制作过程如下:建模 -> 展UV -> 材质和纹理贴图 -> 骨骼绑定 -> 动画制作
Unity支持的模型:.fbx、.dae、.3ds、.dxf、.obj 等等(官方建议为FBX格式)
fbx模型格式的优势:
- 减少不必要数据,提升导入效率
- 不需要在每台计算机上安装建模软件的授权副本
- 对Unity版本无要求
导入模型的基本流程如下:
- 美术用3D建模软件制作好模型导出FBX格式模型资源
- 程序将这些模型资源导入到Unity的资源文件夹中
- 在Unity内部对模型进行基础设置:模型、骨骼、动作、材质
设置模型
在Project窗口中选中导入的模型后就可以在Inspector窗口中进行相关页签的设置。一共有四个重要页签,接下来分别介绍。
Model 模型页签
模型页签是用来设置模型比例、是否导入模型中的摄像机和光源、网格压缩方式等等内容的。
- Scene:场景相关
- Scale Factor:缩放
- Convert Units:启用后可以将模型文件中定义的模型比例转换为Unity的比例
- Import BlendShapes:是否允许Unity随网格一起导入混合形状
- Import Visibility:导入是否可见
- Import Cameras:是否导入摄像机
- Import Lights:是否导入光源
- Preserve Hierarchy:始终创建一个显示预制体根。如果美术资源把人物和骨骼分别导入,这个参数能确保动画正常播放
- Sort Hierarchy By Name:在层级窗口根据名字排序子物体
- Meshes:网格相关
- Mesh Compression:网格压缩。调整该参数可以优化游戏包的大小
- Read/Write Enabled:是否开启读写网格信息。开启后会增加内存占用,当需要读取网格信息、使用网格碰撞器的时候需要勾选。
- Optimize Mesh:确定三角形在网格中列出的顺序以提高GPU性能
- Generate Colliders:生成碰撞器
- Geometry:几何体相关
- Keep Quads:保留四边形
- Weld Vertices:合并在空间中共享相同位置的顶点
- Index Format:网格索引缓冲区的大小
- Legacy Blend Shape Normals:启用后会基于Smoothing Angle值来计算法线
- Normals:定义如何计算法线
- Blend Shape Normals:定义如何为混合形状计算法线
- Normal Mode:计算法线的模式
- Smoothness Source:如何确定平滑
- Smoothing Angle:控制是否为硬边拆分顶点
- Tangents:定义如何导入或计算顶点切线
- Swap UVs:在网格中交换UV通道
- Generate Lightmap UVs:为光照贴图创建第二个UV通道
Rig 操纵(骨骼)页签
骨骼页签是用来设置骨骼和替身系统相关信息的。
- Animation Type:动画类型
- None:不存在动画。主要用于一些环境静态模型
- Legacy:旧版动画类型。与Unity3.x及更早版本导入和使用动画,一般不使用
- Generation:动画导入方法
- Generic:通用模型,非人形模型。可以是任意形状,可能有8只腿2个头等等。这种模型主要需要设置骨骼根节点
- Root Node:选择用于此Avatar的根节点的骨骼
- Humanoid:人形模型。一般情况下,有头和四肢(一个头两条腿两只手)。人形模型需要使用Avatar化身系统,绑定人主要关节的映射关系
- Avatar Definition:选择获取Avatar定义的位置
- No Avatar:没有化身系统信息
- Create From This Model:根据此模型创建Avatar化身信息
- Configure..:打开Avatar化身配置
- Copy From Other Avatar:指向另一个模型上设置的Avatar化身信息
- Source:复制另一个具有相同骨架的Avatar化身信息以导入其动画剪辑
- Skin Weights:设置影响单个顶点的最大骨骼数量
- Standard(4 Bones):使用最多4个骨骼来产生影响
- Custom:设置自定义的最大骨骼数
- Max Bones/Vertex:定义每个顶点的最大骨骼数
- Max Bone Weight:设置考虑骨骼权重的最低阈值
- Optimize Game Object:在Avatar化身系统和Animator动画组件中删除和存储所导入的游戏对象骨骼层级信息
- Extra Transform to Expose:要公开的骨骼层级
- Avatar Definition:选择获取Avatar定义的位置
另外,其中提到的Avatar化身系统的本质是让一个人形动作可以在另一个人形模型上复用。
Avatar化身系统可以通过Create From This Model -> Configure..进入,其中有2个页签,分别是Mapping和Muscles & Settings,接下来一一介绍。
- Mapping:映射关系
- Body:身体
- Head:头部
- Left/Right Hand:左右手
在这下方还有两个下拉列表:
Mapping:映射关系
- Clear:清空映射
- Automap:自动映射
- Load:从文件中读取
- Save:保存映射信息
Pose:姿势
- Reset:重置姿势
- Sample Bind-Pose:绑定姿势实例
- Enforce T-Pose:强制T姿势
Mascles & Settings:肌肉和设置
- Muscle Group Preview:肌肉群预览
- Per-Muscle Settings:肌肉设置。用于设置各骨骼在旋转时的范围限制。
Additional Settings:高级设置。可以设置手脚的扭转和伸展值
- Translation DoF:如果启用,将启用人形角色的移动动画;如果禁用,Unity仅使用旋转对骨骼进行动画化
在这下方还有一个下拉列表:
- Muscles:肌肉
- Reset:重置所有设置
Animation 动画页签
动画页签是用来导出动作的。当我们选中包含动画剪辑的模型时,该页签将展示动画设置相关的内容。导出动画时建议将模型和动画文件分别导出。
如果这个模型没有动画,只会有下面两个参数:
- Import Constraints:从此资源导入约束
- Import Animation:从此资源导入动画
如果有动画,将会多出这些参数:
- Back Animation:通过反向动力学(IK)或模拟创建的动画以便推进运动关键帧,仅用于Maya、3Dmax和Cinema4D文件
- Anim.Compression:导入动画时使用的压缩类型
- Off:禁用动画压缩
- Keyframe Reduction:减少冗余关键帧。仅适用于Generic通用动画类型
- Optimal:让Unity自己决定如何压缩。仅适用于Generic通用和Humanoid人形动画类型
- Rotation Error:设置旋转曲线压缩容错度
- Position Error:设置位置曲线压缩容错度
- Scale Error:设置缩放曲线压缩容错度
- Animated Custom Properties:导入你指定为自定义用户属性的任何FBX属性
- Clips:动画剪辑选择列表。选择一个动画剪辑后,可以在下方编辑具体的相关属性信息
- Loop Time:播放动画剪辑时是否循环播放
- Loop Pose:无缝循环运动
- Loop Match:循环匹配。亮绿灯代表已经匹配上了
- Cycle Offset:循环动画在其他时间开始时的周期偏移
- Root Transform Rotation:根位置的角度相关
- Bake Into Pose:将根旋转烘焙到骨骼移动,禁用后将存储为根运动
- Based Upon:根旋转的基础
- Original:保持源文件中的原始旋转
- Root Node Rotation:使用根节点的旋转
- Body Orientation:保持上半身朝前
- Offset:根旋转偏移
- Root Transform Position(Y):根垂直位置
- Bake Into Pose:将垂直根运动烘焙到骨骼移动,禁用后将存储为根运动
- Based Upon(at Start):垂直方向根位置的基础
- Original:保持源文件中的垂直位置
- Root Node Rotation:使用垂直根位置
- Body Orientation:保持质心于根位置对齐
- Feet:保持双脚与根位置对齐
- Offset:垂直根位置偏移
- Root Transform Position(XZ):根水平位置
- Bake Into Pose:将水平根运动烘焙到骨骼移动,禁用后将存储为根运动
- Based Upon(at Start):水平方向根位置的基础
- Original:保持源文件中的水平位置
- Root Node Rotation:使用水平根位置
- Center Of Mass:保持与根位置对齐
- Offset:水平根位置偏移
- Mirror:左右镜像
- Additive Reference Pose:启用后可以设附加动画层(附加动画层:在动画控制器中可以添加新的动画层)基础参考姿势的帧
- Pose Frame:当启用参考姿势帧时,该项就是选择的具体是哪一帧
- Curves:曲线。在播放动画过程中可以控制值的改变,从而进行一些逻辑处理
- Events:事件
- Mask:动画遮罩。可以指定哪一部分或者哪些骨骼排除在外,不受动作影响
- Humanoid:人形。点击变为红色即启用遮罩
- Transform:通用类型。明确指定哪些骨骼关节点不受影响
- Motion:当导入的动画剪辑包含根运动时,我们可以在这里手动设置特定骨骼关节点作为我们的跟运动节点
- Import Messages:导入动画时如果出现问题(黄色警告符号),可以勾选来查看具体的问题
- Loop Time:播放动画剪辑时是否循环播放
预览窗口:
Materials 材质纹理页签
材质纹理页签是用来设置材质的。
- Material Creation Mode:定义希望Unity如何为模型生成或导入材质
- None:不使用此模型中嵌入的任何材质,改用Unity的默认漫反射材质
- Standard:默认规则
- Import via MaterialDescription(Experimental):用FBX文件中嵌入的描述来生成材质
- sRGB Albedo Colors:是否在伽马空间中使用反射率颜色
- Location:定义如何访问材质和纹理
- Use Embedded Materials:将导入的材质保持在导入的资源中
- Use External Materials(Legacy):将导入的材质提取为外部资源
- Textures/Materials:当选择Use Embedded Materials后会出现的内容,这两个按钮可以提取导入的资源中嵌入的所有材质和纹理
- On Demand Remap
- Naming:定义材质的命名规则
- By Base Tecture Name:使用导入材质的漫反射纹理名称来命名材质
- From Model’s Material:使用导入材质的名称来命名材质
- Model Name + Model‘s Material:使用模型文件的名称与导入材质的名称相结合来命名材质
- Search:定义在使用Naming选项定义的名称时查找现有材质的位置规则
- Local Materials Folder:在本地Materials材质子文件夹中查找现有材质
- Recursive-Up:在所有父文件夹中所有Materials子文件夹中查找现有材质
- Project-Wide:在所有Unity项目文件夹中查找现有材质
- EthanWhite:重新映射的材质。如果找到了,Unity会自动关联;如果没有找到,我们可以手动进行关联
- Naming:定义材质的命名规则
动画基础
窗口相关
Animation 动画窗口
参数相关
Animation
打开:Window -> Animation -> Animation
动画文件
动画文件(Animation Clip)出现在Assets中,选择后可以在Inspector窗口中查看参数。
- Loop Time:是否是循环动画
- Loop Pose:无缝循环动画
- Cycle Offset:循环动画在其他时间开始时的周期偏移
另外,还可以按右方三点切换为Debug模式,在这里可以设置帧率和播放模式相关。
创建编辑动画及动画事件
创建动画
- 在场景中选中想要创建动画的对象
- 在
Animation窗口中点击创建 - 选择动画文件将要保存到的位置
注意:保存动画文件时,Unity会帮助我们完成以下操作
- 创建一个动画状态机(
Animator Controller)资源 - 将新创建的动画文件添加到
Animator Controller中 - 为动画对象添加
Animator组件 - 为
Animator组件关联创建的Animator Controller文件
创建完成后Animation窗口会发生以下变化:
- 多出一个可以选择当前动画的下拉列表,可以在这里创建新动画和切换动画
- 多出一个可以添加变化属性的按钮
编辑动画
关键帧模式
- 方法一:点击录制模式 ->
Add Property添加变化属性 -> 在想要改变的时刻添加关键帧,改变上面的值 - 方法二:点击录制模式 -> 直接在
Animation窗口中选择要改变的时间节点,直接在Inspector窗口中改变
- 方法一:点击录制模式 ->
曲线模式
选择Animation窗口下方Curves即可切换。
选择关键帧后右键会出现其他可选操作:
- Add Key:添加关键帧
- Delete Key:删除关键帧
- Clamped Auto :切线模式,自动设置切线
- Auto:旧版切线模式(建议不使用,除非是老动画)
- Free Smooth:自由设置切线
- Broken:单独控制左右曲线切线
下方的xxx Tangent代表选择的曲线,其中的参数对应如下:
- Free 自由设置
- Linear:直线
- Constant:常量切换
- Weighted:权重切换
动画事件
动画事件主要用于处理当动画播放到某一时刻,想要触发某些逻辑,比如进行伤害监测、发射子弹、特效播放等等。
可以在Inspector窗口中选择函数,设置参数。
代码相关
- 得到Animation脚本
1 | private Animation animation; |
- 播放动画
1 | // 参数:动画名 |
- 淡入播放,自动产生过渡效果(当要播放的动画的开始状态和当前的状态不一样时产生)
1 | animation.CrossFade("动画2"); |
- 前一个播放完再播放下一个
1 | animation.PlayQueued("动画2"); |
还有一种有过渡效果的方式:
1 | animation.CrossFadeQueued("动画2") |
- 停止播放所有动画
1 | animation.Stop(); |
- 判断是否在播放某个动画
1 | if( animation.IsPlaying("动画1") ) |
- 播放模式设置
1 | animation.wrapMode = WrapMode.Loop; |
其他不重要的API:
设置层级:
animation["动画1"].layer = 1;设置权重:
animation["动画1"].weight = 1;混合模式(叠加 or 混合):
animation["动画1"].blendMode = AnimationBlendMode.Additive;设置混合相关骨骼信息:
animation["动画1"].AddMixingTransform();
Animator 动画状态机
Animator状态机是 Unity 用于管理游戏对象多段动画切换逻辑的核心系统。
界面参数相关
- 创建动画状态机
- 自动创建:通过为场景中物体创建
Animation - 手动创建:Project -> Create -> Animator Controller
- 功能:
左侧功能板
- Layer:动画层级页签,为动画添加更多层级。层级权重大的会覆盖低层。
- Parameters:参数页签,为动画状态机添加控制状态切换的参数。
右侧网格
右侧网格主要用于编辑状态之前的切换关系。其中:每一个矩形表示一个状态,每一个箭头表示一个切换条件。
其中,不同颜色的矩形表示:
| 矩形 | 表示 |
|---|---|
| 绿色Entry | 进入状态机流程 |
| 红色Exit | 退出状态机流程 |
| Any State | 任意状态 |
| 橙色 | 一开始的默认状态动画 |
| 灰色 | 自己添加的某一种状态 |
窗口上还可以进行这些操作:
- 添加动画:
- 自动添加:为对象创建动画后自动将动画添加到状态机种
- 手动添加:
- 将动画文件拖入到状态机中
- 右键创建状态,再关联动画
- 添加切换连线:
- 创建连线:右键
- 设置为默认状态:右键
点一下连线后Inspector窗口中会有新的参数。其中,Has Exit Time是指有没有退出上一个动画状态时间,如果勾选上会有一定的延时。一般取消勾选。
- 添加切换条件:
- 首先在
Animator左侧窗口的Parameters参数页签中添加可以改变的参数; - 然后点击连线,再右侧
Inspector窗口中的Conditions设置条件。
条件类型及条件如下:
| 条件类型 | 条件 |
|---|---|
float |
大于Greater \ 小于Less 某个值 |
int |
大于Greater \ 小于Less \ 等于Equals \ 不等于 NotEqual某个值 |
bool |
真true \ 假false |
trigger |
触发一次后会变回原样 |
Animator
- Controller:对应的动画控制器(状态机)
- Avatar:对应的替身配置信息
- Apply Root Motion:是否启用动画位移更新
- UpdateMode:更新模式(一般不修改)
- Normal:正常更新
- Animate Physics:物理更新
- Unscaled Time:不受时间缩放影响
- Culling Mode:裁剪剔除模式
- Always Animate:始终播放动画(在屏幕外也不剔除)
- Cull Update Transforms:摄像机没有渲染该物体时,停止位置、IK的写入
- Cull Completely:摄像机没有渲染物体时,整个动画被完全禁用
动画分层和遮罩
动画分层就是将动画分成若干层,比如全身层、上半身层、表情层、武器层等等。它的作用是可以提升动画多样性,节约资源。
使用动画分层要新建一个动画层:Animator -> Layers -> 右侧加号 -> 点击齿轮设置动画层参数
- Weight:权重。当动画同时播放时,如果选择的是叠加状态,会根据权重决定叠加的比例
- Mask:动画遮罩。在Inspector窗口:Create -> Avatar Mask。再关联到上方,该层动画全部都会受该层遮罩的影响
- Blending:混合方式
- Override:覆盖方式,播放该层动画时会忽略其它层信息
- Additive:叠加方式,会和其它层动画叠加播放
- Sync:是否同步其他层。将另一层的状态复制过来,在该层中进行修改;选择后多一个
Source Layer表示要复制哪一层的状态 - Timing:当选中Sync时,该参数激活。选中,会采用折中方案调整同步层上的动画时长(基于权重计算);不选中,动画时长将使用原始层作为模板
- IK Pass:是否开启反向动力学
其中,Weight可以通过代码修改:
1 | public class Test : MonoBehaviour |
状态参数相关
我们可以选中状态机窗口中的某一个状态为其设置相关参数,比如当前状态的播放速度等等细节,具体参数如下:
- Motion:分配给此状态的动画剪辑
- Speed:动画的默认速度
- Multiplier:控制速度的乘数。如果要使用需要勾选的Parameter选中配合的参数
float类型
- Multiplier:控制速度的乘数。如果要使用需要勾选的Parameter选中配合的参数
- Motion Time:运动的时间。如果要使用需要勾选的Parameter选中配合的参数
float类型 - Mirror:是否生成镜像
- Cycle Offset:循环偏移时间
- Foot IK:是否遵循Foot IK
- Write Defaults:默认勾选。AnimatorStates是否为其运动执行未动画化的属性写回默认值
- Transitions:过渡
- Solo:仅播放该过渡
- Mute:禁用过渡(如果和Solo一起选择会优先执行)
还能设置状态和状态之间的连线上的参数:
- 黑条:为动画过渡改名。不改默认“某一状态名” -> “某一状态名”
- Has Exit Time:是否有退出时间
- Settings:设置
- Exit Time:退出时间。决定了过渡生效的准确时间。如果小于1,比如0.85,播放了85%的动画时过渡;如果大于1,比如4.5,动画循环4.5次后过渡
- Fixed Duration:选中后,下方的Transition Duration过渡持续时间将以秒为单位解读过渡时间;如果不选中,则以百分比解读过渡时间
- Transition Duration(s):过渡持续时间。对应下方两个蓝色箭头包裹区域
- Transition Offset:过渡到目标状态的起始播放的时间偏移
- Interruption Source:该过渡中断的情况
- None:不再添加任何过渡
- Current State:将当前状态过渡排队
- Next State:使下一个状态的过渡进行排队
- Current State Then Next State:将当前的过渡和下一个状态的过渡依次排队
- Next State Thne Current State:将下一状态的过渡和当前状态的过渡依次排队
- Ordered Interruption:当前过渡是否可在不考虑顺序的情况下被其他过渡中断。选中,找到有效过渡或当前过渡时,会中断;不选中,找到有效过渡,会中断
- Conditions:过渡条件
AnyState和状态之间的连线多出了几个参数:
- Can Transition To Self:是否可以过渡到自己
- Preview source state:预览各种过渡状态。可以查看任意状态切换到当前状态的过渡效果
动画混合
动画混合就是让多个动画片段(比如走路、跑步、转向)根据你设定的参数(比如移动速度、方向)自动平滑过渡、融合,避免动画切换时的僵硬感,让角色动作更自然。
要使用动画混合需要创建混合树(在Animator Controller窗口右键 -> Create State -> From New Blend Tree -> 双击Blend Tree),混合树分为1D混合树和2D混合树。
1D动画混合树
1D动画混合树就是通过一个参数来混合子运动,是线性的。注意:往混合树里面加动作时要找到动画文件进行关联。接下来介绍参数相关:
- Parameter:参数。用于控制混合的参数,在参数列表中的参数
- 蓝色图像:可以在这里控制n个动画的阈值
- Motion:关联的动画列表。可以用鼠标改变顺序
- Threshold:对应动作的临界阈值。当等于这个值时动作权重最大
- Automate Thresholds:是否自动设置阈值。他会在取值范围内平均分,一般可以取消勾选,我们手动控制更准确
- Compute Thresholds:计算阈值的方式
- Speed:速度
- Velocity X、Y、Z:xyz上的分速度
- Angular Speed(Rad、Deg):角度或弧度表示的角速度
- Adjust Time Scale:调整时间刻度
- Homogeneous Speed:均匀速度
- Reset Time Scale:重置时间刻度
2D动画混合树
2D动画混合树就是通过两个参数来混合子运动,是平面的。2D动画混合树还分好几个种类:
| 种类 | 表示 |
|---|---|
| 2D Simple Directional(2D简单定向模式) | 不同方向的前、后、左、右走 |
| 2D Freeform Directional(2D自由形式定向模式) | 不同方向的运动。不过同一方向上有多个运动,比如向前跑和走 |
| 2D Freeform Cartesian(2D自由形式笛卡尔坐标模式) | 不表示所有动作。比如向前走不拐弯、向前跑不拐弯,向前走 |
| Direct(直接模式) | 自由控制每个节点权重,一般做表情动作等 |
子状态机、行为脚本及状态机复用
子状态机是在状态机中的状态机。他的主要作用是可以排列组合一系列连续的动作。
创建子状态机:在Animator Controller窗口右键 -> Create Sub-State Machine
状态机行为脚本
状态机行为脚本是一类特殊的脚本,继承指定的基类StateMachineBehaviour。
我们可以为Animator Controller状态机窗口中的某一个状态添加一个脚本,利用这个脚本我们可以做一些特殊功能。比如:进入或退出某一状态时播放声音;仅在某些状态下检测一些逻辑,比如是否接触地面等等;激活和控制某些状态相关的特效。
| 方法名 | 调用时机 |
|---|---|
| OnStateEnter | 进入状态时,第一个Update中调用 |
| OnStateExit | 退出状态时,最后一个Update中调用 |
| OnStateIK | OnAnimatorIK后调用 |
| OnStateMove | OnAnimatorMove后调用 |
| OnStateUpdate | 除第一帧和最后一帧,每个Update上调用 |
| OnStateMachineEnter | 子状态机进入时调用,第一个Update中调用 |
| OnStateMachineExit | 子状态机退出时调用,最后一个Update中调用 |
值得一提的是,状态机能够复用。状态机复用就是创建一个状态机,为不同对象使用共同的行为,减少工作量,提升开发效率。
状态机复用的方法如下:
- 在Project窗口右键 Create -> Animator Override Controller
- 在Inspector窗口为 Animator Override Controller文件关联想要复用的状态机文件
- 关联需要的动画
动画IK
在状态机层级设置中开启IK通道:Animator -> Layers -> 勾选IK Pass
继承MonoBehavior的类中,Unity定义了一个IK回调函数:OnAnimatorIK,我们可以在该函数中调用Unity提供的IK相关的API来控制IK。运用IK后可以制作拾取物品、拿枪等操作。
头部相关API
- 设置头部IK权重
1 | // weight:LookAt全局权重0~1 |
- 设置头部IK看向位置
1 | // 参数1:想要看向的点的位置 |
四肢相关API
- 设置权重
1 | AvatarIKGoal 四肢末端IK枚举 |
- 设置IK位置权重
1 | animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1); |
- 设置IK旋转权重
1 | animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1); |
- 设置IK对应的位置
1 | animator.SetIKPosition(AvatarIIKGoal.RightHand, pos2.position); |
- 设置IK对应的角度
1 | animator.SetIKRoatation(AvatarIIKGoal.RightHand, pos2.position); |
关于OnAnimatorIK和OnAnimatorMove两个函数的理解:
可以将这两个函数理解为两个和动画相关的特殊的生命周期函数。他们在Update之后和LateUpdate之前调用,他们会在每帧的状态机和动画处理完后调用。OnAnimatorIK在OnAnimatorMove之前调用。
OnAnimatorIK主要处理:IK运动相关逻辑;
OnAnimatorMove主要处理:动画移动以修改根运动的回调逻辑
代码相关
- 通过不同状态切换动画
1 | string animName = "idle"; // 表示玩家的状态 |
- 设置状态机条件
1 | // 参数1:条件名 |
- 得到状态机条件对应的值
1 | animator.GetFloat("条件名"); |
- 直接播放动画(无视过渡条件)
1 | animator.Play("状态名"); |
老动画系统
Unity中有两套动画系统,新(Mecanim动画系统——主要用Animator组件控制动画),老(Animation动画系统——主要用Animation组件控制动画)。有特殊需求或者针对一些简易动画时才会使用老动画系统。
老动画系统控制动画播放:为对象添加
Animation组件 -> 再创建动画Animation参数
- Animation:默认播放的动画
- Animations:该动画组件可以控制所有的动画
- Play Automatically:是否一开始就自动播放默认动画
- Animate Physics:动画是否与物理交互
- Culling Type:决定什么时候不播放动画
- Always Animate:始终播放
- Based On Renderers:基于默认动画姿势剔除
- 动画文件参数
- Default:读取设置的更高的重复模式
- Once:播放一次就停止
- Loop:从头到尾循环播放
- PingPong:从头到尾从尾到头不停播放
- ClampForever:播放结束会停在最后一帧,并且一直播放最后一帧
2D动画
序列帧动画
序列帧动画的实现方式是:以固定的时间间隔按序列切换图片,当固定的时间间隔足够短时,肉眼会认为图片是连续动态的,进而形成动画。
序列帧动画的制作方式是:创建一个空物体 -> 创建一个动画(可以修改动画帧率)-> 拖入一个动作的序列帧
Animator中的动画控制:
1 | public Animator animator; |
2D骨骼动画
2D骨骼动画就是将一张2D图片分割为n个部位,为每个部位帮上骨骼,控制骨骼旋转移动。占用资源较少。
2D骨骼动画的制作方式有2种,一种是Unity自带的工具——2D Animation;还有一种是第三方工具Spine。
2D Animation
参数相关
通过Insprctor窗口中的Sprite Editor按钮可以进入Sprite Editor面板。
在这个界面上方有这些按钮:
| 顶部按钮 | 作用 |
|---|---|
| Reset Pose | 将角色骨骼和关节恢复到原始位置 |
| Sprite Sheet(PSB特殊的选项) | 图集显示 |
| Copy | 复制当前选择的数据 |
| Paste | 粘贴复制数据 |
左侧边栏里有这写参数:
- Bone骨骼相关
| 按钮 | 作用 |
|---|---|
| Preview Pose | 预览模式,可以预览动作并不会真正的改变位置 |
| Edit Bone | 编辑骨骼,可以改变骨骼的位置、长度、方向、名称等等。删除骨骼:选中想要删除的骨骼,delete |
| Create Bone | 创建骨骼。双击选中图片(出现橙色边缘线)-> 创建根骨骼,默认第二根骨骼以第一根骨骼的终点为起始位置(右键可以取消);可以点击其中一根想要用作父对象的骨骼,点击一下根部,再右键创建 |
| Split Bone | 拆分骨骼,将一个骨骼一分为二 |
- Geometry蒙皮相关
作用:决定了骨骼主要控制哪一部分。
| 按钮 | 作用 |
|---|---|
| Auto Geometry 自动蒙皮 | Outline Detail 边缘细节,值越大,轮廓越细致;Alpha Tolerance 阿尔法公差值,控制蒙皮细节;Subdivide 细化程度,控制蒙皮细节;Weights 是否自动设置权重,一般勾选。 |
| Edit Geometry 编辑蒙皮 | 用自动蒙皮后可以细调一下三角形面片的顶点 |
| Create Vertex 创建顶点 | |
| Create Edge 创建边线 | |
| Split Edge 拆分边 | 用一个新的顶点分离一个边 |
- Weights权重相关
作用:决定了当骨骼动时如何影响顶点和边。
| 按钮 | 作用 |
|---|---|
| Auto Weights 自动赋予权重 | |
| Weight Slider 编辑顶点和边的权重 | Mode 计算模式(Add And Subtract 加减法、Grow And Shrink 增长和收缩、Smooth 平滑);Bone 设置骨骼;Normalize 标准化设置;Amount 数量级;Vertex Weight 顶点权重对应的骨骼 |
| Weight Brush 用笔刷赋予权重 | Size 笔刷大小;Hardness 笔刷强度,越大效果越明显;Step 步数 |
| Bone Influence 骨骼的影响(PSB) | 点开后可以在右下角选择受不受某一根骨骼影响。本身没有骨骼的部分也可以选择受骨骼影响来跟着身体一起动。 |
注意:编辑完后一定要点一下Apply
骨骼动画制作相关
单张图片
进行骨骼创建:Sprite Editor -> Skinning Editor
图集
- 对图集每一部分进行单张图片的骨骼设置
- 拖到场景中时要创建一个根物体,然后把身体上的脊椎骨骼作为左右胳膊以及头的父物体,左右腿和身体同层。注意根据图片设置层级关系
- 改变整体大小时建议改变视口大小
注意:要把Sprite设置为图集模式,对图集图片进行切片
Psb图集
Psb是用PS处理后得到的一种文件格式
要使用PSB文件:Packages Manager -> 2D PSD Importer -> Install
Inspecto窗口会多出来一些参数:
- Extrude Edges:图片边缘延伸网格
- Import Hidden:是否导入psb文件中隐藏的图层
- Moszic:启用后,将图层生成Sprite,并将他们合并成单个纹理
- Character Rig:是否使用人物已经绑定的骨骼
- Use Layer Grouping:使用psb文件中的层分组
- Pivot:轴心点位置
- Reslice:从导入层重新生成Sprite,并清除对Sprite的任何更改,只有启用Moszic后有用
- Keep Duplicate Name:让Sprite名称保留PSB文件中的名字
反向动力学IK
反向动力学IK简单来说就是和正常动力学的知识相反的一种理解方式:从骨骼末端来控制整个骨骼运动。它的作用是提升动画的真实感。
导入:Window -> Package Manager -> 2D IK;Advanced -> Show Preview packages
使用时为根对象添加一个IK Manager 2D脚本,在想要产生反向动力学的骨骼末端添加一个空物体。
以下是IK Manager 2D的参数:
- IK Solvers:IK解算器,在这里添加IK
- Chain(CCD):可以自定义影响N个关节点,不能反向
- Chain(FABRIK):可以自定义影响N个关节点,可以反向
- Limb:只会影响3个关节点
- Weight:权重,当有多个IK控制同一点是权重会影响控制的百分比
CCD模式
特点:不能完全反向,有一个临界值
| 参数 | 作用 |
|---|---|
| 效应器(Effector) | 默认的IK位置 |
| 目标(Target) | 会根据IK点位置生成真正的IK点对象 |
| 链长(Chain Length) | IK影响的骨骼数量。选择好后要点击Create Target |
| 算法运行的次数(Iterations) | |
| 容错程度(Tolerance) | |
| 速度(Velocity) | |
| 约束旋转(Constrain Rotation) | |
| 从默认姿势进行求解计算(Solve from Default Pose) | |
| 权重(Weight) |
点击Restore Default Pose是重置位置
FABRIK模式
特点:可以完全反向
Limb模式
特点:主要是在四肢上运用
| 参数 | 作用 |
|---|---|
| 是否反向(Flip) | 勾选后反向 |
换装
换装时有两种情况:一种是换装资源在同一个文件中,还有一种是换装资源在不同文件中。选择两种方案时,根据换装的多少选择:换装较少的,如只有面部表情更换 -> 同一psb文件;换装较多的,各部位有n种装备 -> 不同psb文件。接下来一一讲解:
换装资源在同一个文件中
- 在同一个PSB文件中制作换装资源
- 在ps中制作时,将一个游戏对象的所有换装资源都摆放好位置
- 当导入该资源时,要注意是否导入隐藏的图层
- 编辑换装骨骼信息
- 在右侧Sprite中可以隐藏暂时不想看到的资源
- 资源太多的情况下可以不分别做骨骼,先大致画出骨骼(配合隐藏使用),如果有一个部位骨骼全都没关联,再关联时没有颜色,要点一下Weight中的Auto Weights来生成权重
- 蒙皮后再一一调整:可以在Wight中选择第四个选项看具体和哪些骨骼关联
- 换装准备
- 在
Sprite Editor面板右侧,点击Sprite,可以在下面添加Category类别,方便之后换装
- 在
- 设置好的换装图片会添加两个关键组件和一个数据文件:
- 两个关键组件:
- Sprite Librery(精灵资料库):确定类别分组信息
- Sprite Resolver(精灵解算器):用于确定部位类别和使用的图片
- 一个数据文件:
- Sprite Library Asset(精灵资料库资源):具体记录类别分组信息的文件
- 两个关键组件:
代码换装
- 单个部位的切换
1 | using UnityEngine.Experimental.U2D.Animation; |
优化:不用外部拖入关联
- 多个部位的切换
1 | using UnityEngine.Experimental.U2D.Animation; |
换装资源在不同文件中
- 先决条件:保证各个部位在ps文件中位置的统一,基础部位可选择性隐藏
- 先编辑第一张图片的骨骼信息 -> 点击预览模式 -> Copy
- 在点击第二张图片 -> Paste -> 右下角粘贴Bones(取消Mesh蒙皮)
- 手动添加关键组件:
- 创建根物体后拖入
- Project -> Create ->
Sprite Library Asset资料库资源 -> 创建类别,关联图片 - 为根对象添加
SpriteLibrary资料库并关联Sprite Library Asset资料库资源 - 为换装部位关联
Sprite Resolver解算器 -> 选择类别
Spine
Spine是一个收费的跨平台的2D骨骼动画制作工具,我们只用学习使用Spine制作的资源。
导入官方运行库
进入Spine官网 -> 点击上方 运行库 选项 -> 左侧 官方运行库 -> 点击 Unity版本的运行库 -> 下载最新版认识和使用Spine骨骼动画
- Spine导出的Unity资源
- Spine导出的资源有3个文件:
.json骨骼信息、.png使用的图片图集、.atlas.txt图片在图集中的位置信息 - 导入Unity后生成的:
_Atlas材质和.atlas.txt文件的引用配置文件、_Material材质文件、_SkeletionData json_和_Atlas资源的引用配置文件
- Spine导出的资源有3个文件:
- 使用Spine导出的骨骼动画
- 方式1:将
_SkeletonData文件拖入到Hierarchy窗口 -> 选择创建SkeletonAnimation对象(其中,SkeletonMecanim是创建动画状态机) - 方式2:创建空对象,手动添加脚本
SkeletonAnimation,关联相应动画
- 方式1:将
参数相关
_SkeletonData 骨骼数据文件
- SkeletonData JSON:骨骼数据文件
- Scale:缩放大小
- Skeleton Data Modifiers:骨骼数据修改器
- Blend Mode Materizls:混合模式材质
- Apply Additive Material:是否使用叠加材质
- Additive Materials:叠加材质
- Multiply Materials:相乘材质
- Screen Materials:屏幕材质
- Atlas Assets:图集资源
- Mix Settigs:混合设置
- Animation State Data:动画状态数据
- Default Mix Duration:默认混合持续时间
- Add Custom Mix:添加自定义混合
Preview 预览
- Animations:动画
- Setup Pose:设置姿势
- Create Animation Reference Assets:创建动画参考资源
- Slot:插槽相关
- SkeletonMecanim:骨骼机制
- Controller:关联动画控制器
- Skin:蒙皮选择
SkeletonAnimation 骨骼动画脚本
- SkeletonData Asset:关联的骨骼动画信息
- Animation Name:当前播放的动画名
- Loop:是否循环
- Initial Skin:初始蒙皮
- Time Scale:事件缩放
- Root Motion:是否添加根运动的脚本
Advanced 高级
- Initial Filip X/Y:初始翻转X和Y
- Update When Invisible:不可见时是否更新
- Nothing:不更新
- Only Animation Status:仅动画状态
- Only Event Timelines:仅事件
- Everything Except Mesh:除了网格其他都更新
- Full Update:更新所有
- Use Single Submesh:使用单个子网格
- Fix Draw Order:固定提取顺序
代码相关
- 得到脚本
1 | using Spine.Unity; |
- 控制动画播放:
1 | // 方法1:直接改变SkeletonAnimation中的参数 |
- 转向(-1:向左转, 1:向右转):
1 | sa.skeleton.ScaleX = -1; |
- 动画播放时添加事件:
1 | // 动画开始播放 |
- 便捷特性:
| 特性 | 作用 |
|---|---|
[SpineAnimation] 动画特性 |
可以用在AnimationName前,在Inspector窗口中可以直接选择 |
[SpineBone] 骨骼特性 |
可以用在BoneName前,可以在Inspector窗口中直接选择要用的名字 |
[SpineSlot] 插槽特性 |
可以用在SlotName前,可以在Inspector窗口中快捷得到插槽名字 |
[SpineAttachment] 附件特性 |
可以用在attachmentName前,配合插槽使用,可以在Inspector窗口中直接选择附件的名字 |
- 获取骨骼
1 | // boneName可以通过特性准确得到 |
- 设置插槽附件:
1 | sa.skeleton.SetAttachment(slotName, attachmentName); |
在UI中使用骨骼对象:将_SkeletonData文件拖入到Hierarchy窗口 -> 选择创建SkeletonGraphic(UI)对象,再创建一个UI -> Image (得到Canvas),将SkeletonGraphic(UI)对象拖入其下方,可以在UI界面控制
动作目标匹配
当游戏中角色要以某种动作移动,该动作播放完毕后,人物的手或者脚必须落在某个地方。比如,角色需要跳过脚踏石或者跳跃并抓住房梁。
接下来是实现动画目标匹配的步骤:
- 找到动作关键点位置信息(比如起跳点、落地点,简单理解就是真正可能产生位移的动画表现部分),在此处创建一个空物体
- 将关键信息传入
MatchTargetAPI中
1 | private void MatchTarget() |
注意:调用匹配动画的时机有一些限制,如果发现匹配不正确往往是前两个原因。
- 必须保证动画已经切换到了目标动画上
- 必须保证调用时动画并不是处于过渡阶段,而真正在播放目标动画
- 需要开启
Apply Root Motion
CharacterController 角色控制器
角色控制器是专门给人形角色设计的移动组件,自带碰撞、地面检测、斜坡处理,不用物理也能稳定移动,最适合玩家控制。
注意:
- 添加角色控制器后,不用再添加刚体
- 能检测碰撞、触发器函数
- 能被射线检测
参数相关
- Slope Limit:坡度度数限制,大于该值的斜坡上不去
- Step Offset:台阶偏移值,单位为米。低于这个值的台阶才能上去,该值不能大于角色控制器的高度
- Skin Width:皮肤的宽度。两个碰撞体可以穿透彼此最大的皮肤宽度,较大的值可以减少抖动,较小的值可能导致角色卡住,可以设置为半径的10%
- MinMoveDistance:最小移动距离。大多数情况下为0,可以用来减少抖动
注意:需要将Inspector窗口中Animator脚本上的Apply Root Motion取消勾选
代码相关
1 | public class Test : MonoBehaiour |
导航寻路系统
导航寻路系统是能够让我们在游戏世界当中,让角色从一个起点准确的到达另一个终点,避开两点间的障碍物选择最近最合理的路径的系统。本质是在A星寻路算法的基础上进行了拓展和优化。
导航寻路系统需要两个核心组件:NavMesh(烘焙后的可行走地面)、NavMesh Agent(角色寻路组件)。接下来一一讲解。
NavMesh 导航网格
使用NavMesh可以生成角色在场景中自动寻路的地形数据。需要安装包:Window -> PackageManager -> AI Navigation,然后打开Window -> AI -> Navigation
Agents 代理页签
代理页签主要用于设置寻路代理信息。
- Radius:代理半径。决定了烘焙边缘精确度,控制平台可行走区域和边缘可行走区域
- Height:代理高度。决定了烘焙高度精确度,控制拱桥是否可以穿越
- Step Height:最小楼梯高度。决定了台阶是否可以行走
- Max Slope:最大坡度。决定了斜坡是否可以行走
- Generated Links:生成连接。两个分开的网格之间连接的相关设置
- Drop Height:掉落高度。可以从这个高度掉下来
- Jump Distance:跳跃距离。决定不同平面上的跳跃距离
Areas 导航地区页签
导航地区页签主要用于设置对象的寻路消耗。
- Name:区域名字
- Cost:寻路消耗
NavMesh Agent 导航网格寻路
NavMesh Agent可以生成自动寻路路径。
参数相关
- Agent Type:代理类型
- Base Offset:基础偏移值。相对对象轴心点的高度偏移
- Steering:移动设置
- Speed:寻路时的最大移动速度(世界单位/秒)
- Angular Speed:寻路时转身的最大旋转速度(度/秒)
- Acceleration:最大加速度(世界单位/平方秒)
- Stopping Distance:当靠近目标点多少距离时,停止运动
- Auto Braking:自动制动(减速)。启用后,当到达目标时将减速,如果存在连续移动(比如巡逻)建议不要开启此选项
- Obstacle Avoidance:避障设置
- Radius:半径。用于计算障碍物和其他寻路对象之间的碰撞
- Height:高度。通过头顶障碍物时用于计算高度间隙使用
- Quality:障碍躲避品质。越高越精确,但是性能消耗较大。如果不想主动避开其他动态障碍,可以设置为无,这样只会解析碰撞
- Priority:优先级。0~99,避障时,数字较小的障碍物表示较高的优先级,优先级低的会忽略避障
- Path Finding:路径寻找规则
- Auto Traverse Off Mesh Link:是否开启自动遍历网格外的其他网格连接。如果要自定义判断,则关闭此功能
- Auto Repath:是否开启自动重设路线。如果开启,当到达路径后段时会再次尝试寻路;当没有到达目标的路径时,会生成一条到达与目标位置最近的可达点
- Area Mask:寻路时考虑的区域。
代码相关
- 设置目标点并自动寻路
1 | agent.SetDestination(); |
- 停止寻路
1 | agent.isStopped = true; |
- 面板参数相关(期望速度上限、加速度、旋转速度)
1 | agent.speed |
- 当前是否有路径
1 | if( agent.hasPath ) |
- 代理目标点(可以设置、得到)
1 | agent.destination |
- 当前路径
1 | agent.path |
- 路径是否在计算中
1 | if( agent.pathPending ) |
- 路径状态
1 | agent.pathStatus |
- 是否更新位置
1 | agent.updatePosition = true; |
- 是否更新角度
1 | agent.updateRotation = true; |
- 实际速度大小
1 | agent.velocity |
- 手动寻路时计算生成路径
1 | NavMeshAgent path = new NavMeshAgent(); |
- 设置新路径
1 | if( agent.SetPath(path) ) |
- 清除路径
1 | agent.ResetPath(); |
- 调整到指定点位置
1 | agent.Warp(Vector3.zero); |
以下是设置目标点并自动寻路和停止寻路的实例:
1 | using UnityEngine.AI; |
Off Mesh Link 导航网格连接
当地形中间有断层,想让角色能从一个平面跳向另一个平面(有限条路径、可在运行时动态添加)
- Start:起始点
- End:结束点
- Cost Override:覆盖消耗值。负数或0则使用所属Area区域中的消耗值;如果为正数,则使用Area区域寻路消耗值
- Bidirectional:是否开启双向连接点。
- Activated:是否启用该连接点。关闭相当于自动寻路失效
- Auto Update Positions:是否自动更新位置。如果启用,当开始和结束位置改变时,导航网格也将更新;如果不启用,即使改变了开始结束位置,也会按照刚开始的位置进行计算
使用时需按照以下步骤完成:
- 使用两个对象作为两个平面之间的连接点(起点和终点)
- 添加
Off Mesh Link脚本进行关联(起点或终点或空物体上) - 设置参数
NavMesh Obstacle 导航网格动态障碍物
NavMesh Obstacle是挂载在地形中可以移动或动态销毁的障碍物上的组件
- Shape:动态障碍的形状
- Carve:是否开启雕刻功能。开启后,障碍物会在导航网格中挖一个孔(生成对应的网格信息,认为这片区域无法前往)。如果障碍物固定不动,建议开启;如果时频繁移动的,建议不开启
- Move Threshold:移动阈值。当障碍物移动超过该距离时,会认为其为移动状态,更新移动的空
- Time To Stationary:障碍物作为静止状态需要等待的时间。当静止时间超过该值认为真正静止了
- Carve Only Stationary:只有在静止状态时才会计算孔
进阶
ScriptableObject 配置基类
ScriptableObject是Unity提供的一个数据配置存储基类。他是一个可以用来保存大量数据的数据容器,就像是一个可以自定义的数据资源文件。
由于它是一个基类,我们需要继承它来进行使用。它的主要作用有:
- 数据复用:如果继承
MonoBehaviour,每一次实例化的时候都会对其中的成员变量进行内存分配;但是通过这种方式就只需要分配一次内存,可以避免内存的浪费。 - 配置文件:直接在Inspector窗口配置,更方便。
- 编辑模式下的数据持久化:可以在编辑模式下实时修改数据,会被保存。
创建和使用
创建
创建一个ScriptableObject文件,需要先声明一个模板类(数据容器):一个继承ScriptableObject的类,其中有成员(变量、方法等),这个模板类里面的成员会当做默认设置。
然后为它添加一个特性:
1 | [] |
使用
- 不使用资源加载的方式:
在继承MonoBehaviour类中申明数据容器类型的成员,然后再在Insector窗口中进行关联。
- 使用资源加载的方式:
1 | data = Resources.Load<MyData>("MyDataTest"); |
另外,ScriptableObject也存在生命周期函数,不过用的都比较少。
持久化相关
非持久化数据
非持久化数据指的是在编辑器模式和发布后都不会持久化的数据。它的好处是只在运行时使用,不会创建文件来占用内存空间。
使用方式就是不要在Inspector窗口中去配置,而是通过代码改变。
1 | class Test : MonoBehaviour |
注意:这种方法不会自动关联模板类在Inspector窗口中关联的默认数据。
持久化操作
这里用json来举例,以下是存储操作:
1 | using System.IO; |
接着来看读取操作:
1 | string str = File.ReadAllText(Application.persistentDataPath + "/testJson.json"); |
InputSystem 输入系统
InputSystem 是Unity提供的一种新的输入系统。
要想使用InputSystem,需要导入一个InputSystem包。Window -> Package Manager -> Unity Registry -> InputSystem(点击Yes后会启用新输入系统,关闭老输入系统)
当然,也可以自行选择要开启哪一种模式:File -> Build Setting -> Player Setting -> Other -> Active Input Handling。
代码检测输入
新输入系统提供了对应的输入设备类,可以帮助我们对某一种设备输入进行检测。
获取这些类需要引用对应的命名空间:
1 | using UnityEngine.InputSystem; |
注意:得设备后需要进行逻辑处理之前最好先进行判空
键盘输入
- 获取当前键盘设备
1 | Keyboard keyBoard = Keyboard.current; |
- 单个按键按下
1 | // A键按下 |
- 单个按键抬起
1 | // 空格键抬起 |
- 单个按键长按
1 | // 回车键长按 |
- 任意键按下(抬起和长按的API和单个按键一样)
1 | if(keyBoard.anyKey.wasPressedThisFrame) |
另外,我们还可以通过事件监听按键按下。这样做的好处是可以有一触即发、多处响应的效果。
1 | // 往事件里添加函数 |
鼠标输入
- 获取当前鼠标设备
1 | Mouse mouse = Mouse.current; |
- 按下
1 | // 左键 mouse.leftButton |
- 抬起
1 | if(mouse.leftButton.wasRealeasedThisFrame) |
- 长按
1 | if(mouse.leftButton.isPressed) |
- 获取当前鼠标位置
1 | mouse.position.ReadValue(); |
- 获取鼠标相对于上一帧的偏移量
1 | mouse.delta.ReadValue(); |
- 鼠标中键滚轮的方向向量
1 | mouse.scroll.ReadValue(); |
触屏输入
- 获取当前鼠标设备
1 | Touchscreen ts = Touchscreen.current; |
- 得到触屏手指数量
1 | ts.touches.Count |
- 得到单个触屏手指
1 | using UnityEngine.InputSystem.Controls; |
- 得到所有触屏手指
1 | foreach(var item in ts.touches) |
- 手指按下
1 | if(tc.press.wasPressedThisFrame) |
- 手指抬起
1 | if(tc.press.wasReleasedThisFrame) |
- 手指长按
1 | if(tc.press.isPressed) |
- 点击手势
1 | if(tc.tap.isPressed) |
- 连续点击次数
1 | tc.tapCount |
- 获取手指位置
1 | tc.position.ReadValue() |
- 第一次接触时位置
1 | tc.startPosition.ReadValue() |
- 接触区域的大小
1 | tc.radius.ReadValue() |
- 偏移位置
1 | tc.delta.ReadValue() |
- 当前手指的状态
1 | UnityEngine.InputSystem.TouchPhase tp = tc.phase.ReadValue(); |
其中状态对应如下表:
| 状态 | 含义 |
|---|---|
None |
无 |
Began |
开始接触 |
Moved |
移动 |
Ended |
结束 |
Canceled |
取消 |
Stationary |
静止 |
手柄输入
- 获取当前手柄设备
1 | Gamepad gamePad = Gamepad.current; |
- 手柄摇杆
1 | // 左摇杆 |
- 手柄方向键
1 | gamePad.dpad.left |
也可以搭配按下、抬起、长按等逻辑。
- 手柄右侧按键
1 | // 上下左右分别对应北南西东 |
同样也可以搭配按下、抬起、长按等逻辑。
- 手柄中央按键
1 | // 右边开始键 |
- 手柄肩部按键
1 | // 左右前方肩部键 |
另外,还有摇杆、电子笔、传感器、陀螺仪等等设备,这里不做介绍,可以在Unity官方文档中查看。
InputAction 输入动作类
InputAction是InputSystem帮助我们封装的输入动作类。它的主要作用是不需要我们通过写代码的形式来处理输入,而是直接在Inspector窗口编辑想要处理的输入类型。
需要引用命名空间:
1 | using UnityEngine.InputSystem; |
参数相关
齿轮
- Action:输入动作设置
- Action Type:动作类型
- Value:值类型。得到某值,只会发送主设备的输入
- Button:按钮类型
- Pass Through:直通类型。和值类型一致,不过他会发送所有设备的输入
- Control Type:控制类型。得到的返回值只能是当前选择的类型
- Action Type:动作类型
- Interactions:相互作用设置。用于特殊输入,比如长按、多次点击等等。其中代码中涉及三个事件:
started 开始、performed 触发、canceled 结束- Hold:长按。按下时触发
started,按住时间大于等于Hold Time触发performed,否则触发canceled - Tap:点击。需要在一定时间内按下并松开才能触发
- SlowTap:缓慢点击。按下持续一定时间并松开才能触发
- MultiTap:多次点击。几次点击的间隔时间不超过多少才能触发
- Press:按下。
- Press Only:按下的时候触发
started和performed,不触发canceled - Release Only:按下单时候触发
started,松开的时候触发performed - Press And Release:按下的时候触发
started和performed,松开的时候再次触发
- Press Only:按下的时候触发
- Hold:长按。按下时触发
- Processors:值处理加工设置,对得到的值进行加工处理。
- Clamp:将输入值钳制在
[min, max]之间 - Invert / Invert Vector2 / Invert Vector3:反转控件中的值
- Normalize / Normalize Vector2 /Normalize Vector3:单位化
- Scale / Scale Vector2 /Scale Vector3:将所有输入值乘以系数
- Axis Deadzone / Stick Deadzone:死区。防止因为设备太敏感导致的人物移动等问题
- Clamp:将输入值钳制在
加号
- Add Binding:添加新的单按键输入
- Add Positive\Negative Bindig:类似Input中的水平竖直热键,返回-1~1之间的一个值
- Negative:负数
- Composite Type:复合类型
- MinValue:最小值
- MaxValue:最大值
- Which Side Wins:当同时按下时如何处理
- Positive:正数
- Negative:负数
- Add Up\Down\Left\Right Composite:类似Input中的水平竖直热键组合在一起,返回Vector2
- Up:上
- Composite Type:复合类型
- Mode:处理模式
- Analog:模拟值,浮点值
- Digital Normalized:单位化向量
- Digital:未单位化的向量
- Down:下
- Left:左
- Right:右
- Up:上
- Add Up\Down\Left\Right\Forward\Backward Composite:3D向量组合
- Add Button With One Modifier Composite:添加带有一个复合修改器的按钮,可以理解为双组合键,比如Ctrl + C 、Ctrl + V
- Modifier:复合输入内容
- Button:输入内容
- Add Button With Two Modifier Composite:可以理解为三组合键
代码相关
- 声明一个输入动作类
1 | public class Test : MonoBehaviour |
- 启用输入检测
1 | move.Enable() |
- 开始操作
1 | // started是一个事件 |
- 真正触发
1 | move.performed += (context) => |
- 结束操作
1 | move.canceled += (context) => |
其中,CallbackContext是InputAction内部的一个结构体,其中还能得到很多相关信息:
- 当前状态
1 | // Disabled 没有启用 |
- 动作行为信息
1 | context.action.name |
- 控件设备信息
1 | context.control.name |
- 获取值
1 | context.ReadValue<Vector2> |
- 持续时间
1 | context.duration |
- 开始时间
1 | context.startTime |











