一、Unity简介
1. Unity界面
Shift + Space : 放大界面
- Scene
- 界面按钮
- 渲染模式
- 2D/3D
- 光源
- 声音
- 各种显示开关
- 场景中元素图标的显示开关(Gizmos)
- 搜索框
- 界面操作
- 缩放
- 旋转
- 移动
- QWERTY快捷键
- 界面按钮
- Game
最终给玩家看到的界面,由场景中的摄像机拍摄到的画面- 界面按钮
- Display 通道
- 分辨率
- 缩放Scale:将摄像机拍摄到的画面缩放
- 播放时最大化
- 静音
- 状态信息面板开关
- 场景中元素图标的显示开关(Gizmos)
- 界面按钮
- Hierarchy(层级面板)
能够看到场景中所有的层级对象,与其层级关系- 新建
- 搜索
- Project (项目面板)
- 一列展示 / 两列展示
- 与硬盘中的
/Project/Assets
文件夹同步
- Inspector(属性面板)
- 作用
- 选中游戏对象后,在面板看到物体身上的组件
- 看到一个资源文件的属性
- 作用
- Console(控制台面板)
- 按钮
- Clear:清空
- Collapse:合并重复项
- Clear on Play:运行时清空上一次的日志
- ErrorPause:当程序遇到错误(异常)时暂停
- 按钮
- AssetStore(资源商店)
- 修改语言
- 价格以美元做单位
2. 坐标系
- 世界坐标系
“东西南北” - 物体坐标系
“前后左右” - 中心点 Pivot/Center 快捷键Z
- 坐标系 Global/Local 快捷键X
3. 材质球(Material)
想要修改一个游戏对象的外观,需要使用材质球
4. 资源包(Assets package)
*路径和名字不要有中文
- 导入 Import
- 导出 Export
- 标准资源包 Standard Assets
- 天空盒使用的两种方式
- Window -> Lighting -> Settings
- 给摄像机加组件Skybox
5. 预设体(Prefab)
- 设置
- Select
- Revert
- Apply
6. 地形系统(Terrain)
- 上升/下降地形
最低高度为0 - 恒高地形
- 平滑地形
- 绘制地面纹理
- 绘制树木
- 可以添加多种树
- 笔刷大小
- 树木密度
- 锁定宽高比
- 删除
- 按住shift:删除范围内全部
- 按住ctrl:删除范围内选择的树木
- 绘制草坪
- 删除
同删除树木
- 删除
- 设置
- 长宽高
7. 向量
- 标量
只有大小没有方向 - 基本运算
- 向量的加减
- 加法:将两个向量首尾相连,和等于第一个向量的头到第二个向量的尾这个向量
- 减法:等于被减数的终点到减数的终点这个向量
- 向量的点乘
- 向量的叉乘
- 向量的几何含义
- 向量的加减
8. 2D 项目与 3D 项目
-
2D与3D项目区别
- 2D没有天空盒
- 2D没有默认光源
- 摄像机的成像模式2D是正交视图,3D是透视图
-
将动画中的所有Sprite帧选中,拖到Hierarchy中就自动生成了一个精灵动画
-
组件
- Transform
- Sprite Renderer:精灵图渲染器
- Sprite:素材图片
- Color:颜色
- Flip:翻转
- FlipX:水平翻转
- FlipY:垂直翻转
- Material:材质
- Sorting Layer:在哪个层排序
- Order In Layer:决定谁在屏幕前,数字越大越靠前
- Collider2D
- Rigidbody2D
- PhysicsMaterial2D
- Edit -> Project Settings -> Physics2DSettings 修改默认物理材质
二、Unity脚本
- 脚本在创建后名称不能被修改
- 脚本可以当做一个组件来使用
- 脚本是一个类,继承自
MonoBehaviour
- 脚本文件的名称和类名一定要保持一致,否则会不能将脚本挂在
GameObject
上 - 继承关系:GameObject -> Component -> Behaviour -> MonoBehaviour -> Scripts
- 此GameObject不是彼Object,是UnityEngine定义的GameObject
- Runtime/Editor 脚本
- Runtime脚本,执行随着游戏运行时进行而运行
- Editor脚本,执行在游戏运行前
Unity脚本的生命周期
- Unity中什么都是对象
- 脚本的声明周期:一个脚本对象从最开始执行到最后销毁的全部过程
- Unity中的打印
Debug.Log("Message");
print("Message"); //不是脚本不能用此方法
- 脚本前的对勾:
- 脚本是否激活。依赖于Start()和OnEnable()
- 生命周期
- void Awake():游戏开始后第一个调用的回调函数。无论脚本是否激活,始终都执行。如果游戏对象未激活,他身上所有的组件是不会触发的
- void Start():游戏开始后第一帧执行的回调函数,只会执行一次
- void OnEnable():当脚本激活的时候触发一次,可以重复触发
- void OnDisable():当脚本取消激活的时候触发一次,可以重复触发
- void OnDestroy():当脚本被销毁前触发。脚本被移除或者游戏对象被销毁触发。游戏对象在销毁前会移除身上所有的组件,所以就是脚本被移除的时候触发
- void Update():每帧触发一次
- void LateUpdate():每帧触发一次,晚于Update()
- void FixedUpdate():每隔固定的时间间隔触发一次,时间间隔默认0.02s,在
Edit->Project Settings->Time
中修改 - void OnGUI()(基本不用了):每帧触发两次
graph LR
Awake-->OnEnable
OnEnable-->Start
OnDisable-->OnDestroy
Start-->Update
Update-->LateUpdate
LateUpdate-->Update
LateUpdate-->OnDisable
Start-->FixedUpdate
FixedUpdate-->FixedUpdate
FixedUpdate-->OnDisable
- 先挂的脚本后执行,后挂的脚本先执行。执行顺序可以在
Inspector->Execution Order
中设置,Default Time
数值越小的先执行
三、常用类
1. GameObject类
- GameObject 是一个用来描述游戏对象的类
- gameObject 表示游戏对象,脚本挂给谁,gameObject就是谁
常用属性与方法
-
gameObject.name
设置/获取一个游戏对象的名字 -
gameObject.tag
设置/获取一个游戏对戏的标签-
Inspector -> Tag -> Add Tag
或Edit -> Project Settings -> Tags and Layers
管理标签 - 标签可以用来查询获取游戏对象
- 注意:设置的
Tag
值需要是预先设置好的tag
值
-
-
gameObject.layer
设置/获取一个游戏对象的层- int型 代表层的序号
-
LayerMask
结构体- LayerMask.NameToLayer() 通过层名字获取层号
gameObject.layer = LayerMask.NameToLayer("abc");
- LayerMask.LayerToName() 通过层号获取层名字
gameObject.layer = LayerMask.LayerToName(8);
- LayerMask.NameToLayer() 通过层名字获取层号
-
Inspector -> Layer -> Add Layer
或Edit -> Project Settings -> Tags and Layers
管理层 - 层是有序号的,前8个不能更改,是系统自定的
- 层的作用:
- 场景中可以分层显示
-
Main Camera -> Culling Mask
分层获取图像 - 指定层接收/屏蔽射线
- 游戏对象的激活
- 获取游戏对象的激活状态
bool active = gameObject.activeSelf;
- 设置一个游戏对象的激活状态
gameObject.SetActive(false);
- 获取游戏对象的激活状态
- 查找游戏对象
- 通过名字查找
GameObject obj = GameObject.Find(string name);
- 名字不能打错
- 同名的时候,后创建的先找到,返回找到的这个游戏物体
GameObject obj = GameObject.Find("/Sphere/Sphere");
通过路径查找
- 通过标签查找
-
GameObject obj = GameObject.FindWithTag(string tag);
找到一个,后创建的先找到 -
GameObject[] obj = GameObject.FindGameObjectsWithTag(string tag);
找到所有这个标签的游戏物体 - 推荐使用tag查找,效率高
-
- 通过组件类型查找
SphereCollider[] colliders = GameObject.FindGameObjectsOfType<SphereCollider>();
- GameObject中所有的查询方法,都不能找到非激活的物体
- 通过名字查找
- 获取组件
-
BoxCollider collider = gameObject.GetComponent<BoxCollider>();
获取组件 -
BoxCollider collider = gameObject.GetComponentInChildren<BoxCollider>();
从自己开始往子物体中找,获取组件。如果自己有,选中自己的;自己没有,选中子物体的 -
collider.enabled = false;
设置组件的激活状态
-
- 添加组件
BoxCollider boxCollider = gameObject.AddComponent<BoxCollider>();
- 添加脚本
gameObject.AddComponent<ScriptName>();
- 添加脚本
- 销毁游戏对象或者组件
Rigidbody rigi = gameObject.GetComponent<Rigidbody>();
Destroy(rigi); // 直接销毁
Destroy(rigi, 3); // 延迟3秒销毁
Destroy(gameObject, 2); // 2秒后销毁游戏对象
2. Mathf 类
常量 | 作用 |
---|---|
Mathf.Rad2Deg | 弧度转角度 degrees = radian * Mathf.Rad2Deg |
Mathf.Deg2Rad | 角度转弧度 radian = degrees * Mathf.Deg2Rad |
- 角度:360° = 弧度:2π
- 反三角函数求出的都是弧度
方法 | 作用 |
---|---|
Mathf.Abs | 绝对值 |
Mathf.Pow | 指定次幂 |
Mathf.Sqrt | 开平方 |
Mathf.Sin | 正弦值 |
Mathf.Cos | 余弦值 |
Mathf.Tan | 正切值 |
Mathf.Asin | 反正弦 |
Mathf.Acos | 反余弦 |
Mathf.Atan | 反正切,一个参数 |
Mathf.Atan2 | 反正切,两个参数 对边和临边的值 |
Mathf.Floor | 向上取整 |
Mathf.Round | 四舍五入 |
Mathf.Ceil | 向下取整 |
Mathf.Clamp(int value, int min, int max) | 取限制值:如果小于min返回min,如果大于max返回max,否则返回value |
Mathf.Lerp(float a, float b, float t) | 取插值:a和b可以任意设置,t的取值范围是[0, 1],返回a + (b - a) * t |
Mathf.Lerp不是为了求一个具体的插值,而是一个趋势,t越小,越趋近于a,t越大,越趋近于b
3. Random 类
求随机数
取[min, max)范围内的整型随机数
Random.Range(int min, int max);
取[min, max)范围内的浮点型随机数
Random.Range(float min, float max);
4. Time 类
属性 | 作用 |
---|---|
Time.time | 获取游戏开始到当前的时间 |
Time.deltaTime | 获取两帧的时间差(每帧的时间) |
Time.FixedDeltaTime | 获取TimeStep |
Time.timeScale | 获取/设置Unity和现实世界的时间比例(默认1:1)用来改游戏速度 |
5. Vector3 结构体
表示一个三维空间中的点的坐标,或者表示一条三维向量
// 实例化一个
Vector3 a = new Vector(); // 默认x,y,z都是0
Vector3 a = new Vector(1, 2); // x=1, y=2, z=0
Vector3 a = new Vector(1, 2, 3); // x=1, y=2, z=3
5.1 Vector3的常量
常量 | 值 |
---|---|
Vector3.zero | (0, 0, 0) |
Vector3.one | (1, 1, 1) |
Vector3.up | (0, 1, 0) |
Vector3.down | (0, -1, 0) |
Vector3.right | (1, 0, 0) |
Vector3.left | (-1, 0, 0) |
Vector3.forward | (0, 0, 1) |
Vector3.back | (0, 0, -1) |
x轴正方向为右,y轴正方向为上,z轴正方向为前
5.2 Vector3中的运算符重载
- + :x相加 y相加 z相加得到新的点
- - :x相减 y相减 z相减得到新的点
- * :例 p * 4 是x/y/z分别乘4 得到新的点
- == :判断是否相等
- != :判断是否不等
5.3 Vector3的属性
属性 | 作用 |
---|---|
p.normalized | 求单位向量(标准化向量/向量归一化) |
p.sqrMagnitude | 求向量的模的平方(向量的模:向量的长度) |
p.magnitude | 求向量的模 |
5.4 Vector3的方法
方法 | 作用 |
---|---|
Vector3.Angle(p0, p1) | 求两个向量的夹角,返回一个角度 |
Vector3.Distance(p0, p1) | 求两个点的距离 |
Vector3.Cross(p0, p1) | 求两个向量的叉乘 |
Vector3.Dot(p0, p1) | 求两个向量的点乘 |
Vector3.Lerp(Vector3 a, Vector3 b, float t) | 求插值 |
6. 调试
- 划线调试
// 在两点之间划线
Debug.DrawLine(Vector3 start, Vector3 end);
Debug.DrawLine(Vector3 start, Vector3 end, Color color);
Debug.DrawLine(Vector3 start, Vector3 end, Color color, float duration);
// 从p点开始向direction方向发射射线,长度可以固定
Debug.DrawRay(Vector3 start, Vector3 direction);
Debug.DrawRay(Vector3 start, Vector3 direction, Color color, float duration);
- 日志调试
Debug.Log(string message);
Debug.LogWarning(string message);
Debug.LogError(string message);
7. Transform 类
表示一个物体的位置旋转缩放,维系一个物体的父子关系
// 获取Transform组件
Transform t = GetComponent<Transform>();
gameObject.transform
transform
- 属性
属性 | 作用 |
---|---|
position | 物体的世界坐标(世界坐标) |
localPosition | 物体相对于父物体的坐标(物体坐标) |
eulerAngles | 欧拉角旋转(世界坐标) |
localEulerAngles | 欧拉角旋转(物体坐标) |
rotation | 四元数旋转(世界坐标) |
localRotation | 四元数旋转(物体坐标) |
localScale | 物体的缩放 |
parent | 得到父物体的transform |
root | 得到根物体的transform |
childCount | 返回子物体的个数 |
- 常量
常量 | 值 |
---|---|
forward | 物体自己的z轴正方向(前方) |
right | 物体自己的x轴正方向(右方) |
up | 物体自己的y轴正方向(上方) |
- 方法
方法定义 | 作用 |
---|---|
transform.Translate(Vector3 v3) | 只移动一次(物体坐标) |
transform.Translate(Vector3 v3, Space relativeTo) | 只移动一次(Space坐标) |
transform.Rotate(Vector3 axis, float angle) | 沿轴axis,旋转angle角度(自转)(物体坐标) |
transform.RotateAround(Vector3 point, Vector3 axis, float angle) | 绕着point,沿轴axis,旋转angle角度(公转) |
transform.LookAt(Transform transform) | z轴正方向指向某个物体 |
transform.SetParent(Transform transform) | 设置父物体 |
transform.GetChild(int index) | 获得子物体(index是子物体的序号) |
transform.Find(string name) | 获得子物体(通过子物体的名字) |
transform.GetSiblingIndex() | 获取物体在父物体中的下标 |
transform.SetSiblingIndex(int index) | 修改物体在父物体中的下标 |
- transform中所有的查找方法可以找到非激活的
// 速度每秒10米 用10 * Time.deltaTime
transform.Translate(Vector3.right * 10 * Time.deltaTime); // 沿物体坐标
transform.Translate(Vector3.right * 10 * Time.deltaTime, Space.World); // 沿世界坐标
// 获取主摄像机的Camera组件
// 主摄像机:具有MainCamera的tag值的物体
Camera c = Camera.main;
8. Quaternion 类
- 欧拉角转四元数
Quaternion rotation = Quaternion.Euler(Vector3 euler);
Quaternion rotation = Quaternion.Euler(float x, float y, float z);
- 四元数转欧拉角
Vector3 euler = rotation.eulerAngles;
- 空旋转
Quaternion.identity
- 插值函数
Quaternion.Lerp(Quaternion a, Quaternion b, float t);
- 将一个向量转成一个四元数,用来取方向
Quaternion.LookRotation(Vector3 forward);
- 向量右乘(Quaternion * Vector3)
几何含义:将向量旋转指定的四元数角度
Vector3 result = Quaternion.Euler(0, 90, 0) * Vector3.forward;
// result是 Vector3.right (1, 0, 0)
9. Input 类
9.1 键盘、鼠标
- 检测按键一定不能写在FixedUpdate()中,检测是每一帧检测一次,FixedUpdate可能一帧触发多次
- 获取鼠标在屏幕上的位置
- 屏幕坐标是左下角为(0, 0, 0)点,右上角为(Screen.width, Screen.height, 0)点
-
Screen.width
屏幕宽Screen.height
屏幕高
Vector3 mousePosition = Input.mousePosition;
- 点击鼠标键盘
// KeyCode是一个枚举,包含几乎所有按键
bool isPress = Input.GetKey(KeyCode key) // 按住按键持续触发
bool isUp = Input.GetKeyUp(KeyCode key) // 按键抬起的时候触发一次
bool isDown = Input.GetKeyDown(KeyCode key) // 按键按下的时候触发一次
bool isDown = Input.GetKeyDown(string name) // 容易写错,不推荐
// 专门检测鼠标按键的,用button数字代表按钮
Input.GetMouseButton(int button)
Input.GetMouseButtonUp(int button)
Input.GetMouseButtonDown(int button)
9.2 Input虚拟轴
-
Horizontal
轴:水平 -
Vertical
轴:垂直 -
Mouse X
轴:鼠标在水平方向上的偏移量 -
Mouse Y
轴:鼠标在垂直方向上的偏移量 -
Mouse ScrollWheel
轴:鼠标滚轮的偏移量
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// 容错判断
if (Mathf.Abs() < 0.1f || Mathf.Abs() < 0.1f) return;
// 做一个方向向量
Vector3 direction = new Vector3(horizontal, 0, vertical);
if (direction != Vector3.zero)
{
// 得到目标方向
Quaternoin target = Quaternion.LookRotation(direction);
// 旋转
transform.rotation = Quaternion.Lerp(tranform.rotation, target, Time.deltaTime * speed);
// 移动
transform.translate(Vector3.forward * speed * Time.deltaTime);
}
父物体想要放在子物体中心:
- 将父物体作为子物体的子物体
- reset transform
- 再将子物体作为父物体的子物体
10. Application 类
都是静态的,用Application.xxxx
- runInBackground:游戏是否支持后台运行
- dataPath:返回当前工程的Assets文件夹路径
- persistentDataPath:根据发布的平台,返回一个持久化路径
- streamingAssetsPath:返回一个StreamingAssets文件夹的路径,是Assets文件夹的一个子文件夹,打包后这个文件夹依然存在
- OpenURL:打开指定的网页
- Quit():关闭当前应用程序(只有打完包(发布应用程序)后才有效,手游几乎没有)
11. ScreenCapture 类
ScreenCapture.CaptureScreenshot("绝对路径"):截图
- 使用代码截图
using System;
using System.IO;
// 判断
bool exists = Directory.Exists(Application.persistentDataPath + "/Images");
if (exists == false)
{
// 创建文件夹
Directory.CreateDirectory(Application.persistentDataPath + "/Images");
}
// 获取时间
DateTime now = DateTime.Now;
// 格式化为自定义的格式
string name = now.ToString("yyyy年MM月dd日 HH时mm分ss秒");
string filePath = Application.persistentDataPath + "/Images/" + name + ".png";
ScreenCapture.CaptureScreenshot(filePath);
- 时间格式化(部分)
时间标签 | 代表内容 |
---|---|
yyyy | 年 |
MM | 月 |
dd | 日 |
HH | 时(24小时制) |
hh | 时(12小时制) |
mm | 分 |
ss | 秒 |
12. Scene Manager 类
- 场景切换:在发起切换的场景中做一个panel,做切换画面,放一个Slider把Handler删除,Interactable取消勾选,动态修改value值为asyncOperation.progress
using UnityEngine.SceneManagement;
// 1. 将需要切换的场景添加到Scenes In Build
// File -> Build Settings -> Scenes In Build
// 按按钮添加,右键选场景删除
// 拖拽后每一个场景都有一个编号
// 2. 使用SceneManager类来切换
// 同步切换
void SceneManager.LoadScene(string name); // 通过场景名字切换
void SceneManager.LoadScene(int index); // 通过场景下标编号切换
// 异步切换
AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(string name); // 通过场景名字切换
AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(int index); // 通过场景下标编号切换
// 可以通过asyncOperation拿到切换的进度
// 在Update()中使用进度
private void Update()
{
if (asyncOperation != null)
{
print(asyncOperation.progress);
}
}
- DontDestroyOnLoad 静态方法
此静态方法允许游戏物体在加载任何场景的时候不销毁,只能让根物体不销毁,可以用来管理全局的事务
DontDestroyOnLoad(Object target);
使用此方法存在一个问题,当在A场景生成了DontDestroyOnLoad的游戏物体后,切换B场景,再切回A场景,会出现多次调用该方法,从而无限循环,解决办法如下:
- 将需要全局保留的物体放到一个初始场景里,游戏运行第一时间就加载,加载完成再跳到初始页面
- 判断该方法是否已经调用过,设一个全局(静态)变量默认False,当运行过一次之后将其变为True,之后每次进入判断True是否调用
- 使用静态构造方法,当第一次使用这个类的时候调一次,所以这个静态构造方法全局只会调用一次,在这个构造方法中创建空物体,将此脚本绑定上去,再DontDestroyOnLoad这个空物体,此时这个DontDestroyOnLoad全局只会调用一次,这个脚本也做成了类似单例脚本,使用方便
13. Audio Source 类
用于管理播放声音文件
- AudioSource组件
- AudioClip:音源文件
- Output:混音调音
- Mute:静音
- Loop:循环
- Volume:音量
- Spatial Blend:使用2D还是3D声音。2D声音没有位置;3D声音有位置,可以调声音曲线
// 获得AudioSource组件
private AudioSource _audio;
_audio = GetComponent<AudioSource>();
// 加载播放音效
AudioClip clip = Resources.Load<AudioClip>(string path);
// 播放一次
_audio.PlayOneShot(clip);
// 设置一个新的要播放的声音片段
_audio.clip = clip;
// 设置其他的
_audio.loop = loop;
// 背景音播放
_audio.Play();
// 死亡第一段
private void Die1()
{
// 延迟播放第二段
Invoke("Die2", 0.5f);
}
// 死亡第二段
private void Die2()
{
}
14. Invoke 延迟调用
- Invoke(string functionName, float delay); 延迟调用方法
- CancelInvoke(string functionName); 取消指定方法的延迟调用
- Cancel(); 取消所有延迟调用的方法
15. Gizmos 类
- Gizmos是用于在场景视图可视化调试或辅助设置
- 所有gizmo绘制需要在脚本的OnDrawGizmos或OnDrawGizmosSelected里完成
- OnDrawGizmos在每帧调用。所有在OnDrawGizmos中渲染的gizmos都是可见的。OnDrawGizmosSelected仅在脚本附加的物体被选择时被调用。
- 静态变量
变量名 | 作用 |
---|---|
Gizmos.color | 为随后绘制的gizmos设置颜色 |
Gizmos.matrix | 设置gizmo的矩阵用于绘制所有gizmos |
- 静态方法
方法名 | 作用 |
---|---|
Gizmos.DrawCube(Vector3 center, Vector3 size) | 绘制立方体,使用center和size参数,绘制一个实心立方体 |
Gizmos.DrawFrustum(Vector3 center, float fov, float maxRange, float minRange, float aspect) | 绘制相机可视区域,用当前的Gizmos.matrix设置它的位置和旋转,参数:center(棱锥体的顶点),fov(视角大小,角度制),maxRange(视野最大距离),minRange(视野最小距离),aspect(宽高比,宽/高) |
Gizmos.DrawIcon(Vector3 center, string name, bool allowScaling = true) | 绘制图标,图标被命名为name并且被放置在center处。图标的路径需要放在Assets/Gizmos文件夹 |
Gizmos.DrawLine(Vector3 from, Vector3 to) | 绘制线段,绘制一条从from起点到to位置的线段 |
Gizmos.DrawRay(Ray ray) | 绘制射线 |
Gizmos.DrawRay(Vector3 from, Vector3 direction) | 绘制射线,从from开始向direction绘制一条射线 |
Gizmos.DrawSphere(Vector3 center, float radius) | 绘制球体,使用center和radius参数,绘制一个实心球体 |
Gizmos.DrawMesh(Mesh mesh, Vector3 position = Vector3.zero, Quaternion rotation = Quaternion.identity, Vector3 scale = Vector3.one) | 绘制一个网格 |
Gizmos.DrawWireCube(Vector3 center, Vector3 size) | 绘制线框立方体,使用center和size参数,绘制一个线框立方体 |
Gizmos.DrawWireSphere(Vector3 center, float radius) | 绘制线框球体,根据center和radius参数设置线框球体 |
引用自http://wiki.ceeger.com/script/unityengine/classes/gizmos/gizmos
四、物理引擎
1. 碰撞体 Collider
1.1 属性
- Edit Collider:编辑碰撞体的大小
- Is Trigger:是否是触发器
- Material:物理材质
- Center:中心点位置
- Size:缩放比例
- Radius:半径
- ...
1.2 碰撞体的作用
- 碰撞
- 射线
1.3 回调方法
方法定义 | 作用 |
---|---|
OnMouseEnter() | 鼠标进入碰撞体时触发一次 |
OnMouseOver() | 鼠标在碰撞体内停留时持续触发,每帧一次 |
OnMouseExit() | 鼠标离开碰撞体时触发一次 |
OnMouseDown() | 鼠标在碰撞体范围内按下的时候触发一次 |
OnMouseDrag() | 鼠标在拖拽的时候持续触发,每帧一次 |
OnMouseUp() | 鼠标在抬起的时候触发一次 |
OnMouseUpAsButton() | 鼠标在碰撞体范围内抬起的时候触发一次 |
1.4 获取范围内碰撞体
方法 | 作用 |
---|---|
Physics.OverlapBox() | 获得立方体形范围内的所有碰撞体 |
Physics.OverlapSphere() | 获得球体形范围内的所有碰撞体 |
Physics.OverlapCapsule() | 获得胶囊体形范围内的所有碰撞体 |
1.5 碰撞
- 碰撞三要素
- 碰撞双方都有Collider组件,并且不是Trigger
- 至少有一方有rigidbody组件
- 双方需要保持相对运动
- 回调方法
方法 | 作用 |
---|---|
OnCollisionEnter(Collision collision) | 碰撞开始时触发一次 |
OnCollisionStay(Collision collision) | 碰撞过程中持续触发,每帧一次 |
OnCollisionExit(Collision collision) | 碰撞结束时触发一次 |
collision 是碰撞的信息
// 存有所有接触点的数组
Vector3 point = collision.contacts[0].point;
// 获取到和哪一个碰撞体发生碰撞了
Collider c = collision.collider;
1.6 设置物理引擎相关参数
Edit -> Project Settings -> Physic
属性 | 作用 |
---|---|
Gravity | 重力 |
Default Material | 默认的物理材质 |
Layer Collision Matrix | 层之间的碰撞矩阵,控制层之间是否能够碰撞 |
2. 刚体 Rigidbody
完成逼真的物理效果的物理组件
2.1 属性
- Mass:质量
- Drag:阻力
- Angular Drag:角阻力
- Use Gravity:使用重力
- Is Kinematic:开启动力学,不受任何力
- Interpolate:插值
- none:无
- interpolate:内插值
- extrapolage:外插值
- Collision Detection:碰撞检测模式
- Discrete:离散检测,精度不高,性能消耗低,检测不到极端情况
- Continuous:连续检测,提高检测精度,性能消耗大
- Continuous Dynamic:连续动态检测,提高检测精度,性能消耗大
- Constraints 约束
2.2 代码中的属性和方法
Inspector面板上显示的属性都可以用代码获取到
属性方法 | 作用 |
---|---|
mass | 重量 |
drag | 阻力 |
angularDrag | 角阻力 |
useGravity | 使用重力 |
isKinematic | 开启动力学 |
interpolation | 插值 RigidbodyInterpolation枚举 |
collisionDetectionMode | 碰撞检测模式 CollisionDetectionMode枚举 |
constraints | 约束 RigidbodyConstrains枚举 |
velocity | 速度 Vector3类型(初速度) |
angularVelocity | 角速度 Vector3类型(初速度) |
AddForce(Vector3 force); | 给刚体添加一个力 |
AddExplosionForce(float explosionForce, Vector3 explosionPosition, float explosionRadius) | 给刚体添加一个爆炸力(炸自己)。想别的刚体也受到爆炸力影响,需要给别的刚体也添加一个爆炸力 |
2.3 层检测与层屏蔽
// 已知cube1是第9层,cube2是第10层
// 层检测
LayerMask mask = 1 << 10 | 1 << 9; // 获取第9和10层
LayerMask mask = 1 << LayerMask.NameToLayer("cube1"); // 获取第9层
LayerMask mask = GetMask(params stirng[] layerNames);
// 层屏蔽
mask = ~mask; // 按位取反mask
3. 触发 Trigger
触发会用到Collider
的IsTrigger
- 触发三要素
- 两个物体都有碰撞体
- 至少有一个带有刚体
- 至少有一个是触发器
- 触发的回调
回调定义 | 作用 |
---|---|
OnTriggerEnter(Collider collider) | 触发开始时触发一次 |
OnTriggerStay(Collider collider) | 触发持续过程中,每帧调用 |
OnTriggerExit(Collider collider) | 结束触发时触发一次 |
4. 射线 Ray
- 射线包含起点和方向,Unity中可以限定射线长度(Vector3的长度)
- 有Collider的物体会被射线打中,碰撞体和触发器都能被触发
- 系统提供了一个层叫Ignore Raycast,用于忽略射线
// 生成射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// 声明一个RaycastHit,用来存储射线打中的物体的信息,**是一个结构体,值类型**
RaycastHit hit;
// 发射射线
// true:打中物体了 false:没有打中物体
bool result = Physics.Raycast(ray, out hit);
if (result)
{
// 获取射线打中的物体
hit.collider;
// 获取射线打中的坐标(世界坐标)
hit.point;
}
// 发射一条射线,返回射线打中的所有物体
RaycastHit[] hits = Physics.RaycastAll(ray);
5. 动态生成游戏物体
// 生成谁
public GameObject plane;
// 在哪生成
// 旋转是什么
Instantiate(Object obj, Transform parent); // 游戏对象生成并绑定父物体
Instantiate(Object obj, Transform position, Quaternion q) // 在指定的位置position使用指定的旋转q生成游戏对象obj
- 代码读取 Resources 中的Prefab
// 资源路径一定是 /Resources/ 或其子文件夹中,否则读取不到
// 示例中放在了 /Resources/Bullet/bullet1
GameObject _bulletPre;
private void Start(){
// Load()的返回值是Object,所以要向下转型
_bulletPre = Resources.Load("Bullet/bullet1") as GameObject;
// 使用泛型的返回值是 T 类型,可以不用转型
_bulletPre = Resources.Load<GameObject>("Bullet/bullet1");
// 加载文件夹中所有的 T 类型资源
GameObject[] objs = Resources.LoadAll<GameOBject>("Bullet");
}
五、UGUI
- 用于绘制2D界面,Z轴方向的缩放是无效的
- 需要引用命名空间
using UnityEngine.UI;
1. Text:文本
- W工具会改变内容的宽高比,T工具不会改变内容的宽高比
- Rect Transform:是Transform的子类
- Canvas Renderer:画布渲染器
- Text(Script)组件
- Text:显示的文本
- Character:字体
- Font:字体
- Font Style:文字风格
- 粗体
- 斜体
- 粗体+斜体
- Font Size:字体大小
- Line Spacing:行间距
- Rich Text:是否支持富文本(富文本:可以单独修改某些文字的风格)
例:<b>hello</b> <i>world</i>
- Paragraph
- Alignment:对齐方式
- Align By Geometry:是否对齐到网格
- Horizontal Overflow:水平方向溢出
- Wrap:正常
- Overflow:溢出还显示
- Vertical Overflow:垂直方向溢出
- Truncate:正常
- Overflow:溢出还显示
- Best Fit:根据文本内容的多少和Text的大小,动态调节文字的大小(条件:需要水平和垂直方向不能是Overflow)
- Min Size:最小文字大小
- Max Size:最大文字大小
- Color:字体颜色
- Material:字体材质
- Raycast Target:能否接收射线
- 可添加组件
- Shadow:Text阴影
- Effect Color:阴影颜色
- Effect Distance:阴影距离
- Use Graphic Alpha:
- Outline:字体描边
- Effect Color:描边颜色
- Effect Distance:描边距离
- Use Graphic Alpha:
- Shadow:Text阴影
- 代码中的设置
// 获取属性
_text = GetComponent<Text>();
// 属性设置
_text.text = "hello world";
_text.font = Resources.Load<Font>(string path); // 通过Resources文件load
_text.fontStyle = FontStyle.Bold; // FontStyle枚举
_text.fontSize = 14;
// 段落设置
_text.alignment = TextAnchor.LowerCenter; // 枚举
_text.color = Color.Red;
_text.color = new Color(float r, float g, float b, float a); // rgba颜色
2. Image 与 Raw Image 图片
- 区别
- Image显示Sprite,精灵图
- Raw Image显示Texure,纹理图
- 图片设置
- Texture Type:图片类型
- Default:默认为纹理图
- Sprite(2D and UI):精灵图
- Sprite Mode:精灵图模式
- Single:单图
- Multiple:多图
- Sprite Mode:精灵图模式
- Texture Type:图片类型
- 组件
- Image(Script)
- Source Image:图片源,若代码中想修改,需要使用
Image.sprite
改图片 - Color:图片颜色
- Material:图片材质
- Raycast Target:是否标记为光线投射目标。默认勾选,表明鼠标无法点击该图片后面的物体;不勾选代表可以点击后面的物体
- Image Type:填充方式
- Simple:默认填满整个Image
- Sliced:图片为单张的时候,除了四个角都拉伸,用于做边框
- Tiled:平铺
- Filled:填满
- Fill Method:填充方式
- Horizontal:水平方向填充
- Vertical:垂直方向填充
- Radial 90:以左下角为圆心,按角度填充
- Radial 180:以下边中心点为圆心,按角度填充
- Radial 360:以图片中心点为圆心,按角度填充
- Fill Origin:填充起始位置
- Fill Amount:填充比例
- Clockwise:顺时针逆时针
- Preserve Aspect:保持宽高比
- Fill Method:填充方式
- Set Native Size:设置原图大小
- Source Image:图片源,若代码中想修改,需要使用
- Raw Image
- Source Image:图片源,需要sprite 代码中使用
Image.texure
改图片 - UV Rect:令图片的一部分显示在Raw Image中,可根据x,y坐标让图片动起来
- X:图片左下角的坐标x
- Y:图片左下角的坐标y
- W: 宽 Width
- H: 高 Height
- Source Image:图片源,需要sprite 代码中使用
- Image(Script)
3. Button 按钮
- 有一个子物体Text
- Button(Scrite)组件
- Interactable:用户能否交互
- Transition:在不同的状态间切换的时候显示的效果
- Normal Color: 按钮实例化后显示的颜色
- Highlighted Color:鼠标移动到button上显示的颜色
- Pressed Color:鼠标点击了按钮显示的颜色
- Disabled Color:禁用按钮时的颜色
- Color Multipier:颜色切换速度,越大则颜色变化越快
- Fade Duration:渐变动画的时长,越长越不明显
- 按钮的点击事件
- 一定是无返回值的事件
- 通过代码给一个按钮添加一个点击事件
// 声明一个按钮
private Button _button;
// 获取按钮组件
_button = GetComponent<Button>();
// 给按钮添加一个点击事件(监听)
// UnityAction 是void返回无参的委托
_button.onClick.AddListener(UnityAction call);
// 用 Lambda表达式添加一个点击事件
_button.onClick.AddListener(()=>
{
Debug.Log("Hello world");
});
// 删除按钮的点击事件
_button.onClick.RemoveListener(UnityAction call);
// 删除按钮的所有点击事件
_button.onClick.RemoveAllListeners();
4. Toggle 选项框
由背景Background(Image),对勾Checkmark(Image),标签Lable组成(Text)
- Toggle(Script)
- Transition:渐变
- None:无渐变
- Color Tint:使用color为按钮染色,同button
- Sprite Swap:在精灵图中切换
- Tartget Graphic:目标图形,默认的是子物体的Background,是一个Image组件
- Highlighted Sprite:高亮显示的精灵图
- Pressed Sprite:点击时显示的精灵图
- Disabled Sprite:禁用时显示的精灵图
- Animation:使用Toggle controller控制渐变
- Navigation:选项框导航,用于遥控器或手柄等设备
- Is On:设置/获取选项框的勾选状况
- Toggle Transision:效果
- Fade:淡入淡出效果
- Group:分组,将想要单选的所有Toggle放在同一个组中,每组中只能勾选一个
- Toggle Group组件
- Allow Switch Off:勾选后,可以一个选项都不选,不勾选时,至少选择一项
- Toggle Group组件
- Transition:渐变
- On Value Changed(boolean):按钮的回调方法,勾选状态变更,会触发一次
// 声明一个Toggle组件
private Toggle _toggle;
// 获取Toggle组件
_toggle = GetComponent<Toggle>();
// 添加事件
_toggle.onValueChanged.AddListener(status =>
{
// 参数status:表示发生变化后,toggle的isOn
Debug.Log(_toggle.isOn);
// 或者就使用status
Debug.Log(status);
});
5. Slider 滑块
- 由背景background(Image)、Fill Area -> 进度条Fill(Image)、Handle Slide Area -> 滑块Handle(Image)组成
- 用于场景加载进度、下载进度、调音量等等
- Slider
- Rill Rect:进度条的填充,默认使用子物体的Fill
- Handle Rect:滑块的填充,默认使用子物体的Handle
- Direction:方向
- Left To Right:从左到右
- Right To Left:从右到左
- Bottom To Top:从下到上
- Top To Bottom:从上到下
- Min Value:最小值
- Max Value:最大值
- Whole Numbers:滑块代表的值只能是整数
- Value:当前滑块所在的值(属性value:可读可写)
- OnValueChanged(Single)
- Single是单精度浮点型
- 代码同toggle
6. Input Field 输入框
- 由子物体Placeholder(Text)和Text(Text)组成
- Input Field自带一个Image组件,代表输入框的背景
- Placeholder:没有任何字符输出的时候,显示在输入框中
- Text:用户输入的内容文本
- Input Field(Script) 组件
- Text Component:用户输入的文本显示在哪个文本框中,按本身的输入框文本样式的显示
- Text:输入框中的文本
- Character Limit:位数限制,0是不限位置
- Content Type:文本类型
- Standard:标准
- Line Type:换行方式
- Single Line:单行显示
- Multi Line Submit:超出显示范围另起一行
- Multi Line Newline:可以使用回车键另起一行
- Line Type:换行方式
- Autocorrected:自动纠错
- Integer Number:只能输入整型数字
- Decimal Number:可以输入浮点型数字
- Alphanumeric:可以输入字母和数字
- Name:每个单词的首字母自动大写
- Email Address:可以输入邮件格式
- Password:将输入的文字以星号
*
显示 - Pin:将输入的文字以星号
*
显示,只能输入数字 - Custom:自定义
- Line Type:换行方式
- Single Line
- Multi Line Submit
- Multi Line Newline
- Input Type:输入方式
- Standard
- Auto Correct
- Password
- Keyboard Type:键盘模式
- Default
- ASCII Capable
- Numbers And Punctuation
- URL
- Number Pad
- Phone Pad
- Name Phone Pad
- Email Address
- Nintendo Network Account
- Character Validation:字符验证
- None
- Integer
- Decimal
- Alphanumeric
- Name
- Email Address
- Line Type:换行方式
- Standard:标准
- Caret Blink Rate:光标闪烁频率(1秒几次,默认0.85次)
- Caret Width:光标宽度
- Custom Caret Color:自定义光标颜色
- Selection Color:自定义选择颜色
- Hide Mobile Input:隐藏系统键盘,iOS专用
- Read Only:只读不能写
- On Value Changed(Stirng) 每次输入框改变都触发
- On End Edit(String) 停止编辑后触发一次
7. Dropdown 下拉菜单
- 由标签label 箭头arrow 模板template(下拉菜单每一个选项的模板)组成
- Dropdown(Script)组件
- Template
- Caption Text:将选择的选项中的文字显示在指定的Text组件中
- Caption Image:将选择的选项中的图片显示在指定的Image组件中
- Item Text:每个选项中的文字显示在指定的Text组件中
- Item Image:每个选项中可以加图片,显示在指定的Image组件中
- Options
- OnValueChanged(Int32)
参数是Option的序号,从0开始 - 代码设置Dropdown
// 声明一个dropdown
Dropdown _dropdown = GetComponent<Dropdown>();
// Dropdown.OptionData用来存Option数据
List<Dropdown.OptionData> data = new List<Dropdown.OptionData>()
{
new Dropdown.OptionData(){ text = "helloworld1" },
new Dropdown.OptionData(){ text = "helloworld2" }
};
// 两种方式设置options
// 1. 覆盖之前的所有设置
_dropdown.options = data;
// 2. 添加设置
_dropdown.AddOptions(List<Dropdown.OptionData> data);
8. Panel
- 自带一个Image组件,是一个有勾边有圆角的Image,默认alpha通道小
- 更多情况下来做容器,默认和父物体一样大
9. Rect Transform
继承自Transform,拥有transform的所有属性方法
- Pivot:轴(心)点
- 一个物体的中心,T工具中心的空心蓝点,
- 如果选择了Center,不能移动轴点
- 如果选择了Pivot,可以移动轴点
- X:轴点的X比例,1是最右侧,0是最左侧
- Y:轴点的Y比例,1是最上侧,0是最下侧
- (0, 0)是左下角,(1, 1)是右上角
- Anchor:锚点
- 锚点是两个点
- 锚点之间的关系
- 锚点:Min = Max 是一个点
- Pos X:轴点到锚点水平方向的间距,轴点 - 锚点
- Pos Y:轴点到锚点垂直方向的间距,轴点 - 锚点
- Width:物体的宽度
- Height:物体的高度
- 锚线:Min != Max 坐标有一个相同,有一个不同,两点连成一条线
- 水平
- Left:物体的左边缘到左边锚点的水平间距
- Pos Y:轴点到锚点垂直方向的间距
- Right:物体的右边缘到右边锚点的水平间距
- Height:物体的高度
- 垂直
- Pos X:轴点到锚点水平方向的间距
- Top:物体的上边缘到上边锚点的垂直间距
- Width:物体的宽度
- Bottom:物体的下边缘到下边锚点的垂直间距
- 水平
- 锚框:Min != Max坐标都不同,连城一个矩形,min在左下角,max在右上角
- Left:物体的左边缘到左边锚点的水平间距
- Right:物体的右边缘到右边锚点的水平间距
- Top:物体的上边缘到上边锚点的垂直间距
- Bottom:物体的下边缘到下边锚点的垂直间距
- 锚点:Min = Max 是一个点
- 锚点是相对于父物体的
- 代码中修改RectTransform
// 获得RectTransform组件
RectTransform rect = GetComponent<RectTransfor>();
// 矩形右上角的相对于父物体的按比例坐标位置
rc.anchorMax = new Vector2(0.7f, 0.7f);
// 矩形左下角的相对于父物体的按比例坐标位置
rc.anchorMin = new Vector2(0.7f, 0.7f);
// 矩形的右上角相对于右上角锚点的偏移量(x轴,y轴)
// 两个参数是RectTransform的(-左下角,-右上角)
rc.offsetMax = new Vector2(10, 10);
// 矩形的左下角相对于左下角锚点的偏移量(x轴,y轴)
// 两个参数是RectTransform的(左上角,右下角)
rc.offsetMin = new Vector2(10, 10);
// 矩形的轴点相对于锚点的偏移量
rc.anchoredPosition = new Vector2(10, 10);
// 轴点的按比例坐标位置
rc.pivot = new Vector2(5, 5);
// 当两点重合时,代表高度和宽度
// 当两点不重合时(?跟失了智一样)
rc.sizeDelta = new Vector2(10, 10);
10. UGUI事件的回调
UGUI事件使用接口回调,使用时要使脚本类实现接口的方法
回调方法名 | 所在接口 | 说明 |
---|---|---|
void OnPointerEnter(PointerEventData eventData) | IPointerEnterHandler | 鼠标进入时触发 |
void OnPointerExit(PointerEventData eventData) | IPointerExitHandler | 鼠标离开时触发 |
void OnPointerDown(PointerEventData eventData) | IPointerDownHandler | 鼠标按下时触发 |
void OnPointerUp(PointerEventData eventData) | IPointerUpHandler | 鼠标抬起时触发 |
void OnPointerClick(PointerEventData eventData) | IPointerClickHandler | 鼠标点击时触发 |
void OnBeginDrag(PointerEventData eventData) | IBeginDragHandler | 开始拖拽时触发 |
void OnDrag(PointerEventData eventData) | IDragHandler | 拖拽中持续触发 |
void OnEndDrag(PointerEventData eventData) | IEndDragHandler | 拖拽结束后触发 |
11. Canvas 画布
- 添加UI组件时自动添加,所有的UI元素必须是canvas的子物体
- 一个场景中可以放多个画布
- 画布的大小:和屏幕一样大
- Canvas组件
- Render mode:渲染模式
- Screen Space - Overlay:平铺,呈现在所有物体之前
- Sort Order:决定多个画布的前后顺序,数字越大越靠前
- Target Display:在指定通道渲染
- Screen Space - Camera:指定摄像机渲染,物体前后顺序由Plane Distance决定。在UI上做些特效的时候使用。
- Render Camera:指定使用哪个摄像机进行渲染
- Plane Distance:画布距离摄像机的间距
- Sorting Layers:在哪个图层排序
- Order in Layer:在排序图层中的顺序,数字越大越靠前
- World Space:可以把画布当做一个三维的物体来使用,此时Rect Transform可以修改
- Event Camera:使用的摄像机
- Sorting Layers:在哪个图层排序
- Order in Layer:在排序图层中的顺序,数字越大越靠前
- Screen Space - Overlay:平铺,呈现在所有物体之前
- Render mode:渲染模式
- Canvas Scaler:多分辨率适配:找一个模板,按该模板的分辨率做UI,其他分辨率缩放
- UI Scale Mode:屏幕缩放的模式
- Constant Pixel Size:按恒定的像素大小缩放
- Scale With Screen Size:按屏幕尺寸缩放
- Reference Resolution:分辨率
- Screen Match Mode:缩放模式
- Match (Width ~ Height):按宽或高的比例缩放,或者是其中的某一个比例
- Constant Physical Size:按恒定的物理尺寸缩放
- UI Scale Mode:屏幕缩放的模式
- Graphic Raycaster:决定能否相应事件
按照默认设置即可
12. EventSystem 事件系统
添加UI组件时自动添加,表示这个场景中的所有画布能否响应事件
13. Camera 摄像机
- 一个场景可以放多个摄像机
- Camera组件
- Clear Flags:摄像机背景,默认是Skybox
- Skybox:天空盒
- Solid Color:纯色背景
- Depth Only:透明背景,显示物体不显示天空盒
- Don't Clear:不清除物体原来的轨迹
- Culling Mask:摄像机显示指定层的物体,默认Everything
- Nothing
- Everything
- 各个层的名字...
- Projection:摄像机的成像模式,默认Perspective透视图
- Perspective:透视图,近大远小,视野范围是一个金砖型空间
- Feild of View:视角,角度[0, 180],一般为60
- Orthographic:正交图,物体的真实大小,视野范围就是一个立方体空间,可以做对齐操作
- Size:摄像机的视野平面范围
- Perspective:透视图,近大远小,视野范围是一个金砖型空间
- Clipping Planes:摄像机视野的纵向范围,在近景面和远景面之间的物体可以被拍到
- Near:近景面
- Far:远景面
- Viewpoint Rect:最终摄像机拍到的画面在屏幕中的位置和大小
- 坐标点在左下角
- XYWH四个值都是[0, 1]取值范围,代表比例
- Depth:深度,控制摄像机的前后,数字越大越靠前,显示的越靠上
- Target Texture:目标纹理图
- Clear Flags:摄像机背景,默认是Skybox
- 可添加组件
- Skybox组件:使用指定的Skybox
- 小地图的制作思路
在Project中新建一个Render Texture(是一个纹理图,存在文件中),把Camera拍摄到的画面存到Render Texture中,在Hierarchy中新建一个Raw Image,用来展示Render Texture,此Raw Image就是小地图
14. Layout 布局组件
为了方便对某个物体的子物体布局位置和大小进行调整,布局组件互相之间冲突,最多只能有一个布局组件
- Horizontal Layout Group:水平布局
- Padding:内间距
- Left
- Right
- Top
- Bottom
- Spacing:每两个物体之间的间距
- Child Alignment:子物体的整体的对齐方式
- Child Controls Size:能否通过布局组件修改子物体的宽高,勾选后子物体的宽高不能修改
- Child Force Expand:通过调整物体位置,适当拉伸,填满整个屏幕
- Padding:内间距
- Vertical Layout Group:垂直布局,内容同水平布局
- Grid Layout Group:网格布局
- Padding:内间距
- Cell Size:单元格大小
- Spacing:间距,包含水平间距X和垂直间距Y
- Start Corner:布局起始的位置和方向
- Start Axis:布局轴,向哪个方向布局
- Child Alignment:子物体的整体的对齐方式
- Constraint:默认Flexible
- Flexible:自由
- Fixed Column Count:固定列数
- Fixed Row Count:固定行数
15. Scroll View 滚动视图
由 ViewPort(展示区域) -> Content(展示内容) 和两个 ScrollBar(滚动条) -> Sliding Area(滑轨) -> Handle(滑块) 组成
- Scroll View
- Image组件:展示区域的背景
- Scroll Rect(Script)组件
- Content:关联能够显示的范围
- Horizontal:是否支持在水平方向滚动,拖滚动条可以动,滚轮和拖内容不能动
- Vertical:是否支持在垂直方向滚动
- Movement Type:默认越界回弹
- Unrestricted:不限制,越界之后原地停留
- Elastic:越界回弹
- Elasticity:弹性系数,值越大回弹越慢
- Clamped:限制,不让越界
- Inertia:是否有惯性
- Deceleration Rate:减速率,0为没有惯性马上停,1为没有减速,1以上是加速
- Scroll Sensitivity:滚动灵敏度,用滚轮滚动的时候的灵敏度
- 关联子物体
- Visibility:滚动条的显示与隐藏
- On Value Changed(Vector2) 回调方法
- Viewport:展示区域
- Image组件
- Mask组件:遮罩,遮住父物体外面的物体
- Content:空物体,内容集合
- 把想滚动的物体作为Content的子物体,Content的大小决定了能否滚动
- 借助布局组件的方式
- 任意添加一个布局组件
- 添加一个组件Content Size Fitter:可以根据布局组件自动计算Content的大小
- Scroll Bar:滚动条
- Handele Rect:关联拖拽的滑块是哪一个
- Value:滑块滑动表示一个值[0, 1]
- Size:滑块的大小,表示一个比例[0, 1],如果是0,不会消失,以最小的形式出现
- Number Of Steps:一个滑轨上有几个点可以让滑块出现,范围[0, 11],0的时候为平滑滑动
- On Value Changed(Single)回调方法
六、动画
1. 一些知识
- 模型中包含动画,由模型师来做,我们在获得的时候就决定了模型能做什么动画
- Unity模型格式推荐.fbx
- 模型在Inspector面板上类似Prefab
- 模型子物体中包含各种零件、网格和动画
2. 旧版动画
- Window -> Animation 创建动画(ctrl + 6 / Command + 6)
- 需选中一个游戏对象
- Samples:每秒播放多少帧
- 横轴表示帧数
- 编辑时选上录制按钮,改帧的Image和把图片拖到时间条上都可以
- Add Property:添加动画物体需要修改属性
- Rect Transform
- Image
- Doposheet:逐帧编辑
- Curves:曲线图
3. 新版动画
- Mecanim动画系统
- 方便的实现任性动画的设置和重用
- 方便的实现动画剪辑的设置
- 可视化的动画控制界面
- 对动画播放的精确控制
- 模型及动画设置
- Model 模型标签
- Scale Factor:模型的缩放比例 Unity长度单位为米,一些模型软件长度单位是厘米,所以默认要设置0.01
- Rig 动画配置标签
- Animation Type
- None:无动画
- Legacy:旧版动画
- Generic:通用动画(非人型动画)
- Humanoid:新版人型动画
- Avatar Definition:骨骼配置
- Configure:配置,成功后前面会有个✔
- 会打开新场景,能看到模型骨骼
- Mapping:骨骼映射图
- 实线圈:必须配置的骨骼,如果缺失,会影响整个动画的执行
- 虚线圈:可选骨骼,配置会让动画更精细
- 人是面对我们的,屏幕右边的是左臂,屏幕左边的是右臂
- 三种方法做映射:屏幕上选中拖进去,Hierarchy中选中拖进去,拖到下面的列表中
- Mapping -> Automap 自动映射
- Muscles & Settings
- Preview:预览
- Animation Type
- Animation 动画面板
- Import Animation:导入动画,必选
- Clips:动画片段
- 截取片段:调整Start和End的帧数,横轴单位是由(秒:帧)表示的,帧率是30FPS
- loop match:绿色没有问题,黄色红色都表示有问题
- Loop Time:是否循环播放
- Loop Pose:会结合最后一帧和第一帧做一个中和
- Root Transform Rotation:动画旋转
- Bake into Pose:动画朝向不会改变
- Root Transform Position(X)
- Root Transform Position(Y)
- Bake into Pose:动画位移不会改变
- Mirror:镜像动画
- loop match:绿色没有问题,黄色红色都表示有问题
- 截取片段:调整Start和End的帧数,横轴单位是由(秒:帧)表示的,帧率是30FPS
- Model 模型标签
4. Animation组件
- 用来播放旧版动画
- Animation:默认在执行什么动画
- Animations:集合,表示这个模型会什么动画,如果模型中包含A动画但是这个集合中没有A动画,则不能切换
// 声明动画组件
private Animation _animation;
// 获得组件
_animation = GetComponent<Animation>();
// 按键切换动画
if (Input.GetKeyDown(KeyCode.W))
{
_animation.CrossFade("Walk");
}
else if (Input.GetKeyUp(KeyCode.W))
{
_animation.CrossFade("Idle");
}
5. Animator 组件
- Controller:动画状态机Runtime Animator Controller
- Avatar:骨骼
- Apply Root Motion:应用根动画,跟Bake into Pose联动
6. Animator Controller 动画状态机
滚轮缩放,alt加左键移动画面,把动画拖到状态机中松手,会自动新建状态,delete键删除联系
-
Inspector面板设置
- Motion:执行的动画
- Speed:动画播放速度
- Mirror:镜像动画
- Transitions:联系列表
- Has Exit Time:有停止播放的时间,勾选上之后播放完前一个动画才会切换到下一个动画
- Settings:过渡设置
- Fixed Duration:自动过渡
- Transition Duration:过渡时间
- Transition Offset:过渡的偏移量
- Interruption Source:打断源,动画可以被打断
- Conditions:切换条件
- 参数,大于或小于,具体数值
- 多个条件的时候,是与的关系,满足了所有条件之后才会切换
-
Animator状态机设置
- Layers:层
- Parameters:参数,切换不同的状态
- Float
- Int
- Bool
- Trigger
- 右键
- Make Transition:建立联系,可以联系自己
- Set as Layer Default State:设置为层默认状态
- Copy
- Create new BlendTree in State
- Delete
通过代码控制状态机:
// 获取Animator组件 private Animator _animator; _animator = GetComponent<Animator>(); // 设置 _animator.SetInteger(string name, int value); _animator.GetInteger(string name); _animator.SetFloat(string name, float value); _animator.GetFloat(string name); _animator.SetBool(string name, bool value); _animator.GetBool(stirng name); _animator.SetTrigger(string name); // 将一个字符串类型的参数名称转成一个对应的HashCode int Animator.StringToHash(string name); // 使用整型id设置,可以稍微提高效率 _animator.SetInteger(int id, int value); _animator.GetInteger(int id); _animator.SetFloat(int id, float value); _animator.GetFloat(int id); _animator.SetBool(int id, bool value); _animator.GetBool(int id); _animator.SetTrigger(int id);
- Add Behaviour 给状态添加行为,会生成一个脚本,里面包含StateMachineBehaviour回调
// 参数 // animator:状态机所在的Animator组件,可以通过它.gameObject拿到游戏对象 // stateInfo:动画状态信息,用来区分不同的状态 // layerIndex:动画层下标,从0开始 // 回调函数 // 进入状态的时候调用一次 override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {} // 在某个状态停留的时候持续调用,每帧一次 override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {} // 退出状态的时候调用一次 override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {} // 会在OnAnimatorMove方法之前调用,如果使用了Apply Root Motion进行移动,就不要使用这个,unity会放弃自动位移 override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {} // 调用IK动画方法OnAnimatorIK之后调用 override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {} // 判断状态名字是否为指定的 if (stateInfo.IsName("idle")) { Debug.Log("name idle"); } // 判断状态的标签是否为指定的 if (stateInfo.IsTag("idle")) { Debug.Log("tag idle"); }
7. BlendTree 动画融合树
可以给几个动画做一个融合
右键 -> Create -> From new Blend Tree,双击编辑,点左上角导航条返回,右键Add motion添加动画
就是一种状态
- Blend Type:融合类型
- 1D:用一个参数来控制这些动画的切换
- 2D:用两个参数来控制
- Simple:所有被融合的动画方向不一致
- Freedom Direction:相比第一个,需要有一个没有方向的动画
- Freedom Cartesian:随意融合
- Pos X:第一个参数
- Pos Y:第二个参数
- Direct:做表情动画
- Thresholds:阈值
- 播放倍数
- 镜像动画
- Automate Thresholds:是否自动设置阈值
- Compute Thresholds:根据计算的值来设置阈值
- speed:速率
- Velocity X
- Velocity Y
- Velocity Z
- AngularSpeed Deg
- AngularSpeed Red
8. Layers 分层动画
每个层是有权重的
- Base Layer:权重不能设置,默认为1
- Avatar Mask:骨骼遮罩,添加遮罩后,该层上会有“M”标记
- 层有下标,从0开始
- 用代码修改层的权重
private Animator _animator;
_animator = GetComponent<Animator>();
if (Input.GetKeyDown(KeyCode.R)
{
// 设置权重
SetLayerWeight(int layerIndex, float value);
}
9. IK 动画
- IK:反向动力学
- 人型模型的IK部位:双手 双脚 眼睛
- 动画状态机中的层设置,IK Pass按钮需要勾选,选上后该层上有“IK”标记
// 获取组件
private Animator _animator;
_animator = GetComponent<Animator>();
// 获取敌人
private Transform _enemy;
_enemy = GameObject.Find("enemy").transform;
// 如果要做IK动画,必须在这个方法中完成
private void OnAnimatorIK(int layerIndex)
{
// 角度限制
// 做一个角色指向敌人的向量
Vector3 direction = _enemy.position - transform.position;
// 求这个向量和角色前方的向量的夹角
float angle = Vector3.Angle(direction, transform.forward);
// 判断
if (Mathf.Abs(angle)) <= 60)
{
// 设置IK部位的权重
_animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
// 设置IK部位的位置
_animator.SetIKPosition(AvatarIKGoal.RightHand, _enemy.position);
}
}
10. Curves 动画曲线
Animation标签中设置
- 横轴:范围[0, 1],到1动画结束
- 纵轴:随时间改变而变,代表一个值,而且我们能随时获取到
// 需求:根据动画中曲线Curve的值,来实时调整BoxCollider的高度
// 获取Animator组件
private Animator _animator;
_animator = GetComponent<Animator>();
// 获取BoxCollider
private BoxCollider _collider;
_collider = GetComponent<BoxCollider>();
// 获取Curve曲线的值
// 方式:在状态机中添加一个Float类型的参数,名字和曲线名字一致即可
float curve = _animator.GetFloat(Animator.StringToHash("Curve"));
_collider.size = new Vector3(_collider.size.x, curve, _collider.size.z); // 这样会缩在人物中间
_collider.center = new Vector3(_collider.center.x, curve / 2, _collider.center.z); // 再控制一下高度
11. Event 动画事件(帧事件)
在动画播放的过程中,让它去执行一个特定的方法,这个方法必须是在玩家身上的脚本中,必须是public,返回值是void,名字和设置的名字相同,参数可以是无参,如果需要参数,则参数类型必须是Float,Int,String,Object这四种之一,不能写多个,值就是Inspector面板上写的值
public void Shout ()
{
_audio.Play();
}
七、粒子特效
1. 创建
- 右键 -> Effect -> Particle System
- Scene面板中的Particle Effect
- Play:播放特效
- Restart:重新播放特效
- Stop:停止播放特效
- Playback Speed:播放的速度
- Playback Time:已播放的时长(有速度加成)
- Particles:粒子的数量
- Speed Range:
- Simulate layers:模拟的层
- Resimulate:对粒子系统的更改是否马上生效
- Show Bounds:显示边界
2. Particle System 组件
- Particle System:粒子系统
- Duration:粒子生成的持续时间
- Looping:循环生成
- Prewarm:预热,满数量开始特效
- Start Delay:延迟生成
下箭头展开- Constant:常量
- Random Between Two Constants:在两个常量之间随机
- Start Lifetime:粒子的生命周期,一个粒子从生成到销毁需要经历多长时间,默认5s
下箭头展开- Constant:常量
- Curve:曲线
- 横轴:Duration 粒子生成的持续时间
- 纵轴:常量
- Random Between Two Constants:在两个常量之间取随机数
- Random Between Two Curves:在两个曲线之间取随机数
- Start Speed:粒子的初始移动速度
- 3D Start Size:分轴调整粒子的初始大小
- Start Size:粒子的初始大小
- 3D Start Rotation:分轴设置粒子的初始旋转
- Start Rotation:粒子的初始旋转
- Flip Rotation:范围[0, 1],有这些比例的粒子会以反方向生成
- Start Color:粒子的初始颜色
- Color:纯色
- Gradient:渐变
上标签表示透明度,下标签表示颜色,最多加8个标签- Mode:调色板模式
- Blend:渐变
- Fixed:没有渐变直接改变颜色
- Location:在时间轴的位置代表生命周期比例
- Mode:调色板模式
- Random Between Two Colors:在两种颜色重随机
- Random Betwwen Two Gradients:在两个调色板中随机
- Random Color:随机颜色
- Gravity Modifier:重力调整
- Simulation Space:坐标系,设置粒子特效会随哪个物体移动而移动
- Local:本地坐标
- World:世界坐标
- Custom:自定义坐标
- Simulation Speed:调整特效播放速度
- Delta Time:是否使用Time.deltaTime属性来计算特效播放,用于在暂停的时候播放特效
- Scaling mode:如何使用scale值
- Hierarchy:从Hierarchy组合计算出自己的缩放值
- Local:只使用自己的坐标系,忽略任何父物体
- Shape:将缩放使用给粒子的位置,而不是缩放大小
- Play On Awake:程序激活后就播放特效,会影响到同一个粒子系统中的其他特效
- Emitter Velocity:当粒子系统移动的时候使用哪种方式计算速度
- Transform:使用坐标计算速度
- Rigidbody:使用刚体计算速度
- Max Particles:在这个特效系统中的最大粒子数量
- Auto Random Seed:是否每次激活时使用新的随机种子生成随机数
- Stop Action:当粒子系统停止产生粒子并且所有的粒子都消失后,对游戏物体做什么动作
- None:什么都不做
- Disabled:取消激活
- Destroy:销毁游戏物体
- Callback:回调
- Emission:生成
- Rate Over Time:粒子每秒生成的数量(粒子生成速度)
- Rate Over Distance:每移动单位长度能产生多少粒子,需要配合世界坐标系使用
- Bursts:爆发产生粒子
- Time:从特效播放开始多长时间开始Burst
- Count:一次Burst产生多少数量的粒子
- Cycles:这个特效产生多少次Burst
- Interval:两次Burst之间的时间间隔
- Shape:形状
- Shape:
- Sphere:球形
- 半球形,上半球
- Cone:锥形
- Donut:环形
- Box:立方体形
- Mesh:网格
- Circle:圆形
- Line:线形
- Shape:
- Velocity Over Lifetime:随生命周期修改粒子的速度
- Force Over Lifetime:随生命周期修改粒子收到的力
- Color Over Lifetime:随生命周期修改粒子的颜色
- Size Over Lifetime:随声明周期修改粒子的大小
- Renderer:渲染
- 材质决定效果,标准资源包中有一些材质
3. Trail Renderer 轨迹渲染,拖尾
- Trail Renderer 组件
- Width:宽度,数值代表缩放比例
4. Line Renderer 线性渲染
- Line Renderer 组件
- Materials:材质
- Loop:首尾相连
- Width:宽度
- Positions:位置
- Size:位置集合的长度
- Element 0:位置
- 用代码控制线性渲染
// 获得组件
private LineRenderer _line;
_line = GetComponent<LineRenderer>();
// 设置线性渲染需要
// 1. 设置一共要经过多少个点
_line.positionCount = 3;
// 2. 设置每个点的坐标
// 通过每个点的序号设置每个点的坐标
_line.SetPosition(int index, Vecotr3 position);
// 传一个向量数组作为所有点的坐标,导航的NavMeshPath返回的就是一个Vector3[],但是不包含起始点和终点
_line.SetPositions(Vector3[] positions);
// 设置loop,导航获得的路径不需要首尾相连
_line.loop = false;
八、导航
1. 导航的定义
- 不同于现实中的GPS,unity中的导航主要体现在鼠标点击地面移动和自动寻路
- 如果有多条路,会选择最近的路线进行导航
- 导航物体需要放置在可移动的导航网格上的时候才可以开始导航
2. 步骤
- 设置导航静态物体(Navigation Static),哪些东西是不能动的
方法如下:- 在 Inspector 面板上 name 后面选择 Navigation Static
- 在 Navigation 面板上的 Object 中也能设置
- 可以设置父物体为导航静态物体,然后选择应用于子物体
- 烘焙地图
但凡是场景发生改变,都需要重新烘焙地图 - 实现导航,添加 NavMeshAgent 组件
脚本控制,脚本导航谁就挂给谁
// 使用命名空间
using UnityEngine.AI;
// 获取导航组件
private NavMeshAgent _agent;
_agent = GetComponent<NavMeshAgent>();
// 获取终点
private Transform _target;
_target = GameObject.Find("target").transform;
// 开启导航
if (Input.GetMouseButtonDown(0))
{
// 想开启导航只需要设置终点即可
// 设置属性
_agent.destination = _target.position;
// 使用方法
_agent.SetDestination(_target.position);
}
3. Navigation 面板
- window -> Navigation面板
- Agents:可以设一些导航物体模型,给NavMeshAgent中的Agent Type用
- Areas:分层
- 系统自带三个层,也可以新建层,颜色对应地图上导航网格的颜色
- Built-in 1:Not Walkable
- Built-in 0:Walkable 默认为这个层
- Built-in 2:Jump
- 自定义的层...
- Cost:Unity在计算线路时候附加的权重
- 系统自带三个层,也可以新建层,颜色对应地图上导航网格的颜色
- Bake:烘焙地图
- Baked Agent Size:根据多大的角色来烘焙地图
导航物体是一个圆柱体- Agent Radius:圆柱体半径,不能设置太小,如果设置太小,Unity会提示;如果设为0,会被调为1
- Agent Height:圆柱体高度
- Max Slope:最大爬坡的坡度限制,范围[0, 60]
- Step Height:台阶的高度,角色能迈上去的最高高度,最高和身高一样,超过的话按最高身高算
- Generated Off Mesh Links:分离路面导航路线连接设置
- Drop Height:允许向下掉落的最高高度
- Jump Distance:允许跳跃的最大距离
- Clear按钮,点击一次清除Scene面板中所有的导航网格
- Bake按钮,点击一次获得蓝色导航网格,根据地图的复杂程度来决定烘焙时间,只能在导航面板才会看到导航网格,点击一次变成Cancel,不要连点
- Baked Agent Size:根据多大的角色来烘焙地图
- Object:更改指定物体的导航属性
只有带Mesh Renderer的物体或Terrain才能被更改- Scene Filter:显示过滤器
- All:所有物体
- Mesh Renderers:所有带Mesh Renderer的物体
- Terrain:所有地形
- Navigation Static:导航静态物体
- Generate OffMeshLinks:分离路面导航
- Navigation Area:分层路面导航所在的层
- Scene Filter:显示过滤器
4. NavMeshAgent组件
- Agent Type:此物体的导航物体类型
- Base Offset:导航圆柱体的物体本身的偏移量
- Steering:导航速度相关设置
- Speed:导航移动的最大速度
- Angular Speed:导航移动的最大角速度
- Acceleration:加速率
- Stopping Distance:距离终点还有多远就停下来(例:去找npc,找到后会和npc保持一段距离,到达停止距离的时候开始减速,停的位置会比设置的距离近一些)
- Auto Braking:自动刹车,当角色到导航终点的时候自动减速
- Obstacle Avoidance:障碍物的回避设置
- Radius:回避半径
- Height:回避高度
- Quality:回避质量,如果有大量的Agent,将这个值设为0,只处理碰撞,不处理其他的Agent和障碍,节省CPU性能
- Priority:优先级,范围[0, 99],数字越低优先值越高,物体只会回避数字比自己低的障碍
- Path Finding:寻路相关设置
- Auto Traverse Off Mesh Link:自动通过OffMeshLink
- Auto Repath:自动重新计算路线
- Area Mask:物体能走哪些层
5. 代码控制
// 设置终点,自动开始导航
// 设置属性
_agent.destination = _target.position;
// 使用方法
_agent.SetDestination(_target.position);
// 能让导航圆柱体带动物体的位置更新
_agent.updatePosition = false;
_agent.updateRotation = false;
// 导航剩余距离
_agent.remainingDistance; // 过大的时候显示Infinity
// 停止导航
_agent.Stop(); // 弃用
// 恢复导航
_agent.Resume(); // 弃用
// 建议使用
_agent.isStopped = true; // true 为停 false 为导航
// 期望速度(Vector3),Scene面板上的红箭头
_agent.desiredVelosity
// 实际速度(Vector3),Scene面板上的蓝箭头
_agent.velosity
// 声明 path 储存路径信息
NavMeshPath path = new NavMeshPath();
// 计算能否导航到指定的点,如果能,把路径信息存到path中(返回值为bool)
_agent.CalculatePath(vector3 targetPosition, NavMeshPath path);
// 获取路径信息,信息包含了所有的拐点
Vector3[] corners = path.corners;
// 控制物体可以走哪些层(int)
// 0:Nothing
// -1:Everything
// 1:Walkable 1 << 0
// 2:Not Walkable 1 << 1
// 4: Jump 1 << 2
_agent.areaMask = 1 << 1 | 1 << 2; // 可以走Not Walkable和Jump层
6. 动态路障
需要加Nav Mesh Obstacle组件,不用重新烘焙,不会影响导航网格,碰到路障后等待
- Shape:形状
- Capsule
- Box
- Center:中心点设置
- X
- Y
- Z
- Size:大小设置
- X
- Y
- Z
- Carve:碰到路障后是否重新规划路径,如果路都不通,就找一个距离终点最近的地方等待
7. 分离路面导航
- 从一个路面到另一个路面上,两个路面没有联系
- 一般情况下我们把除了地面之外的物体设为分离路面,Navigation面板上设置Off Mesh Link Generation下面的内容
- 跳下去和跳过去的问题通过修改Navigation -> Bake -> Generate Off Mesh Links下面的值就可以,跳上去的问题需要用到Off Mesh Link组件
- Off Mesh Link组件
点到点跳跃时需要用到次组件- Start(Transform):起始点
- End(Transform):终点
- Cost Override:计算路径时候的权重
- Bi Direction:是否为双向通行
- Activated:是否激活
- Auto Update Position:是否随着起点终点的改变而改变导航的线路
- Navigation Area:导航线路所在的层
九、数据持久化存储
将数据存储在硬盘中
1. Player Prefs类
- Unity内置的一个用来做数据存储的类
- 优点:使用方便,简单
- 缺点:存储的数据类型有限 int float string
- 适用于:关卡数,最高分,UesrID等...
// 存
PlayerPrefs.SetInt(string key, int value);
PlayerPrefs.SetFloat(string key, float value);
PlayerPrefs.SetString(string key, string value);
// 取
PlayerPrefs.GetInt(string key);
PlayerPrefs.GetInt(string key, int defaultValue); // 尝试取,如果能取到,返回值,如果取不到,返回默认值
PlayerPrefs.GetFloat(string key);
PlayerPrefs.GetFloat(string key, float defaultValue); // 尝试取,如果能取到,返回值,如果取不到,返回默认值
PlayerPrefs.GetString(string key);
PlayerPrefs.GetString(string key, string defaultValue); // 尝试取,如果能取到,返回值,如果取不到,返回默认值
- 存储路径
- Mac OS X:~/Library/Preferences/
- Windows:HKCU\Software\ [company name]\ [product name]
- 存储的内容不会互相转型,用什么类型存的就要用什么类型取
- 存储的内容更新不及时,在程序结束的时候才会同步,需要及时Save()
// 删除:
// 删除指定键值对
PlayerPrefs.DeleteKey(string key);
// 删除所有的键值对
PlayerPrefs.DeleteAll();
// 判断文件中是否包含指定的键值对
bool has = PlayerPrefs.HasKey(string key);
// 将对文件的修改同步到文件
PlayerPrefs.Save();
2. XML
- XML是一个特殊格式文档
- XML的结构
- 节点
- 元素节点:成对出现
<x> </x>
- 属性节点:包含
name = "xiaoming"
- 文本节点:元素节点包含的内容是文本节点
- 元素节点:成对出现
- 元素节点是互相嵌套的,属性节点依赖于元素节点,文本节点包含在元素节点尖括号中
- 根节点:直接写在文档中的元素节点,被称为是根节点(ROOT),在一个XML文档中,根节点有且只有一个
- 节点
<!-- 头部信息 -->
<?xml verson="1.0" encoding="utf-8"?>
<Person>
<name type="string" type1="hello world">xiaoming</name>
<age>10</age>
<Pet>
<Dog>
<name>hashiqi</name>
<age>1</age>
</Dog>
</Pet>
<Pets>
<Dog>
<name>hashiqi</name>
<age>2</age>
</Dog>
<Dog>
<name>taidi</name>
<age>3</age>
</Dog>
<Dog>
<name>keji</name>
<age></age>
<!-- 如果节点没有任何内容和子节点,可以直接内部闭合 -->
<age />
</Dog>
</Pets>
</Person>
-
C# 中的 System.Xml 类
-
继承结构:
- System.Object
- System.Xml.XmlNode(表示XML节点)
- System.Xml.XmlDocument(表示XML文档)
- System.Xml.XmlAttribute(表示XML属性节点)
- System.Xml.XmlLinkedNode
- System.Xml.XmlElement(表示XMl元素节点)
- System.Xml.XmlNode(表示XML节点)
- System.Object
-
属性和方法:
- System.Xml.XmlNode:所有节点的父类
属性和方法 作用 InnerText 获取或设置文本节点的值 InnerXml 获取 或 通过直接编写XML设置子节点 Value 获取或设置属性节点的值 AppendChild(XmlNode node) 将制定的节点添加到该节点的子节点列表的末尾 XmlNodeList ChildNodes 获取所有子节点 XmlNode FirstChild 获取第一个子节点 bool HasChildNode 判断该节点是否包含任何子节点 SelectSingleNode(string XPath) 选择匹配XPath表达式的第一个节点 SelectNodes(string XPath) 选择所有匹配XPath表达式的节点 - System.Xml.XmlDocument:文档
属性和方法 作用 CreateXmlDeclaration(string version, string encoding, string standalone) 创建一个具有指定参数的描述(头节点) CreateElement(string name) 创建具有指定名称的元素节点 CreateNode 创建任意一种节点,使用时需要向下转型 AppendChild(XmlNode node) 继承自XmlNode,添加子节点 Save(string path) 将XML文档保存到指定的文件 Load(string filename) +3 overload 从指定的URL加载XML文档 LoadXML(string xml) 从指定的字符串加载XML文档 - XmlElement:元素节点
属性和方法 作用 SetAttribute(string attributeName, string attributeValue) 设置具有指定名称的属性节点 GetAttribute(string attributeName) 返回具有指定名称的属性值 GetAttributeNode(string name) +1overload 获得具有指定名称的属性节点 HasAttributes(string name) 判断该元素节点是否包含属性
-
-
生成XML的步骤
- 引用命名空间
System.Xml
- 生成XML文档(XmlDocument类)
- 生成根元素(XmlElement类)添加给文档对象
- 依次生成子元素添加给父元素
- 将生成的XML文档保存
- 引用命名空间
-
使用代码生成XML文档
// 1. 引用命名空间
using System.Xml;
// 2.1 设定文档路径
private string path = Application.dataPath + "/File/boxCollider.xml";
// 2.2 创建XML文档
// 2.2.1 创建一个XmlDocument文档对象
XmlDocument document = new XmlDocument();
// 2.2.2 创建一个头部描述文件,version需要是一个可以转型成float类的值,standalone传null
XmlDeclaration declaration = document.CreateXmlDeclaration("1.0", "utf-8", null);
// 2.2.3 将生成的描述信息添加到文档中
document.AppendChild(declaration);
// 3.1 创建一个根元素节点
XmlElement root = document.CreateElement("BoxCollider");
// 3.2 将这个节点添加到文档中
document.AppendChild(root);
// 4.1.1 创建元素节点
XmlElement trigger = document.CreateElement("isTrigger");
// 4.1.2 给root添加子节点
root.AppendChild(trigger);
// 4.1.3 给元素节点设置文本节点
trigger.InnerText = "False";
// 4.1.1 创建元素节点
XmlElement material = document.CreateElement("material");
// 4.1.2 给root添加子节点
root.AppendChild(material);
// 4.1.3 给元素节点设置文本节点
trigger.InnerText = "None";
// 5 添加属性节点
// 5.1.1 创建一个属性节点
XmlAttribute attribute1 = document.CreateAttribute("type");
// 5.1.2 设置属性节点的值
attribute1.Value = "Physic Material";
// 5.1.3 给元素节点添加属性节点
material.setAttributeNode(attribute1);
// 5.2 设置属性节点的方法
material.SetAttribute("type", "Physic Material");
// 6. 删除节点
trigger.RemoveAll();
root.RemoveChild(material);
// 7. 直接设置内部xml元素
root.InnerXml = "<size><x>0</x><y>0</y><z>0</z></size>";
// 8. 保存xml文件
document.Save(path); // 需要传入完整的路径加文件名,如果路径文件存在,则会覆盖原文件
- 使用代码读取XML文档
// 创建一个XmlDocument对象
XmlDocument document = new XmlDocument();
// 2. 用document读取内容
// 读取指定的文档
document.load(path);
// 读取指定的xml字符串
document.loadXml(@"
<size>
<x>1</x>
<y>1</y>
<z>1</z>
</size>
")
// 1. 获取所有的子节点
XmlNodeList list = document.ChildNodes;
// 2. 获取第一个子节点
XmlNode root = document.FirstChild;
// 3. 获取指定的节点
XmlNode node = document.SelectSingleNode("Inspector");
// 4. 获取一个节点的InnerText
XmlNode node = document.SelectSingleNode("//BoxCollider");
string innerText = node.InnerText;
// 5. 获取一个节点的属性
XmlNode node = document.SelectSingleNode("/Inspector/BoxCollider/Material");
// 将node强转为Element
XmlElement element = node as XmlElement;
// 获取element身上的属性节点
XmlAttribute attr = element.GetAttributeNode("type");
// 5.2 获取属性
string result = element.GetAttribute("type");
- 常用的XPath语法
// 1. / 路径分隔符
XmlNode node = document.SelectSingleNode("/Inspector/GameObject");
// 2. // 查找节点, 无论这个节点在哪都能找到
XmlNode trigger = node.SelectSingleNode("//IsTrigger");
// 3. .当前目录 ..父目录
XmlNode node = document.SelectSingleNode("//Center");
XmlNode x = node.SelectSingleNode("../IsTrigger");
// 4. 查找指定下标的节点
// 查询第2个x
XmlNode node = document.SelectSingleNode("/Inspector/x[2]");
// 查询最后一个x
XmlNode node = document.SelectSingleNode("/Inspector/x[last()]");
// 查询倒数第二个
XmlNode node = document.SelectSingleNode("/Inspector/x[last() - 1]");
// 查询序号大于1小于4的x
XmlNodeList list = document.SelectNodes("/Inspector/x[position() < 4 and position() > 1]");
// 5. |
// 查找所有的x和y节点
XmlNodeList list = document.SelectNodes("//x | //y");
// 6. 查找Pet中第二个Dog元素的name元素
XmlNode node = document.SelectSingleNode("//Pet/Dog[2]/name");
// 7. 查找指定的Person元素,要求Person中的age元素必须大于30
XmlNodeList list = document.SelectNodes("/Inspector/Person[age>30]");
// 8. 查找具有指定属性type的material节点
XmlNodeList list = document.SelectNodes("//Material[@type]");
// 9. 查找指定的属性type
XmlNodeList list = document.SelectNodes("//@type");
3. JSON
- 语法:
- 大括号:写键值对,键和值之间用冒号分隔,键值对之间用逗号分隔,类似字典
- 中括号:写数据,类似数组
// json的注释
/*
json的注释
*/
{
"name": "xiaoming",
"age": 10,
"gender": '男'
"pet":
{
"name": "hashiqi",
"age": 3
}
}
// 另一段json
{
"name": "诺克萨斯之手",
"hp": 582.24,
"attack": 55.88,
"skills":
[{
"name": "被动技能-出血"
},{
"name": "Q技能-大杀四方",
"cd": [9, 8, 7, 6, 5],
"mp": [30, 30, 30, 30, 30]
},{
"name": "W技能-致残打击",
"cd": [9, 8, 7, 6, 5],
"mp": [30, 30, 30, 30, 30]
},{
"name": "E技能-无情铁手",
"cd": [24, 21, 18, 15, 12]
},{
"name": "R技能-诺克萨斯断头台",
"cd": [120, 100, 80],
"mp": [100, 100, 0]
}]
}
- Unity使用的是 .net 2.0,没有对Json的支持,需要用第三方库
- System.Json(便于Json生成)
- LitJson(便于Json解析)
- System.Json 中的类
- JsonArray:数组
- JsonObject:键值对
- JsonValue:具体的值,可以是一个数字,一个字符串等,也可以是一个JsonArray或JsonObject
- LitJson
- 对象转Json:
JsonMapper.ToJson(T model)
- Json转对象:
JsonMapper.ToObject<T>(string jsonString)
- Project文件夹中建立Plugins文件夹,用于存放第三方库
Model.cs
- 对象转Json:
public class Player
{
public string name;
public int age;
public char gender;
public Dog pet;
}
public class Dog
{
public string name;
public int age;
public char gender;
}
JSONOperation.cs
using LitJSON;
// 实例化一个对象
Player player = new Player();
// 输入对象的信息
player.name = "xiaoming";
...
// 将Player的信息生成一个json
string json = JsonMapper.ToJson(player);
PlayerPrefs.SetString("json", json);
// 根据一个Json字符串解析成模型
string json = PlayerPrefs.GetString("json");
Player player = JsonMapper.ToObject<Player>(json);
- JsonUtility 类
- Unity集成的用来解析生成json的类,用法和LitJson一模一样,效率完胜LitJson
- 模型中嵌套的类需要添加特性[System.Serializable]
Model.cs
public class Player
{
public string name;
public int age;
public char gender;
public Dog pet;
}
[System.Serializable]
public class Dog
{
public string name;
public int age;
public char gender;
}
JsonUtility.cs
// 模型转Json
string json = JsonUtility.ToJson(player);
// Json转模型
Player player = JsonUtility.FromJson<Player>(json);
-
JsonUtility 中无法存 List<T> 和 Dictionary<Tkey, Tvalue> 的解决办法:
- List<T>:使用一个类来包含List<T>
public class PersonModel : ISerializationCallbackReceiver { public List<Person> person; public PersonModel() { person = new List<Person>(); } } [Serializable] public class Person { public string name; public int age; public Dog[] dogs; public Person() { dogs = new Dog[2]; } } [Serializable] public class Dog { public string name; public int age; public Gender gender; } public enum Gender { male, female }
- Dictionary<Tkey, Tvalue>:使用接口
ISerializationCallbackReceiver
public class PersonModel : ISerializationCallbackReceiver { [SerializeField] List<int> keys; [SerializeField] List<Person> values; public Dictionary<int, Person> personList; public PersonModel() { personList = new Dictionary<int, Person>(); } // 接口的实现方法,解序列化 public void OnAfterDeserialize() { var count = Math.Min(keys.Count, values.Count); personList = new Dictionary<int, Person>(count); for (var i = 0; i < count; ++i) { personList.Add(keys[i], values[i]); } } // 接口的实现方法,序列化 public void OnBeforeSerialize() { keys = new List<int>(personList.Keys); values = new List<Person>(personList.Values); } }
- List<T>:使用一个类来包含List<T>
-
JsonObject插件
- JsonUtility转对象的时候,如果对象包含子类,是无法转换到子类的,这时候需要使用JsonObject
- 在AssetStore下载JsonObject插件,并导入到Unity中
- 使用代码解析Json字符串,代码实例:
private List<Item> itemList = new List<Item>(); /// <summary> /// 解析物品Json /// </summary> public void ParseItemJson(string itemsJson) { JSONObject j = new JSONObject(itemsJson); foreach (JSONObject temp in j.list) { int id = (int)temp["id"].n; string name = temp["name"].str; Item.ItemType type = (Item.ItemType)System.Enum.Parse(typeof(Item.ItemType), temp["type"].str); Item item = null; switch (type) { case Item.ItemType.Consumable: int hp = (int)temp["hp"].n; int mp = (int)temp["mp"].n; item = new Consumable(id, name, type, hp, mp); break; // 其他类型省略 default: break; } Debug.Log("item.id = " + item.ID + " , consumable.hp = " + ((Consumable)item).HP); itemList.Add(item); } }
4. 数据库
4.1 基础知识
- 数据库分类
- 本地数据库
- Sqlite 轻便
- 网络数据库
- Oracle
- SQL Server
- MySQL
- DB2
- 本地数据库
- SqliteManager 可视化管理工具
- Unity支持的数据库后缀名 .sqlite .db
- 数据库中数据的存储格式
- 表格
- 字段(键),不会重复的键称为主键,主键最多1个,可以没有
- 值
- 字段(键),不会重复的键称为主键,主键最多1个,可以没有
- 表格
4.2 SQL语句
操作 | 语句 | 备注 |
---|---|---|
建表 | CREATE TABLE 表名 (键 类型, 键 类型, ...); | |
建表改进 | CREATE TABLE IF NOT EXISTS 表名 (键 类型, 键 类型, ...); | 只有不存在此表的时候才添加,避免报异常 |
给表格中所有的字段赋值 | INSERT INTO 表名 VALUES (值,值,值,值); | 有多少键写多少值 |
给部分字段赋值 | INSERT INTO 表名 (键,键,键,...) VALUES (值,值,值,...); | 键的顺序可以不和建表的时候一样,如果表中有NOT NULL的字段但是没有赋值,会报错 |
条件语句 | WHERE 条件 | > >= < <= = != AND OR NOT/! + - * / % |
从表中删除所有的数据 | DELETE FROM 表名; | |
从表中删除满足条件的数据 | DELETE FROM 表名 WHERE 条件; | |
从表中修改全部的数据 | UPDATE 表名 SET 键=值, 键=值, ...; | |
从表中修改满足条件的数据 | UPDATE 表名 SET 键=值, 键=值, ... WHERE 条件; | |
查询表中所有的数据的所有字段值 | SELECT * FROM 表名; | |
查询所有满足条件的数据的所有字段值 | SELECT * FROM 表名 WHERE 条件; | |
查询所有满足条件的数据的指定字段值 | SELECT 字段, 字段, ... FROM 表名; | 按查询时字段的顺序显示 |
查询结果排序 | SELECT * FROM 表名 WHERE 条件 ORDER BY 键 (ASC/DESC), 键 (ASC/DESC), 键 (ASC/DESC), ...; | ASC 升序 DESC 降序,默认按照主键升序,默认升序所以ASC可以省略 |
- SQL语句不区分大小写,表名和键是区分大小写的
- 每一个键都可以有修饰,修饰语句放到类型后面
常用修饰符:- PRIMARY KEY:主键
- NOT NULL:非空类型
- 类型可以不写,默认是TEXT类型
// 需求:Person
// 字段:姓名(主键),年龄,性别
// 建表
CREATE TABLE Person (name TEXT PRIMARY KEY NOT NULL, age INTEGER, gender);
CREATE TABLE IF NOT EXISTS Person (name TEXT PRIMARY KEY NOT NULL, age INTEGER, gender);
// 增
INSERT INTO Person VALUES ('小明', 10, '男');
INSERT INTO Person (name, age) VALUES ('小红', 12);
// 删
DELETE FROM Person WHERE age > 100 AND age < 20000;
DELETE FROM Person WHERE age % 2 = 1;
// 改
UPDATE Person SET gender = '不知道';
UPDATE Person SET gender = '知道' WHERE age > 100;
// 查
SELECT * FROM Person WHERE age > 100 ORDER BY gender DESC;
4.3 代码使用SQL语句
- 需要用到的动态链接库
- Sqlite3.dll
- Mono.Data.Sqlite.dll
- System.Data.dll
- Window:\Unity\Editor\Data\Mono\lib\mono\2.0\
- MacOS:/Application/Unity/Unity.app/contents/Mono/lib/mono/2.0
- 使用到的类
类 | 描述 |
---|---|
SqliteConnection | 与数据库的连接对象 |
SqliteCommand | 执行SQL语句的对象 |
SqliteDataReader | 读取数据的对象 |
- SqliteConnection的方法
方法 | 作用 |
---|---|
SqliteConnection(path); | 构造方法,若路径下有数据库文件则建立连接,若没有数据库则新建数据库文件 |
SqliteCommand CreateCommand(); | 创建操作指令对象 |
Open(); | 打开数据库 |
Close(); | 关闭数据库 |
- SqliteCommand的方法
方法 | 作用 |
---|---|
int ExecuteNonQuery(); | 执行一个非查询语句,返回有多少行数据收到影响 |
object ExecuteScalar(); | 执行一个查询语句,返回查询到的数据中的第一行第一列 |
SqliteDataReader ExecuteReader(); | 执行一个查询语句,返回一个SqliteDataReader |
- SqliteDataReader的属性与方法
使用SqliteDataReader之后要关闭它,否则被视为上次的查询操作还没完成,无法再设置CommandText
属性与方法 | 作用 |
---|---|
bool Read(); | 类似于索引器的MoveNext(),读取下一个,返回是否有数据 |
FieldCount | 键的数量 |
string GetName(int index); | 通过键的下标,获取键的名称 |
object GetValue(int index); | 通过键的下标,获取键的值 |
Close(); | SqliteDataReader的方法,关闭SqliteDataReader |
using Mono.Data.Sqlite;
// 类
// 与数据库的连接对象
SqliteConnection _connection;
// 执行SQL语句的对象
SqliteCommand _command;
// 读取数据的对象
SqliteDataReader _reader;
// 1. 建立与数据库的连接
// 如果路径下没有数据库,系统会自动帮我们新建一个数据库,如果路径下有数据库,则建立连接
// 1.1 拼接一个数据库的路径
string path = "Data Source = " + Application.dataPath + "/Database/myDatabase.sqlite";
// 1.2 建立与数据库的连接
_connection = new SqliteConnection(path);
// 1.3 创建操作指令对象
_command = _connection.CreateCommand();
// 1.4 打开数据库
// 有打开数据库,就需要有关闭数据库,如果程序结束后数据库没关,数据库会锁上自己,再解锁很麻烦
_connection.Open();
// 2. 建表
// 2.1 写SQL语句
string sql = @"CREATE TABLE IF NOT EXISTS Hero (name TEXT PRIMARY KEY, gender TEXT, book TEXT);";
// 2.2 设置操作指令
_command.CommandText = sql;
// 2.3 执行SQL操作
// 执行一个非查询的语句
_command.ExecuteNonQuery();
// 3. 添加数据
// 3.1 添加一行数据
string sql = @"INSERT INTO Hero VALUES ("萧炎", "男", "斗破苍穹");";
_command.CommandText = sql;
_command.ExecuteNonQuery();
// 3.2 添加多行数据,并返回这个语句对多少行数据产生影响
string[] names = { "林动", "牧尘", "叶凡", "石昊", "唐三", "秦羽" };
string[] genders = {"男", "男", "男", "男", "女", "女"};
string[] books = { "武动乾坤", "大主宰", "遮天", "完美世界", "斗罗大陆", "星辰变" };
// SQL语句
string sql = "";
// 循环拼接
for (int i = 0; i < names.Length; i++)
{
sql += string.Format("insert into Hero values ('{0}', '{1}', '{2}');", names[i], genders[i], books[i]);
}
_command.CommandText = sql;
// 执行非查询语句并记录返回值
int result = _command.ExecuteNonQuery();
Debug.Log(result);
// 4. 删除数据
_command.CommandText = @"delete from Hero where gender = '女';";
int result = _command.ExecuteNonQuery();
Debug.Log("删除了" + result + "行数据");
// 5. 修改数据
_command.CommandText = @"update Hero set gender = '不知道' where name = '牧尘';";
_command.ExecuteNonQuery();
// 6. 查询数据
// 6.1 返回查询到的数据中的第一行第一列
_command.CommandText = @"select * from Hero;";
object result = _command.ExecuteScalar();
Debug.Log(result);
// 6.2 使用_reader来遍历获取所有的查询结果
_command.CommandText = @"select * from Hero;";
_reader = _command.ExecuteReader();
while (_reader.Read())
{
string s = "";
// 6.2.1 通过键的名字来取
s += _reader["name"] + " | ";
s += _reader["gender"] + " | ";
s += _reader["book"];
Debug.Log(s);
// 6.2.2 通过键的下标来取
s += _reader[0] + " | ";
s += _reader[1] + " | ";
s += _reader[2];
Debug.Log(s);
// 6.2.3 循环遍历
for (int i = 0; i < _reader.FieldCount; i++)
{
// 通过键下标, 获取键的名称
string key = _reader.GetName(i);
// 通过键的下标, 获取键的值
object value = _reader.GetValue(i);
s += _reader[i] + " | ";
}
Debug.Log(s);
}
// 注意!: _reader在使用结束后, 记得要关闭
_reader.Close();
// 7. 关闭数据库
private void OnDestroy()
{
_connection.Close();
}
十、进程,线程与协程
1. 进程
- 程序的执行单元,是程序执行所需要所有资源的总和,一个进程就相当于是一个程序,一个程序可以起多个进程,解决了多任务并发的问题
- PC端多用进程,移动端多用线程
2. 线程
- 程序执行的最小单元,进程包含线程,进程创建的时候会自动创建一个进程,称为主线程
- 一个进程可以包含多个线程,线程之间也有层级关系,A线程开辟了B线程,当A线程结束的时候,B线程也结束了
- 理论上说,线程越多,程序执行的效率越高,但是如果无限开辟线程反而会使程序执行效率降低,因为开辟线程需要消耗资源,同时线程会去分掉CPU的时间片
- 线程之间可以资源共享,而进程不能,为了避免多个线程同时访问一个资源,使用了线程锁,保证了同时只有一个线程访问资源
- 线程对游戏引擎不友好,游戏中需要执行的任务几乎都需要用主程序来执行
3. 协程(协同程序)
- 协程不是多线程,是运行在主线程中的一个程序
- 协程每帧执行一次,顺序是Update() -> coroutine -> LateUpdate()
// 动态返回一个枚举器接口的实现类
// 每调用一次yield return,将i加入到一个可枚举的序列里
public IEnumerator GetNumber()
{
for (int i = 0; i < 10; i++)
{
// 输出i
Console.WriteLine(i);
// 动态返回
yield return 0;
}
}
public IEnumerator GetNumber2()
{
for (int i = 0; i < 10; i++)
{
// 输出i
Console.WriteLine(i);
// 每3秒执行一次
yield return new WaitForSeconds(3);
}
}
// 协程的嵌套
public IEnumerator GetNumber3()
{
for (int i = 0; i < 10; i++)
{
// 输出i
Console.WriteLine(i);
// 等待下面的协程执行结束,继续执行
yield return StartCoroutine(GetNumber4());
// 是下面这种写法的简写
// Coroutine t = GetNumber4();
// yield return t;
}
}
public IEnumerator GetNumber4()
{
for (int i = 0; i < 10; i++)
{
// 输出i
Console.WriteLine(i);
yield return 0;
}
}
- 开启协程
// 用IEnumerator开启协程
StartCoroutine(IEnumerator enumerator);
// 使用方法名开启协程,返回值一定是IEnumerator
StartCoroutine(string MethodName);
StartCoroutine(stirng MethodName, object value);
- 停止协程
Coroutine t = StartCoroutine("GetNumber2");
StopCorountine(t);
// 停止全部的协程
StopAllCoroutine();
// 关键字跳出协程
yield break;
- WWW类
数据下载的类,使用它在网络中下载资源,一定要放在协程里面做下载
void Start()
{
string path = "http://...想下载图片的url";
StartCoroutine(Download(path));
}
private IEnumerator Download(string path)
{
WWW www = new WWW(path);
while(www.isDone == false)
{
// 下载中,打印进度
Debug.Log(www.progress);
yield return 0;
}
// 跳出循环,说明下载完成
}
private IEnumerator Download2(string path)
{
WWW www = new WWW(path);
// 等待下载
yield return www;
// 下载完成
}
十一、开发时遇到的一些问题
1. 鼠标点击穿透UI
- EventSystem
EventSystem中有方法IsPointerOverGameObject()用于判定是否鼠标划入了UI游戏物体
void Update()
{
// 判断鼠标是否在UI游戏物体上,如果在,就return
if(EventSystem.current.IsPointerOverGameObject()) return;
// 处理点击逻辑
// ...
}
- 利用UI的GraphicRaycaster判定
3D场景中Canvas和场景不是在一个平面上,所以在做鼠标点击事件的时候射线不会被UI挡住,在操作UI的时候背后的场景内也会与点击互动,解决办法如下:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystem;
public class UIInterceptRaycast : MonoBehaviour
{
// EventSystem
private EventSystem _eventSystem;
// GraphicRaycaster
private GraphicRaycaster _caster;
private void Awake()
{
_eventSystem = GameObject.Find("EventSystem").GetComponent<EventSystem>();
_caster = GameObject.Find("Canvas").GetComponent<GraphicRaycaster>();
}
private void Update()
{
if (CheckGuiRaycastObjects()) return;
// 在这里处理点击事件
// ...
}
// 检查射线是否打中UI上的物体
bool CheckGuiRaycastObjects()
{
PointerEventData eventData = new PointerEventData(_eventSystem)
{
pressPosition = Input.mousePosition,
position = Input.mousePosition
};
List<RaycastResult> list = new List<RaycastResult>();
_caster.Raycast(eventData, list);
//Debug.Log(list.Count);
return list.Count > 0;
}
}
注意:在做UI的时候一般会用一个Panel做根目录,这个panel也会被添加到GraphicRegistry中的公共列表中,如果是这样的话记得把list.Count > 0改成list.Count > 1,或者直接删除Panel上的继承自Graphic的组件
2. A*算法二维世界寻路
算法核心是存在两个集合,Open和Close,Open集合用于存可以移动的点,Close集合用于存舍弃的点。然后计算Open集合中每个点的F = G + H,G是从起点出发到当前点所消耗的数值,H是当前点到终点的预计消耗,取F最小的点为下一个移动的点,将其从Open集合中删除,加入到Close集合,循环执行,直到Open集合中没有元素,则找到了end
- Point.cs 用于存放点的信息的模型
public class Point
{
public Point Parent { get; set; }
public float H;
public int X;
public int Y;
public float G;
public float F;
public bool IsWall;
public Point(int x,int y,Point parent=null)
{
this.X = x;
this.Y = y;
this.Parent = parent;
}
public void UpdateParent(Point parent,float g)
{
this.Parent = parent;
this.G = g;
this.F = G+H;
}
}
- AStar.cs 寻路算法
using System.Collections.Generic;
using UnityEngine;
public class AStar : MonoBehaviour
{
private const int mapWidth = 15;
private const int mapHeight = 15;
private Point[,] map = new Point[mapWidth, mapHeight];
// Use this for initialization
void Start()
{
InitMap(); //初始化地图
Point start = map[2, 2];
Point end = map[12, 12];
FindPath(start, end);
ShowPath(start, end);
}
private void ShowPath(Point start, Point end)
{
int z = -1;
Point temp = end;
while (true)
{
//Debug.Log(temp.X + "," + temp.Y);
Color c = Color.gray;
if (temp == start)
{
c = Color.green;
}
else if (temp == end)
{
c = Color.red;
}
CreateCube(temp.X, temp.Y, z, c);
if (temp.Parent == null)
break;
temp = temp.Parent;
}
for (int x = 0; x < mapWidth; x++)
{
for (int y = 0; y < mapHeight; y++)
{
if (map[x, y].IsWall)
{
CreateCube(x, y, z, Color.blue);
}
}
}
}
private void CreateCube(int x, int y, int z, Color color)
{
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
go.name = x + "," + y;
go.transform.position = new Vector3(x, y, z);
go.GetComponent<Renderer>().material.color = color;
}
private void InitMap()
{
for (int x = 0; x < mapWidth; x++)
{
for (int y = 0; y < mapHeight; y++)
{
map[x, y] = new Point(x, y);
CreateCube(x, y, 0, Color.black);
}
}
map[4, 1].IsWall = true;
map[4, 2].IsWall = true;
map[4, 3].IsWall = true;
map[4, 4].IsWall = true;
map[4, 5].IsWall = true;
map[4, 6].IsWall = true;
}
/// <summary>
/// 查找最优路径
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
private void FindPath(Point start, Point end)
{
List<Point> openList = new List<Point>();
List<Point> closeList = new List<Point>();
openList.Add(start); //将开始位置添加进Open列表
while (openList.Count > 0)//查找退出条件
{
Point point = FindMinFOfPoint(openList);//查找Open列表中最小的f值
//print(point.F + ";" + point.X + "," + point.Y);
openList.Remove(point); closeList.Add(point);//不再考虑当前节点
List<Point> surroundPoints = GetSurroundPoints(point);//得到当前节点的四周8个节点
PointsFilter(surroundPoints, closeList);//将周围节点中已经添加进Close列表中的节点移除
foreach (Point surroundPoint in surroundPoints)
{
if (openList.IndexOf(surroundPoint) > -1)//如果周围节点在open列表中
{
float nowG = CalcG(surroundPoint, surroundPoint.Parent);//计算经过的Open列表中最小f值到周围节点的G值
if (nowG < surroundPoint.G)
{
print("123");
surroundPoint.UpdateParent(point, nowG);
}
}
else//周围节点不在Open列表中
{
surroundPoint.Parent = point;//设置周围列表的父节点
CalcF(surroundPoint, end);//计算周围节点的F,G,H值
openList.Add(surroundPoint);//最后将周围节点添加进Open列表
}
}
//判断一下
if (openList.IndexOf(end) > -1)
{
break;
}
}
}
private void PointsFilter(List<Point> src, List<Point> closeList)
{
foreach (Point p in closeList)
{
if (src.IndexOf(p) > -1)
{
src.Remove(p);
}
}
}
private List<Point> GetSurroundPoints(Point point)
{
Point up = null, down = null, left = null, right = null;
Point lu = null, ru = null, ld = null, rd = null;
if (point.Y < mapHeight - 1)
{
up = map[point.X, point.Y + 1];
}
if (point.Y > 0)
{
down = map[point.X, point.Y - 1];
}
if (point.X > 0)
{
left = map[point.X - 1, point.Y];
}
if (point.X < mapWidth - 1)
{
right = map[point.X + 1, point.Y];
}
if (up != null && left != null)
{
lu = map[point.X - 1, point.Y + 1];
}
if (up != null && right != null)
{
ru = map[point.X + 1, point.Y + 1];
}
if (down != null && left != null)
{
ld = map[point.X - 1, point.Y - 1];
}
if (down != null && right != null)
{
rd = map[point.X + 1, point.Y - 1];
}
List<Point> list = new List<Point>();
if (down != null && down.IsWall == false)
{
list.Add(down);
}
if (up != null && up.IsWall == false)
{
list.Add(up);
}
if (left != null && left.IsWall == false)
{
list.Add(left);
}
if (right != null && right.IsWall == false)
{
list.Add(right);
}
if (lu != null && lu.IsWall == false && left.IsWall == false && up.IsWall == false)
{
list.Add(lu);
}
if (ld != null && ld.IsWall == false && left.IsWall == false && down.IsWall == false)
{
list.Add(ld);
}
if (ru != null && ru.IsWall == false && right.IsWall == false && up.IsWall == false)
{
list.Add(ru);
}
if (rd != null && rd.IsWall == false && right.IsWall == false && down.IsWall == false)
{
list.Add(rd);
}
return list;
}
private Point FindMinFOfPoint(List<Point> openList)
{
float f = float.MaxValue;
Point temp = null;
foreach (Point p in openList)
{
if (p.F < f)
{
temp = p;
f = p.F;
}
}
print("返回open列表中最小的f:" + temp.F);
return temp;
}
private float CalcG(Point now, Point parent)
{
return Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(parent.X, parent.Y)) + parent.G;
}
private void CalcF(Point now, Point end)
{
//F = G + H
float h = Mathf.Abs(end.X - now.X) + Mathf.Abs(end.Y - now.Y);
float g = 0;
if (now.Parent == null)
{
g = 0;
}
else
{
g = Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(now.Parent.X, now.Parent.Y)) + now.Parent.G;
}
float f = g + h;
now.F = f;
now.G = g;
now.H = h;
}
}
引用自 https://blog.csdn.net/truck_truck/article/details/77373514#
十二、游戏内的常用系统
1. 背包系统
1.1 UI搭建
1.2 拖拽物品
使用OnDrag()方法,将需要移动的物体的位置改变
1.3 物品交换
需要在一个能够拿到所有物品Image的父物体上挂control脚本,在脚本中处理交换逻辑。每个子物体中挂相同脚本,在OnPointerEnter方法中设置target值,在OnPointerExit方法中将target设置为null,在OnBeginDrag方法中设置Original,在OnDrag方法中更改original的位置,在OnEndDrag方法中决定要不要交换。关键细节:需要将在上的物体的Raycast Target取消勾选,否则会影响OnPointerEnter方法中的target设置
1.4 制作背包系统的常用思路
设置一个子物体序号最高的Image类型UI的“temp”用于拖拽,大小和想拖动的Image一样,开始的时候取消激活。序号最高决定了拖拽的时候始终在上,将temp的Raycast Target取消勾选,OnBeginDrag中将temp放到original的位置,temp的图片设置成original的图片,将original的图片隐藏,OnDrag中改变的是temp的位置,OnEndDrag中再将temp取消激活,将original还原即可
附代码实现:
- Item.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class Item : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler , IPointerEnterHandler, IPointerExitHandler
{
//获得脚本
private ItemManager _manager;
private void Awake()
{
_manager = transform.parent.parent.parent.GetComponent<ItemManager>();
}
// 开始拖拽
public void OnBeginDrag(PointerEventData eventData)
{
// 设定交换源
_manager.Original = transform;
// 通知开始交换
_manager.BeginDrag();
}
// 拖拽中
public void OnDrag(PointerEventData eventData)
{
// 调用拖拽中的方法
_manager.OnDrag(eventData.position);
}
// 结束拖拽
public void OnEndDrag(PointerEventData eventData)
{
// 调用结束方法
_manager.EndDrag();
}
// 鼠标划入
public void OnPointerEnter(PointerEventData eventData)
{
// 设定target
_manager.Target = transform;
}
// 鼠标划出
public void OnPointerExit(PointerEventData eventData)
{
// 将target放空
_manager.Target = null;
}
}
- ItemManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ItemManager : MonoBehaviour {
// 发起交换的源
private Image _original;
// 交换的目标
private Image _target;
// 用于临时拖拽的Image
private Image _temp;
public Transform Original
{
set
{
_original = value.GetComponent<Image>();
}
}
public Transform Target
{
set
{
if (value != null)
{
_target = value.GetComponent<Image>();
}
}
}
// Use this for initialization
void Start () {
// 获取temp
_temp = transform.Find("Temp").GetComponent<Image>();
}
// 开始拖拽
public void BeginDrag ()
{
// 判断是否是空物体
if (_original.sprite.name == "Character_Slots_Background")
{
// 是拖拽的空物体,则什么都不做直接跳出
return;
}
// 隐藏original的图片
_original.enabled = false;
// 把temp位置设置到original的位置
_temp.transform.position = _original.transform.position;
// 把temp图片换成original的图片
_temp.sprite = _original.sprite;
// 把temp激活
_temp.gameObject.SetActive(true);
}
// 拖拽中
public void OnDrag (Vector2 position)
{
// 移动temp的位置
_temp.transform.position = position;
}
// 结束拖拽
public void EndDrag ()
{
// 因为之前拖拽物体判断是空物体的时候直接跳出,所以没有将
// temp激活,所以当temp为非激活的物体时,什么都不用做,直
// 接跳出
if (_temp.gameObject.activeSelf == false)
return;
// 判断是否要交换
if (_target != null)
{
Debug.Log("Need Swap");
// 需要交换
_original.sprite = _target.sprite;
_target.sprite = _temp.sprite;
}
// 还原original
_original.enabled = true;
// 隐藏temp
_temp.gameObject.SetActive(false);
}
}
2. 第一人称 FPS Demo
- 第一人称其实是在控制摄像机的移动,在官方的标准资源包里有一个设置好的第一人称镜头(Standard Assets/Character/FirstPersonCharacter)
- 枪作为摄像机的子物体,可以实现随摄像机旋转枪也跟着旋转
- 换枪就是激活要换的枪的GameObject,取消激活上一次的枪的GameObject
- 开枪的动画,音效随着点击鼠标而触发,需要添加一个计时器控制射速
- 刚换枪和换子弹的时候不能开枪,需要在状态机中两个状态的Behaviour中控制
- UI搭建子弹数,血量等内容
3. 塔防Demo
- 地图搭建
- 烘焙地图
- 摄像机控制
- CameraControl.cs
- 生成怪物
- MonsterInfo.cs,不继承Monobehaviour,做每波的波次信息的类,整个类Serializable
- WaveInfo.cs,用集合存所有的波次信息,做单例
- MonsterInit.cs,生成怪物,在start点生成
- Monster.cs,控制每个怪物的行为,在生成的时候挂给每一个怪物
- EndCheck.cs,怪物走到终点,销毁
- 生成防御塔(与生成怪物差不多)
- TowerInfo.cs,存储防御塔的信息类
- TowerCollection.cs,单例,用集合存所有的塔的信息
- TowerInit.cs,防御塔生成
- Tower.cs,控制每个塔
- TowerAttack.cs,防御塔攻击
- Bullet.cs,控制炮弹的脚本
- 玩家血量
- PlayerInfo.cs,记录玩家的信息
- UI
- 倍速按钮
- 暂停按钮
- 炮塔列表侧边栏
十三、插件
1. DOTween
DOTween is a fast, efficient, fully type-safe object-oriented animation engine for Unity, optimized for C# users, free and open-source, with tons of advanced features
附:有关脚本的一些琐碎知识点
1. this和gameObject区别
- gameObject:当前脚本挂载给的游戏对象,类型是GameObject
- this:当前脚本的对象,类型是脚本类型
2. gameObject可以省略
gameObject.GetComponent<BoxCollider>();
中 gameObject.
可以省略
3. .gameObject
与.transform
- 任意的组件对象,都可以通过
.gameObject
获取到游戏对象, - 任意的游戏对象,都可以通过
.transform
获取到transform组件
4. 在Inspector面板中设置脚本内字段
- 只有用
public
修饰的字段会在面板中出现,并且List<T>会自动实例化 - 只要面板中编辑过该字段的值,脚本代码无论改什么值,面板中都不会改变
public string name = "hello world";
public bool c;
public GameObject d;
public BoxCollider e;
public List<int> f;
public List<Person> p;
[System.Flags]
public enum Gender{
Male, Female
}
[System.Serializable]
public class Person{
public string name;
public int age;
public Gender gender;
}
5. 特性的使用
- 给类添加
// 不允许重复添加多个组件
[DisallowMultipleComponent]
// 组件依赖,添加的时候自动添加刚体组件
[RequireComponent(typeof(Rigidbody))]
- 给字段添加
// 在Inspector面板给字段添加说明头
[Header(string description)]
// 限制字段范围(浮点型和整型可以用)
[Range(float min, float max)]
// Inspector面板两行之间的间距
[space(100)]
// 可序列化,使得自定义类可以显示在Inspector面板上
[System.Serializable]
// 在Inspector面板上隐藏
[HideInInspector]
6. 单例脚本
如果一个脚本想要在其它脚本中频繁使用,可以做一个单例脚本
public static Joystick Instance;
private void Awake()
{
Instance = this; // this 指脚本对象
}
7. 设计模式
高内聚,低耦合
7.1 MVC
- Model:数据层,用来管理数据
- View:视图层,用来显示数据
- Controller:控制层,用来控制将数据给视图展示,协调数据层和视图层
7.2 MVVM
- Model:数据层
- View:视图层
- View-Model:绑定在视图层
8. 游戏物体标签
游戏物体在Inspector面板上第一个图标即为设置图标标签的地方,选择后会在Scene面板中显示一个图标,可以快捷的选中所设置的游戏物体,避免了多部分组成的模型点选时选不到总体的游戏物体,在搭建场景的时候可以提高效率
网友评论