基础

思考并回答以下问题:

  • Awake()和Start()有什么区别?Awake物体,Start脚本怎么理解?
  • FixedUpdate什么时候使用?怎么设置?
  • Start()和Update是有关系的怎么理解?
  • 融合术在于实现动画的自然过渡怎么理解?

帧数:一秒内Update被调用的次数。性能高的电脑帧数大(例如60),性能低的电脑帧数小(30)。

Time.deltaTime 上一帧所消耗的时间,避免机器性能的影响。

Time.deltaTime也对每个电脑是不同的。Speed * Time.deltaTime只要保证一秒内移动相同的距离就可以。因为帧FPS是以秒为单位的。

坦克按住移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using UnityEngine;
using System.Collections;

public class Tank : MonoBehaviour {
public float speed = 2.5f;
// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {
if (Input.GetKey(KeyCode.W))
{
transform.Translate(new Vector3(0, 0, speed) * Time.deltaTime);
}

if (Input.GetKey(KeyCode.S))
{
transform.Translate(new Vector3(0, 0, -speed) * Time.deltaTime);
}

if (Input.GetKey(KeyCode.A))
{
transform.Rotate(new Vector3(0, 1, 0), -angleSpeed * Time.deltaTime);
//transform.Translate(new Vector3(-speed, 0, 0) * Time.deltaTime);
}

if (Input.GetKey(KeyCode.D))
{
transform.Rotate(new Vector3(0, 1, 0), angleSpeed * Time.deltaTime);
// transform.Translate(new Vector3(speed, 0, 0) * Time.deltaTime);
}
}
}

有如下方法:

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
[ExcludeFromDocs]
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
[ExcludeFromDocs]
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//Input.GetAxis 获取轴的数据   轴 水平轴 垂直轴
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");

Vector3.forward = new Vector3(0,0,1);

TransLate参数为1个时的含义
//
// 摘要:
// Moves the transform in the direction(方向) and distance(距离) of translation.
//
// 参数:
// translation:
//
// relativeTo:

// 这个距离和方向说的很对,因为向量就是表示的速度和方向。

[ExcludeFromDocs]
public void Translate(Vector3 translation);

// 下面两个等价
// 距离 = 速度 * 时间
// new Vector3(0, 0, v * speed)是速度
// Time.deltaTimes是时间
// 向量的乘法
this.transform.Translate(new Vector3(0, 0, v * speed) * Time.deltaTime);
this.transform.Translate(Vector3.forward * v * speed * Time.deltaTime);


// 两个参数的Rotate说明如下
//
// 摘要:
// Rotates the transform around axis by angle degrees.
//
// 参数:
// axis:
//
// angle:
//
// relativeTo:
[ExcludeFromDocs]
public void Rotate(Vector3 axis, float angle);

//第一个参数明明白白说了是轴

this.transform.Rotate(new Vector3(0, 1, 0), h * angleSpeed * Time.deltaTime);

// 一个参数的说明如下
//
// 摘要:
// 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:
[ExcludeFromDocs]
public void Rotate(Vector3 eulerAngles);

唯一的参数指的是欧拉角,此时向量可以进行加减乘除。
所以也可以这样旋转:
Vector3.up = new Vector3(0,1,0);
this.transform.Rotate(Vector3.up * h * angleSpeed * Time.deltaTime);

Vactor3表示axis的时候,不需要乘以任何东西。就是一个轴,表示方向。Vector3(0,1,0)和Vector3(0,100,0)是一样的。

上面的内容其实很重要,因为Vector3向量的各种概念很重要。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//Input GetKey  GetKeyDown  GetKeyUp
if (Input.GetKeyDown("f")) //按下了指定键的那一刻返回一次true值
{
Debug.Log("按下了f键");
}
//参数 字符串必须要小写的
if (Input.GetKey("f")) //按住指定键的时候,每一帧都会返回一次true值
{
Debug.Log("按住了f键");
}
if (Input.GetKeyUp("f")) //松开指定键的那一刻返回一次true值
{
Debug.Log("松开了f键");
}

if (Input.GetMouseButtonDown(0))
{
Debug.Log("按下了左键");
}
//参数 0代表左键,1代表右键
if (Input.GetMouseButton(0))
{
Debug.Log("按住了左键");
}
if (Input.GetMouseButtonUp(0))
{
Debug.Log("松开了左键");
}

天空盒

