Unity学习笔记(五)

资源的访问

资源的使用

在脚本中,也可以引用一个资源。比如

  • AudioClip ,音频文件
  • Texture,纹理贴图
  • Material ,材质

使用步骤:

  1. 准备音效文件,预览
  2. 添加脚本 AudioTest.cs。其中添加资源 public AudioClip audioSuccess;
  3. 引用音频资源
  4. 使用 API 播放音频 AudioSource.PlayOneShot( clip ); ,用于播放音效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AudioTest : MonoBehaviour
{
// 成功时提示音
public AudioClip audioSuccess;

// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
AudioSource audioSource = GetComponent<AudioSource>();
// 一次性播放
audioSource.PlayOneShot(audioSuccess);
}

}
}

image-20220503001709632

资源数组

在脚本中,也可以定义一个数组变量。

例如,一个音乐盒,存了多首歌曲。
public AudioClip[ ] songs;

创建一个音乐盒,点鼠标随机切换。挂载MusicBox.cs

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
public class MusicBox : MonoBehaviour
{
public AudioClip[] songs;

// Start is called before the first frame update
void Start()
{
if (songs == null || songs.Length == 0)
{
Debug.Log("请在检查器中指定音乐资源");
return;
}
}

// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
NextSong();
}
}

private void NextSong()
{
//随机播放
int index = Random.Range(0, songs.Length);
AudioClip clip = this.songs[index];

AudioSource audioSource = GetComponent<AudioSource>();
audioSource.clip = clip;
audioSource.Play();

Debug.Log("正在播放第" + (index + 1) + "首歌曲,曲名:" + clip.name);
}
}

注意:获取随机数API
index = Random.Range( min, max );
用于在 [min, max) 中随机抽取一个数,不包含 max

练习

三色球,制作一个变换颜色的小球

创建Sphere,挂载ThreeColorBall.cs

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
public class ThreeColorBall : MonoBehaviour
{
public Material[] colors;

// 颜色索引
int m_index = 0;

// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
// 改变颜色
ChangeColor();
}
}

private void ChangeColor()
{
// 下一个颜色索引
m_index += 1;
// 如果下一个颜色索引超过最大范围,则归零
if (m_index >= this.colors.Length)
{
m_index = 0;
}

// 当前颜色赋给材质
Material selected = this.colors[m_index];
// 材质赋给MeshRenderer组件
MeshRenderer mr = GetComponent<MeshRenderer>();
mr.material = selected;
}
}

也可以使用其他办法,比如直接修改 Material 的 Albedo 颜色

定时调用

定时调用

定时调用 Invoke* ,即一般所谓的‘定时器’
继承自 MonoBehaviour :

  • Invoke (func , delay) ,只调用一次
  • InvokeRepeating (func, delay, interval) ,循环调用
  • IsInvoking (func) ,是否正在调度中
  • CancelInvoke (func) ,取消调用、从调度队列中移除

传入的参数中,func ,函数名是一个字符串 ( 反射机制 )

创建一个小球,挂载SimpleLogic.cs

1
2
3
4
5
6
7
8
9
void Start()
{
Debug.Log("执行Start()............" + Time.time);
this.Invoke("DoSomething", 1);
}
private void DoSomething()
{
Debug.Log("DoSomething()执行了.........." + Time.time);
}

运行游戏,可以看到DoSomething()方法启动1s后执行了
image-20220504231821563

修改代码,使用InvokeRepeating方法

1
2
3
4
5
6
void Start()
{
Debug.Log("执行Start()............" + Time.time);
//this.Invoke("DoSomething", 1);
this.InvokeRepeating("DoSomething", 1, 2);
}

运行游戏,可以看到启动1s后第一次执行,后面每隔2s执行一次
image-20220504232011766

实现:弹跳小球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SimpleLogic : MonoBehaviour
{
public float speed = 1.5f;

// Start is called before the first frame update
void Start()
{
this.InvokeRepeating("DoSomething", 1, 2);
}

// Update is called once per frame
void Update()
{
this.transform.Translate(0, speed * Time.deltaTime, 0, Space.Self);
}

private void DoSomething()
{
// 向反方向移动
this.speed = -speed;
}
}

定时与线程

