思考并回答以下问题:
- Awake()和Start()有什么区别?Awake物体,Start脚本怎么理解?
- FixedUpdate什么时候使用?怎么设置?
- Start()和Update是有关系的怎么理解?
- 融合术在于实现动画的自然过渡怎么理解?
帧数:一秒内Update被调用的次数。性能高的电脑帧数大(例如60),性能低的电脑帧数小(30)。
Time.deltaTime 上一帧所消耗的时间,避免机器性能的影响。
Time.deltaTime也对每个电脑是不同的。Speed * Time.deltaTime只要保证一秒内移动相同的距离就可以。因为帧FPS是以秒为单位的。
坦克按住移动
1 | using UnityEngine; |
有如下方法:1
this.transform.position += new Vector3(0, 0, 5);
但基本不用这种方法移动。使用TransLate。
轴
Vector3表示轴的时候是没有大小的1
transform.Rotate(new Vector3(0, 1, 0), angleSpeed * Time.deltaTime);
原型如下:1
2
3
4
5
6
7
8
9
10
11
12[ ]
public void Rotate(Vector3 axis, float angle);
//
// 摘要:
// Applies a rotation of eulerAngles.z degrees around the z axis, eulerAngles.x
// degrees around the x axis, and eulerAngles.y degrees around the y axis (in
// that order).
//
// 参数:
// eulerAngles:
//
// relativeTo:
此时写成100也不影响:1
transform.Rotate(new Vector3(0, 100, 0), angleSpeed * Time.deltaTime);
而如果想要用向量表示角度,需要Rotate的另一个重载方法,原型如下:1
2
3
4
5
6
7
8
9
10
11
12[ ]
public void Rotate(Vector3 eulerAngles);
//
// 摘要:
// Rotates the transform around axis by angle degrees.
//
// 参数:
// axis:
//
// angle:
//
// relativeTo:
1 | transform.Rotate(new Vector3(0, angleSpeed * Time.deltaTime, 0)); |
1 | //Input.GetAxis 获取轴的数据 轴 水平轴 垂直轴 |
1 | //Input GetKey GetKeyDown GetKeyUp |
天空盒
两种方式
1、是给摄像机单独添加天空盒。直接给摄像机添加一个“Skybox”组件。然后放置材质。
2、是给整体添加一个天空盒。3步:
①新建一个天空盒材质。
②给天空盒材质添加天空盒。
③把材质赋给环境中的材质。
相同点:
①新建一个天空盒材质。
②给天空盒材质添加天空盒。
不同点:
赋材质的地点不同
天空盒图片又分:
(1)Cubemap
(2)六面
层次(Hierarchy)视图和项目(Project)视图
Hierarchy里的都是游戏对象,Create出来的也是游戏对象。
Project里的都是资源,物理资源,在电脑里可以打开的。
把Hierarchy里的东西拖进Project里,是序列化(持久化)的过程,是把对象变成物理存储。
把Project里的东西拖进Hierarchy,是把物理资源变成游戏对象,是反序列化。
摄像机
两个摄像机的深度值。大的显式。
小地图:两个摄像机的深度一样。调整另一个的Viewport Rect。
脚本
脚本生命周期
OnEnable()在Awake()和Start()中间执行
Awake()物体初始化的时候被调用,这儿有个关键词,“物体”,不管脚本组件是否被选中。因为脚本组件绑不绑定都不影响物体的初始化。物体没选中的时候(没激活)不会调用,因为物体没有初始化进入内存。
初始化是加载到内存里。
Start():Update()函数运行之前调用。脚本进入内存才启动Strat()。Start()和Update是有关系的。脚本选中激活后才会调用。这边也有个关键词:“脚本”。运行的过程中再次不选,选中脚本只会触发OnEnable()和OnDisable(),不会再触发Start()了。可知此时不选中时,脚本还是在内存里的,所以无需再次执行Start()。只有OnDestroy()才从内存里移除。
就像一个游戏对象绑定了很多组件,但是只有选中的组件,才会进入内存,不然都没选中,即不使用这些组件,那进入内存干什么?
OnDestroy():从内存中移除。
多个脚本的执行顺序
根据附加在GameObject上的顺序执行
这个所有脚本是一个对象上的所有脚本,还是所有对象上的所有脚本?
所有对象上的所有脚本
所有对象上的所有脚本的Awake()执行完后才执行Start()
所有对象上的所有脚本的Start()执行完后才执行Update()
所有对象上的所有脚本的Start()执行完后才执行LateUpdate()
查找
1 | using UnityEngine; |
拖曳的缺点:
1、后期不好维护
2、版本控制会丢失。
Find的缺点
1、不显示的物体找不到
2、全局查找,耗费性能
1 | private GameObject cube; |
下面这句很重要:1
cylinder = this.transform.FindChild("Cylinder").gameObject;
因为1
The game object this component is attached to. A component is always attached to a game object.
即可以根据组件寻找到对象1
组件.gameObject 即可得到绑定此组件的对象
unity中查找游戏物体是很寻常的操作,有较多的方法,如下:
unity中提供了获取对象的方法:
通过对象名称(Find方法)GameObject.Find
通过标签获取单个游戏对象(FindWithTag方法)
通过标签获取多个游戏对象(FindGameObjectsWithTags方法)
通过类型获取单个游戏对象(FindObjectOfType方法)
通过类型获取多个游戏对象(FindObjectsOfType方法)
Find方法:
static GameObject Find (string name)
传入的name可以是单个的对象的名字,也可以是hierarchy中的一个路径名,如果找到会返回该对象(活动的),如果找不到就返回null。
注:无论传值是名字或则路径名,只有对象存在就能返回该对象。建议传具体的路径名,以防有多个相同名字的对象的情况。且当有多个相同名字的对象的时候,返回为查找到的第一个对象。另不建议在每一帧都执行的函数(如update,fixupdate…)中调用该函数, 可以在Start这种函数中定义变量获取返回值,在其他函数中使用。
注:
1.使用对象名的情况:可查找带不带脚本,不查找隐藏(隐藏为active=false)的物体对象,返回的不一定是要查找的对象(有多个名相同物体的情况下)
2.使用目录结构:可查找带不带脚本,能查隐藏,可以确定是要找的对象
注:如果路径查找中的任何一个父节点active=false,这个对象都将查找不到
对比:
transform.Find()
1.对象名 只能查当前对象下一级子目录的对象,也基本确定,可查找隐藏对象,根节点需可见
2.目录结构 可查找带不带脚本对象,可以查隐藏物体,可以确定是要找的对象
脚本示例:
1 GameObject.Find(“GameObject”); 2 GameObject.Find(“GameObject/ChildGameObject);
总:使用目录结构进行查找较通过名字查询缩短了查询时间和范围,也更能确定对象,缺点是一旦路径或结构调整后,容易影响到程序。方便使用,但效率低下。
FindWithTag方法:
static GameObject FindWithTag (string tag)
返回一个用tag做标识的活动的对象,如果没有找到则为null。
tag设置:在hierarchy中选择对象,右侧的Inspector面板上面的选择Tag(可添加自定义:利用下拉列表中的AddTag创建)
FindGameObjectsWithTag方法:
static GameObject[] FindGameObjectsWithTag (string tag)
返回一个用tag做标识的活动的游戏物体的列表,如果没有找到则为null。
FindObjectOfType方法:
static Object FindObjectOfType(Type type)
返回类型为type的活动的第一个游戏对象
FindObjectsOfType方法:
static Object FindObjectsOfType(Type type)
返回类型为type的所有的活动的游戏对象列表
注意:一定保证对象是active的才会找到
效率问题,建议在初始函数中进行初始化
对比:
Transform.Find
1.可以查找隐藏对象
2.支持路径查找
3.查找隐藏对象的前提是transform所在的根节点必须可见,即active=true
Resources.FindObjectsOfTypeAll
返回指定类型的对象列表。主要用于编辑器中,eg。检测内存泄露、批量查找的功能等
GameObject.Find(“a”); // 相对路径查找
GameObject.Find(“/a”); // 绝对路径查找
即使隐藏root节点gameObject也能进行查找:
GetComponentsInChildren( typeof(Transform), true );
GetComponent
Resources
1 | using UnityEngine; |
打开项目
能看见Assets文件夹的路径打开,尽量不要点击场景打开。
脚本挂载
真正的开发中,除了游戏初始化脚本,脚本基本都是动态挂载的,不是最开始手动挂上去的。
添加组件:AddComponent<脚本名> 只要不是放在Editor下面就OK。
获取组件:GetComponent<脚本名>
没有直接的移除组件的API,要先获取再Destroy()。
碰撞体
Is Kinematic 取消物理效果
1 | ```cs |
坦克大战
1 | using UnityEngine; |
1 | using UnityEngine; |
1 | using UnityEngine; |
优化
1 | GameObject effect = Resources.Load<GameObject>("Effect"); |
电脑上的资源变成了游戏对象这个过程只要做一次。就在最开始的时候,所有Resources.Load只执行一次,然后把游戏对象存储进内存里。
把数据放进堆里面。
UGUI
anchor 锚点
presets 预调;预置
Rect Transform
Anchor Presets
锚点在左上角,缩小放大的时候和锚点(左上角)保持固定距离。
4个白色三角形不一定在一起。
压缩图片
www.tinypng.com
包体很大时从图片入手。
UGUI打包图集 UGUI不需要亲自去管理图集的打包
什么时候需要手动去打包?
Draw Call和SetPass Calls是一个东西
一个Canvas只占一个Draw Call。
直接把图片拉到场景里,会产生一个Draw Call,但复制时不会新增。(做2D游戏时经常这样直接拖进来,3D基本都使用UI系统的Canvas。)
自己也会有一个,所以10张图片有11个SetPass Calls。
如何打包?
Window->Sprite Packer
动画
bool 循环播放 走路
trigger 一次性的动作
阻尼就是阻止物体继续运动
动画状态机可以很灵活地进行动画的复用,也就是说实现人物的换装系统?
“融合术”技术,可以实现不同状态下动画剪辑的自然过渡,使得游戏画面更具真实感。
动画是在模型里的,动画和模型的图标是不一样的。
自身坐标
Inspector显示的是自身坐标。
自身坐标没有父物体毫无意义。
Inspector的Rotation指的是eulerAngles。
移动肯定是在世界坐标中移动。无论什么坐标都要转成世界坐标才能正确移动。