两种方式
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using UnityEngine;
using System.Collections;
//场景里面所有的物体都是GameObject
public class SearchObj : MonoBehaviour
{
//1、查找物体的第一种方式(通过定义一个公有的GameObject变量,在属性面板里面把对应的物体拖拽上去),尽量少用
//public GameObject cube;
//2、通过代码来查找(一般都是用这种方式)
//-1、GameObject.Find("所要查找的物体的名称");或者GameObject.Find("查找路径")
//只能查找显示中的物体,查找不到隐藏的物体
//属于全局查找,注意不要去频繁调用,会耗费很大的性能
//注意,如果场景里面有多个同名物体,那么就只能查找到一个
//-2、通过标签来查找( 只能查找显示中的物体,查找不到隐藏的物体)
//GameObject.FindGameObjectWithTag("所要查找的物体标签的名称");或者 GameObject.FindWithTag("所要查找的物体标签的名称");

//属于全局查找,注意不要去频繁调用,会耗费很大的性能
//注意,如果场景里面有多个同标签名物体,那么就只能查找到一个

//如何查找多个物体???
//GameObject.FindGameObjectsWithTag("所要查找物体的标签");,返回的是一个数组

//3.如何查找隐藏的物体???
//transform.FindChild("所要查找的物体的名称")或者transform.Find("所要查找的物体的名称"),支持路径查找transform.Find("查找路径")
//既能查找显示的物体,也能查找隐藏的物体
//注意,只能查找子物体(缺点)

private GameObject cube;
private GameObject shpere;
void Start ()
{

//cube = GameObject.Find("Cube");
//cube.GetComponent<Renderer>().material.color = Color.red;

////shpere = GameObject.Find("Sphere");
//shpere = GameObject.Find("Cube/Sphere");
//shpere.GetComponent<Renderer>().material.color = Color.red;

//GameObject myCube = GameObject.FindWithTag("MyObj");
////GameObject myCube= GameObject.FindGameObjectWithTag("MyObj");
//myCube.GetComponent<Renderer>().material.color = Color.yellow;

//查找多个物体(通过标签)
//GameObject[] arr= GameObject.FindGameObjectsWithTag("MyTag");
//for (int i = 0; i < arr.Length; i++)
//{
// arr[i].GetComponent<Renderer>().material.color = Color.green;
//}

//查找隐藏的物体
GameObject myCapsule = this.transform.FindChild("MyCapsule").gameObject;
myCapsule.GetComponent<Renderer>().material.color = Color.black;
}
}

拖曳的缺点:
1、后期不好维护
2、版本控制会丢失。

Find的缺点
1、不显示的物体找不到
2、全局查找,耗费性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private GameObject cube;
private GameObject[] spheres;
private GameObject cylinder;
// Use this for initialization
void Start () {
cube = GameObject.Find("Cube");
cube.GetComponent<Renderer>().material.color = Color.red;

//sphere = GameObject.FindGameObjectWithTag("ball1");

spheres = GameObject.FindGameObjectsWithTag("ball1");

for (int i = 0; i < spheres.Length; i++)
{
spheres[i].GetComponent<Renderer>().material.color = Color.blue;
}

cylinder = this.transform.FindChild("Cylinder").gameObject;
cylinder.GetComponent<Renderer>().material.color = Color.yellow;
}

下面这句很重要:

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(true);

Resources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEngine;
using System.Collections;
//注意
//并不是所有的资源都能放到Resources文件夹下面
//因为打包项目的时候,会把Resources文件夹里面所有的资源全部打包进去
//如果不需要动态加载的资源不要放在Resources文件夹下面,会增大包体的大小
//放在Resources文件夹下面的资源,打包的时候都会被压缩并且加密
public class TestLoad : MonoBehaviour
{
//private GameObject myCube;
private GameObject wall;
void Start () {
//// myCube = Resources.Load("Cube") as GameObject;
// myCube = Resources.Load<GameObject>("Cube");
// Instantiate(myCube);
wall = Resources.Load<GameObject>("Wall");
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
GameObject go=Instantiate(wall);
go.transform.position = new Vector3(i,j+1,0);
}
}
}
}

打开项目

能看见Assets文件夹的路径打开,尽量不要点击场景打开。

脚本挂载

真正的开发中,除了游戏初始化脚本,脚本基本都是动态挂载的,不是最开始手动挂上去的。

添加组件:AddComponent<脚本名> 只要不是放在Editor下面就OK。
获取组件:GetComponent<脚本名>

没有直接的移除组件的API,要先获取再Destroy()。

碰撞体