InvokeRepeating 定时调用,并没有创建新的线程
Unity引擎的核心是单线程的,不必考虑线程、并发、互斥
通过控制台打印可以发现,Start() 、Update()、以及定时调用的方法,是在同一个线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Start()
{
Debug.Log("Start, 线程id=" + Thread.CurrentThread.ManagedThreadId);
// 循环定时调用
this.InvokeRepeating("DoSomething", 1, 2);
}

// Update is called once per frame
void Update()
{
Debug.Log("update, 线程id=" + Thread.CurrentThread.ManagedThreadId);
this.transform.Translate(0, speed * Time.deltaTime, 0, Space.Self);
}

private void DoSomething()
{
Debug.Log("DoSomething, 线程id=" + Thread.CurrentThread.ManagedThreadId);
this.speed = -speed;
}

可以发现线程id相同
image-20220504233812540
image-20220504233850138

注意点

  1. 重复调用。每次 InvokeRepeating ,都会添加一个新的调度(多调度一次)
  2. 调用相关API
    IsInvoking ( func ) ,判断 func 是否在 Invoke 队列
    CancelInvoke ( func ) ,取消 func 的 Invoke 调用
    CancelInvoke ( ),取消当前脚本的所有 Invoke 调用

注意:在 Invoke 时,一般要避免重复调用。实现方法

1
2
3
4
if (!IsInvoking(func))
{
InvokeRepeating(func, delay, interval);
}

练习1

实现一个红绿灯,其中,红灯,4秒;绿灯,4秒;黄灯,1秒

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
public class LightLogic : MonoBehaviour
{
[Tooltip("红、绿、黄按顺序指定")]
public Material[] colors;

// 信号灯索引,红灯开始
int m_index = 0;

// Start is called before the first frame update
void Start()
{
ChangeColor();
}

// Update is called once per frame
void Update()
{

}

private void ChangeColor()
{
// 当前材质
Material color = this.colors[m_index];
MeshRenderer renderer = GetComponent<MeshRenderer>();
renderer.material = color;
Debug.Log("Change=>" + m_index + ", time=" + Time.time);

if (m_index == 0)
{
// 红=>绿,间隔4
Invoke("ChangeColor", 4);
} else if (m_index == 1)
{
// 绿=>黄,间隔4
Invoke("ChangeColor", 4);
} else if (m_index == 2)
{
// 黄=>红,间隔1
Invoke("ChangeColor", 1);
}

m_index++;
if(m_index >= 3)
{
m_index = 0;
}
}
}

练习2

实现速度渐变的效果

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
public class FanLogic : MonoBehaviour
{
// 最大转速
public float maxRotateSpeed = 720;

// 当前转速
float m_speed = 0;

// true加速,false减速
bool m_speedUp = false;

// Start is called before the first frame update
void Start()
{
InvokeRepeating("AdjustSpeed", 0.1f, 0.1f);
}

// Update is called once per frame
void Update()
{
// 鼠标点击切换加减速
if (Input.GetMouseButtonDown(0))
{
m_speedUp = !m_speedUp;
}
// 旋转
if (m_speed > 0)
{
this.transform.Rotate(0, m_speed * Time.deltaTime, 0, Space.Self);
}

}

// 转速调整
private void AdjustSpeed()
{
if (m_speedUp)
{
if(m_speed < maxRotateSpeed)
{
m_speed += 10;
}
} else
{
m_speed -= 10;
if(m_speed < 0)
{
m_speed = 0;
}
}
}
}

向量

向量概述

向量 Vector3 ,三维向量 ( x , y , z )
向量,即有方向的量

  • 方向
  • 长度
    image-20220505105422590

向量的长度 : L = √(𝑥^2+𝑦^2+𝑧^2)
求向量长度的API: 使用 Vector3 类中的 magnitude 属性

1
2
Vector3 v = new Vector3(3, 0, 4);
float len = v.magnitude;

单位向量:长度为 1 的向量。
例如,
Vector3 v1 = new Vector3(1, 0, 0);
Vector3 v2 = new Vector3(0.6f, 0.8f, 0);

标准化 Normalize :缩放一个向量,使其长度为 1
例如,
(3 , 4, 0 ) → ( 0.6, 0.8, 0 )
(2 , 2, 0 ) → ( 0.707, 0.707 , 0 )
(1, 0, 0 ) → ( 1, 0 , 0 )
标准化API : 使用 Vector3 中的 normalized 属性

1
2
Vector3 v1 = new Vector3(2, 2, 0);
Vector3 v2 = v1.normalized;

几个常量