Is Kinematic 取消物理效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
```cs
using UnityEngine;
using System.Collections;
//碰撞的条件(需要同时满足一下条件)
//1、两个物体都必须有碰撞体
//2、两个物体都有刚体,或者至少有一个物体有刚体
//一般情况下,都是有刚体的去碰撞没刚体的,或者两个都有刚体的相互碰撞

//碰撞检测
//触发检测
public class TestCollider : MonoBehaviour
{
//碰撞检测
//检测碰撞进入
void OnCollisionEnter(Collision other)//other就是碰撞到的物体
{
Debug.Log("碰撞进入"+other.gameObject.name);
}
//检测碰撞停留

// 只有至少一个物体在移动时才会一直触发
// 如果两个物体都静止则不会触发
void OnCollisionStay(Collision other)
{
Debug.Log("碰撞停留" + other.gameObject.name);
}
//检测碰撞退出
void OnCollisionExit(Collision other)
{
Debug.Log("碰撞退出" + other.gameObject.name);
}
//触发检测(记得把该物体上面的碰撞体的IsTrigger勾选)
//检测触发进入
void OnTriggerEnter(Collider other)
{
Debug.Log("触发进入"+other.name);
}
//检测触发停留
void OnTriggerStay(Collider other)
{
Debug.Log("触发停留" + other.name);
}
//检测触发退出
void OnTriggerExit(Collider other)
{
Debug.Log("触发退出" + other.name);
}
}

坦克大战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using UnityEngine;
using System.Collections;

public class BulletCtrl : MonoBehaviour {

private Rigidbody rd;
private float speed = 10;
private GameObject effect;
void Start()
{

rd = this.GetComponent<Rigidbody>();
Destroy(this.gameObject,3f);
}
//一般对刚体进行操作(进行物理运算)都要放在FixedUpdate里面,固定时间调用
void FixedUpdate()
{
//Vector3.forward是世界坐标的正方向(方向是不会改变的)
// rd.AddForce(Vector3.forward);

//子弹自身的正方向(跟随子弹变化的)
rd.AddForce(this.transform.forward * speed);
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag=="Enemy")
{
//effect = Resources.Load<GameObject>("Effect");
////播放特效
//Instantiate(effect, other.transform.position,Quaternion.identity);
PlayEffectMgr.PlayEffect("Effect", other.transform.position);
//销毁物体
Destroy(other.gameObject);

}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using UnityEngine;
using System.Collections;

public class TankCtrl : MonoBehaviour
{
//移动速度
private float moveSpeed = 30;
//旋转速度
private float rotateSpeed = 200;
//子弹的预制体
private GameObject bulletPrefab;
//子弹生成的位置
public Transform bulletStartPs;
void Start()
{
bulletPrefab = Resources.Load<GameObject>("Bullet");
bulletStartPs = this.transform.FindChild("BulletStartPs");
}
void Update()
{
Move();
Shoot();
}
//射击
private void Shoot()
{
if (Input.GetMouseButtonDown(0))
{
//Debug.Log("发射子弹");
GameObject bullet = Instantiate(bulletPrefab, bulletStartPs.position, bulletStartPs.rotation) as GameObject;
if (bullet.GetComponent<BulletCtrl>()==null)
{
bullet.AddComponent<BulletCtrl>();
}

}
}
//控制移动和转向
private void Move()
{
//水平方向的输入(-1~1)
float inputHorizontal = Input.GetAxis("Horizontal");
//垂直方向的输入(-1~1)
float inputVertical = Input.GetAxis("Vertical");
//控制坦克前后移动(Vector3.forward指坦克的正方向,Z轴方法)
this.transform.Translate(Vector3.forward * inputVertical * moveSpeed * Time.deltaTime);
//控制坦克转向
this.transform.Rotate(Vector3.up * inputHorizontal * rotateSpeed * Time.deltaTime);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

//播放特效的管理类
public class PlayEffectMgr : MonoBehaviour
{
//缓存特效的字典<特效名称,特效的预制体>
private static Dictionary<string, GameObject> dicEffect = new Dictionary<string, GameObject>();
public static void PlayEffect(string effectName,Vector3 ps)
{
//判断将要播放的这个特效有没有缓存在字典里面
if (!dicEffect.ContainsKey(effectName))
{
//如果没有,就去加载,加载完以后,就缓存到字典里
GameObject effect = Resources.Load<GameObject>(effectName);
dicEffect.Add(effectName, effect);
}
Instantiate(dicEffect[effectName], ps, Quaternion.identity);
}

}

优化

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。

移动肯定是在世界坐标中移动。无论什么坐标都要转成世界坐标才能正确移动。

0%