image-20220505105544618
Vector3.zero 即 ( 0, 0, 0 )
Vector3.up 即 ( 0, 1, 0 )
Vector3.right 即 ( 1, 0, 0 )
Vector3.forward 即 ( 0, 0, 1 )

向量的运算

  • 向量加/减法,即 x y z 三个分量分别相加/减。例如
1
2
3
4
5
6
Vector3 a = new Vector3 (1, 3, 0);
Vector3 b = new Vector3 (4, 1, 3);
// 加法
Vector3 c = a + b;
// 减法
Vector3 d = a - b;
  • 向量乘法,分为 3 种:
  1. 标量乘法 b = a * 2
  2. 点积 c = Vector3.Dot(a, b)
  3. 差积 c = Vector3.Cross(a, b)

其中,一般用标量乘法 ,即对每一个分量相乘

Vector3 是值类型,可以直接赋值。例如

1
2
Vector3 a = new Vector(1, 1, 0) ;
Vector3 b = a;

不能设为 null ,

1
2
3
4
// 正确
public Vector3 speed;
// 错误
public Vector3 speed = null;

向量测距

向量测距,用于求两物体间的距离。例如,

1
2
3
4
Vector3 p1 = this.transform.position;  // 自己位置
Vector3 p2 = target.transform.position; // 目标位置
Vector3 direction = p2 - p1; // 方向向量
float distance = direction.magnitude; // 距离

Vector3.Distance(p2, p1) ,也可以求距离

1
2
3
4
5
Vector3 p1 = this.transform.position;
Vector3 p2 = target.transform.position;
//Vector3 p = p2 - p1;
//float distance = p.magnitude;
float distance = Vector3.Distance(p2, p1); // 和magnitude属性等效

注意:物体间的距离,确切的说是轴心点之间的距离。应检查确认物体的轴心点。

向量的使用

Vector3 可以直接作为脚本的参数

1
2
3
4
5
6
7
8
// 定义速度向量
public Vector3 speed;

void Update()
{
Vector3 delta = speed * Time.deltaTime;
this.transform.Translate(delta, Space.Self);
}

通过设置Speed的XYZ可指定移动方向
image-20220505113714031

预制体

预制体概述

预制体 Prefab ,即预先制作好的物体。使用预制体,可以提高开发效率
例子:导出 RacingCar 资源包
image-20220505145744361

  • 在 Prefabs 目录下,是预制体资源,后缀是 *.prefab
  • 用预制体来构造物体

注意:

  • 使用预制体 Prefab ,可以快速创建物体
  • 在 Prefab 资源中,包含了的所有数据

预制体的创建

预制体的创建:

  1. 先制作好一个样本节点
  2. 做好以后,直接将需要保存的节点拖到 Assets 窗口,则自动生成一个 *.prefab 资源
  3. 原始物体不再需要,可以删除

注意:

  • 在导出 prefab 资源时,应该将依赖文件一并导出。
  • prefab 只是记录了节点的信息。
  • prefab 文件中不包含材质、贴图数据,仅包含引用

预制体的实例

Prefab Instance ,由预制体创建得到的对象
特征 :

  • 在 Hierarchy 中,节点图标不同
  • 在 Hierarchy 中,右键菜单 | Prefab
  • 在 Inspector 中,上下文工具 | Prefab

注意:

  • Prefab Instance 和原 Prefab 之间存在关联
  • 右键节点,选择 Prefab | Unpack ,则解除关联,成为普通物体
  • Prefab 中,具有多级节点 / 父子关系的预制体是常见情况

预制体的编辑

*.prefab 相当于是一个模板,可以再次编辑

第1种方式:单独编辑

  • 双击 Prefab ,进入 单独编辑 模式
  • 编辑节点和组件
  • 退出,完成编辑

第2种方式:原位编辑

  • 选择 Prefab Instance
  • 在检查器中 Prefab-Open
  • Context显示三种模式:Normal:普通视图 / Gray:灰色视图 / Hidden:隐藏编辑节点以外的所有节点
  • 三种模式仅选中物体可编辑,其余物体无法编辑
  • 编辑节点
  • 退出,完成编辑

第3种方式:覆盖编辑

  • 选择 Prefab Instance
  • 直接在场景中编辑
  • 编辑完后,检查器-Prefab-Overrides,Apply/Revert ,应用/放弃编辑

注意:右键Prefab节点-Prefab-Unpack后,可解除节点与预制体的关联

0%