序言

本文记录Unity学习,仅供个人参考

游戏引擎

游戏引擎是指一些一边写好的可编辑电脑有戏系统或者一些交互式实时图像应用程序的核心组件。这些系统为游戏设计者提供各种编写游戏所需的各种工具,其目的在于让游戏设计者能容易和快速的做出游戏程序而不用从零开始。

工程文件夹

Assets:工程资源文件夹(美术资源、脚本等)

Library:库文件夹(unity自动生成管理)

Logs:日志文件夹,记录特殊信息(unity自动生成管理)

obj:编译产生中间文件(unity自动生成管理)

Packages:包配置信息(unity自动生成管理)

ProjectSettings:工程设置信息(unity自动生成管理)

引擎窗口

Scene和Hierarchy

场景窗口和层级窗口是息息相关的,层级窗口中看到的内容就是场景窗口中的显示对象

Hierarchy层级窗口

我们可以在这个窗口中创建或者拖入各种游戏对象,比如:模型、光源、图片、UI等内容,层级窗口中显示的就是一个场景中的所有对象。窗口中右键或者点击左上角+号键可以创建对象和操作对象

Scene场景窗口

我们可以在Scene窗口中查看并设置所有游戏对象

Game游戏窗口

游戏画面窗口,玩家能看到的画面内容。

Project工程窗口

工程资源窗口,所有的工程资源都会在该窗口中显示,显示的内容为Assets文件夹中的内容

默认文件夹:Scenes。里面有一个默认空场景

Packages:官方拓展包

Inspector检查窗口

查看场景中游戏对象关联的c#脚本信息

Console控制台窗口

由于查看调试信息的窗口,报错、警告、测试打印都可以显示在其中

默认未开启,可以在Window->General中开启,或者使用快捷键Ctrl+Shift+C

清空控制台、相同内容折叠显示、运行时清空、构建时清空、报错时暂停运行、是否显示错误信息、是否显示警告信息、是否显示打印信息

资源类型

图片格式:jpg、png、tga

模型格式:fbx、max、maya

音效:wav、mp3、ogg

文本:txt、json、bytes

视频:mp4

反射机制和游戏场景

unity反射机制

unity开发的本质就是在unity引擎的基础上利用反射和引擎提供的各种功能进行的拓展开发。

场景中对象的本质:都是依附于GameObject类的对象

Transform:GameObject在场景中必须要有表示自己所在位置的信息,tranform就是一个必不可少的剧本,相当于就是用一个transform对象和GameObject对象进行关联,用于设置和得到对象在世界中的位置角度缩放等信息

反射机制的体现:除了Trasform外,我们可以为GameObject关联各种脚本(c#脚本),让他按照我们代码逻辑中的命令来处理事情,而这个过程,就是利用反射new一个新的剧本对象和GameObject对象进行关联,让其执行命令

unity已经帮助我们实现了对象查找和关联,1.修改Inspector面板中Transform的内容,利用反射:已知对象,类名,变量名,通过反射为该对象设置变量值。2.新建一个脚本后,添加给一个指定的GameObject对象,利用反射:已知类名,可以获取所有公共成员,所以可以在Inspector面板上创建个公共字段信息

游戏场景的本质

游戏场景文件

后缀为.unity,它的本质就是一个配置文件,Unity有一套自己识别处理他的机制,但是本质就是把场景对象相关信息读取出来,通过反射来创建各个对象关联各个脚本对象

预设体和资源包导入导出

预设体:预先设置好的物体,用来保存单个物体的信息,拖入场景中即可成为预设体,文件后缀名是.prefab,

资源包后缀:.unitypackage

脚本基础

脚本规则

创建规则:1.不在vs中创建脚本。2.可以放在Assets文件夹下的任何位置。3.雷鸣和文件名必须一致,不然不能挂载(反射机制创建对象会通过文件名去找Type,2022之后已经不用了)。4.不是用中文名。5.没有特殊需求不用管命名空间。6.创建的脚本默认继承MonoBehavior。

MonoBehaviour基类

1.创建的脚本默认都继承MonoBehaviour继承了他才能够挂载载GameObject上。2.继承了MonoBehaviour的脚本不能new 只能挂。3.继承了MonoBehaviour的脚本不要去写构造函数,因为我们不回去new它,没有任何意义。4.继承了MonoBehaviour的脚本可以在一个对象上挂多个(如果没有加DisallowMutipleComponen特性)5.继承MonoBehaviour的类也可以再次被继承,遵循面向对象继承多态的规则

不继承MonoBehavior的类

1.不继承Mono的类,不能挂载在GameObject上。2.不继承Mono的类想怎么写怎么写如果要使用需要自己new。3.不继承Mono的类一般是单例模式的类(用于管理模块)或者数据结构类(用于存储数据)。4.不继承Mono的类 不用保留默认出现的几个函数。

执行的先后顺序

默认脚本内容

Editor\Data\Resources\ScriptTemplates 可以修改默认脚本内容

生命周期函数

帧的概念

游戏的本质就是一个死循环,每一次循环处理游戏逻辑就会更新一次画面之所以能看到画面在动是因为切换画面的速度到达一定时人眼就认为画面是流畅的,一帧就是一次循环。

fps:每秒钟的帧数。

人眼舒适放松时可视帧数是每秒24祯。游戏卡顿的原因:跑1祯游戏逻辑的运算量过大,或者cpu步豪,不能再一祯时间内处理完所有游戏逻辑。

unity的底层已经帮助我们做好了死循环。我们需要学习unity的生命周期函数,利用它做好的规则来执行我们的游戏逻辑就行了

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
71
72
73
74
75
76
77
78
79
80
81
82
83
//生命周期函数
//所有继承Mono的脚本,最终都会挂载到Game Object游戏对象上
//生命周期函数 就是该脚本对象衣服的GameObject对象从出生到消亡整个生命周期中
//会通过反射自动调用的一些特殊函数

//unity帮助我们记录了一个GameObject对象衣服了那些脚本
//会自动地得到这些对象,通过反射去执行一些固定名字的函数

//生命周期函数的访问修饰符一般是private和protected

//Awake函数
//出生时调用
//类似构造函数的存在,可以在一个类对象创建时进行一些初始化操作
//一个对象只会调用一次
//只有对象被创建时(自己这个类对象) 才会调用该生命周期函数
private void Awake()
{
//在unity中打印信息的两种方式
//没有继承MonoBehavior类的时候
Debug.Log("123");
Debug.LogError("出错了");
Debug.LogWarning("警告");
//继承了Mono,有一个现成的方法
print("1234567");
}

//OnEnable函数
//依附的GameObject对象每次激活时调用
//对于我们来说,想要但一个对象被激活时,进行一些逻辑处理,就可以写在这个函数中
private void OnEnable()
{
print("OnEnable");
}

//Start函数
//主要作用还是用于初始化信息的,相对于Awake晚一点
//因为时在对象进行第一次祯更新之前才会执行的
void Start()
{
print("Start");
}

//FixedUpdate函数
//每次祯更新都会执行一次
//用于进行物理更新
//每一帧都执行的,这里的祯和游戏祯有点不同
void FixedUpdate()
{

}

//Update函数
//主要用于处理游戏核心逻辑更新的函数
void Update()
{
print("Update");
}

//LateUpdate函数
//一般这个更新是由来处理摄像机位置更新相关内容的
//Update和LateUpdate之间Unity进行了一些处理,处理我们动画相关的更新
void LateUpdate()
{
print("LateUpdate");
}

//OnDisable函数
//失活时调用
//如果我们希望一个对象失活时做一些处理,就可以在这写一些逻辑
void OnDisable()
{
print("OnDisable");
}

//OnDestroy函数
//对象销毁时调用
void OnDestroy()
{
print("OnDestroy");
}

//生命周期函数支持继承多态

Inspector窗口可编辑的变量

Inspector中显示的可编辑内容就是脚本的成员变量。私有和保护无法显示编辑

让私有和保护的也可以被显示,加上强制序列化字段特性[SerializeField],所谓序列化就是把一个对象保存到一个文件或数据库中去。公共的可以显示编辑。公共的也不让其编辑,加上[HideInspector]特性可以不显示。大部分类型都能显示编辑(如数组、枚举等等)但是字典和自定义类型不能被unity显示。

自定义类型如何被访问:加入特性[System.Setializable]就可以访问,但是字典是无论如何不能被访问的。

辅助特性:

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
//分组说明特性 Header
//为成员分组
//Header特性
//[Header("分组说明")]

//悬停注释Tooltip
//为变量添加说明
//[Tooltip("说明内容")]

//间隔特性 Space()
//让两个字段之间出现间隔
//[Space()]

//修饰数值的滑条范围Range
//[Range(最大值,最小值)]

//多行显示字符串 默认不学写参数显示3行
//写参数就是对应行
//[Multiline(4)]

//滚动条显示字符串//默认不写参数就是超过3行显示滚动条
//[TestArea(3,4)]//最少显示3行最多显示4行,超过4行就显示滚动条

//为变量添加快捷方法 ContextMenuItem
//参数1 显示按钮名
//参数2 方法名 不能有参数
//[ContextMenuItem("显示按钮名","方法名")]

//为方法添加特性能够在Inspector中执行
//[ContextMenu("测试函数")]

//注意
//Inspector窗口中的变量关联的就是对象的成员变量,运行时改变他们就是在改变成员变量
//拖拽到GameObject对象后 在改变脚本中变量默认值 界面上不会改变
//运行中修改的信息不会保存

Mono中的重要内容

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
//重要成员
//获取依附的GameObject
print(this.gameObject.name);
//获取依附的GameObject的位置信息
print(this.transform.position);//位置
print(this.transform.eulerAngles);//角度
print(this.transform.lossyScale);//缩放大小
//这种写法和上面的是一样的效果
this.gameObject.transform;
//获取脚本是否激活
this.enabled=false;
public Lesson3 otherLesson3;
//获取别的脚本对象 依附他的gameobject和transform
print(otherLesson3.gameObject.name);
print(otherLesson.transform.position);
//重要方法
//得到自己挂载的单个脚本
//根据脚本名获取
//获取脚本的方法 如果获取失败 就是没有对应的脚本 会默认返回空
脚本名 t=this.GetComponent("脚本名")as 脚本名;
print(t);
//根据Type获取
t=this.GetComponent(typeof(Lesson3_Test))as Lesson3_Test;
//根据泛型获取 建议使用泛型获取 因为不用二次转换
t=this.GetComponent<Lesson_Test>();//默认返回的就是想要的类型,不是object
//只要你能的都场景中别的对象或者对象依附的脚本,就能得到它的所有信息

//得到自己挂载的多个脚本
类名[] array=this.GetComponents<类名>();
print(array.Length);
List<类名>list=new list<类名>();
this.GetComponents<类名>(list);
print(list);
//得到子对象挂载的脚本(它默认也会找自己身上是否挂载该脚本)
this.GetComponentInChildren<类名>();//括号里填true失活也去找
print(t);

Test2[] t2=this.GetComponentsInChildren<类名>(true);//返回值是数组
print(t2.Length);//数组形式

List<Test2>list2=new List<Test2>();
this.GetComponentInChildren<Test2>(true,list2);//用List存储
//得到父对象挂载的脚本
//和子对象用法一样
this.GetComponentInParent<Test2>();//不需要填参数,因为父对象失活,子对象直接失活,这段代码不会运行,不用关心父对象是否失活,如果和本身和父脚本一样,会默认返回自己身上的脚本。

t2=GetComponentsInParents<Test2>();
print(t2.Length);
//尝试获取脚本
Test t3;
//提供了一个更加安全的获取脚本的方法
if(this.TryGetComponent<Test2>(out t3);)
{
//逻辑处理
}

最小单位GameObject

GameObject中的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//名字
print(this.gameObject.name);
this.gameObject.name="田鼠浩二";
print(this.gameObject.name);
//是否激活
print(this.gameObjcet.activeSelf);
//是否是静态
print(this.gameObject.isStatic);
//层级
print(this.gameObject.layer);
//标签
print(this.gameObject.tag);

//transform
//this.transform 通过Mono去得到的依附对象的GameObjcet的位置信息
//他们得到的信息都是一样的,都是依附的GameObject的位置信息
print(this.gameObject.transform.position);

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//准备用来克隆的对象
//可以是场景上的对象
//可以是一个预设体对象
public GameObject obj;



//创建自带的几何体
//只要得到了一个GameObject对象我就可以得到她身上挂载的任何脚本信息
GameObject obj=GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.name="田鼠浩二";

//查找对象
//查找单个对象
//通过对象名查找
GameObject obj2=GameObject.Find("田鼠浩二");
if(obj2!=null)
{
print(obj2.name);
}
//通过tag来查找对象
GameObject obj3=GameObject.FindWithTag("Player");
print(obj3.name);
GameObject,obj4=GameObject=GameObject.FindObjectWithTag("Player");
//只能找到激活的对象
//如果当前场景上出现多个同名满足条件的对象,我们无法确定找到的是谁
//得到一个单个对象,有两种方式
//一种是通过public从外部面板拖进来,进行关联
//一种是通过API找

//查找多个对象
//找多个对象的API只能通过Tag去找多个,通过名字没有找多个的方法
//只能找到激活对象,无法找到失活对象
GameObject []objs=GameObject.FindGameObjectsWithTag("Player");
print(objs.Length);

//用的比较少 是GameObject父类Object提供的方法
//unity里面的Object指的不是万物之父
//unity里的Object 命名空间在UnityEngine中的 Object类也是继承万物之父的一个自定义类
//c#中的Object命名空间是System

//可以找到场景中挂载的某一个脚本对象
Test2 t=GameObject.FindObjectOfType<Test2>();
print(t.gameObject.name);

//实例化对象(克隆对象)的方法
//实例化对象的作用是根据一个GameObject创建出一个和它一摸一样的对象
GameObject obj5=GameObject.Instantiate(obj);
//这个是unity里的object提供的

//删除对象的方法
GameObject.Destroy(obj5,5);//延迟5秒删除
GameObject.Destroy(this);//还可以删除脚本

//删除有两种作用
//一是删除指定的游戏对象
//二是删除一个指定的脚本对象
//一般情况下 它在下一帧时把这个对象移除并从内存中清除 不会马上移除 只是给这个对象加了一个移除标识

//如果没有特殊需求 一定要马上移除一个对象的话 建议使用上面的Destroy方法 因为是异步的 降低卡顿的几率
//下面这个方法 是立即把对象 从内存中移除
GameObject.DestroyImmediate(myobj);

//如果是继承MonoBehavior的类不用写GameObject

//切换场景不移除
//默认情况 在切换场景时 场景中对象都会被自动删除掉
//如果你希望某个对象 过场景不被移除
//下面的代码 传不想被移除的对象
GameObject.DontDestroyOnload(this.gameObject);

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
//创建空物体
//直接new就可以了
GameObject obj6=new GameObject();
GameObject obj7=new GameObject("田鼠浩二创建的空物体");
GameObject obj8=new GameObject("顺便加脚本的空物体",typeof(Test),typeof(脚本名));//想加几个加几个

//为某对象添加脚本
//继承Mono的脚本 是不能够去new的
//如果想要动态的添加继承Mono的脚本 在某一个对象上
//直接使用GameObject提供的方法即可
Test tes1=obj6.AddComponent<Test>();//直接用泛型
//通过返回值 可以得到加入的脚本信息
//来进行一些逻辑处理

//得到脚本的成员方法 和继承Mono的类得到脚本的方法一摸一样

//标签比较
if(this.gameObject.CompareTag("Player"))
{
print("标签是Player");
}

//设置激活 失活
obj6.SetActive(false);

//次要的成员方法 了解即可 不建议使用
//强调
//下面的方法不建议使用
//通过广播或发送消息的形式 让自己或别人执行某些行为方法

//通知自己 执行什么行为
//命令自己 去执行这个TestFun的函数 会在自己身上挂载的所有脚本去找这个名字的函数
//所有同名函数都会执行
this.gameObject.SendMessage("TestFun");

void TestFun()
{
print("TestFun");
}

//广播行为 让自己和自己的子对象去执行
//this.BroadcastMessage("函数名");

//向父对象和自己发送消息 并执行
//this.gameObject.SendMessageUpwards("函数名");

事件相关Time(一般写在Update函数中)

Time相关内容主要用于游戏中位移 计时 时间暂停等

时间缩放比例

1
2
3
4
5
6
//时间停止
Time.timeScale=0;
//恢复正常
Time.timeScale=1;
//2倍速
Time.timeScale=2;

帧间隔时间

1
2
3
4
5
6
7
8
9
10
//帧间隔时间:最近的一帧用了多长时间(秒)
//主要用来计算位移
//路程=时间*速度
//根据需求选择参与计算的间隔时间
//如果希望游戏暂停时帧不动的那就使用deltatime
//如果希望不受游戏影响就用UnscaleDeltaTime
//受scale影响
print(Time.deltaTime);
//不受scale影响
print(Time.UnscaleDeltaTime);

游戏开始到现在的时间

1
2
3
4
5
//主要用来计时
//受scale影响
print("游戏开始到现在的时间"+Time.time);
//不受scale影响
print(Time.UnScaleTime);

物理帧间隔时间 FixedUpdate

1
2
3
4
//受scale影响
print(Time.fixedDeltaTime);
//不受scale影响
print(Time.fixedUnScaleDeltaTime);

帧数

就是从开始到现在游戏跑了多少帧,也就是主循环执行了多少次

1
print(Time.frameCount);

Transform

游戏对象(GameObject)位移、旋转、缩放、父子关系、坐标转换等相关操作都由它处理,它是unity提供的极其重要的类

位置与位移

Vector3

Vector3主要是用来表示三位坐标系中的一个点或者一个向量,是unity中提供的结构体

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
//申明
Vector3 v=new Vector3();
v.x=10;
v.y=10;
v.z=10;
//直传x和y,z默认为0
Vector3 v2=new Vector3(10,10);
//一步到位
Vector3 v3=new Vector3(10,10,10);

//Vector3的基本计算
//就是将各个坐标做对应的运算
//运算符重载

//常用
//常用的一些向量
print(Vector3.zero);//000
print(Vector3.right);//100
print(Vector3.left);//-100
print(Vector3.forward);//001
print(Vector3.back);//00-1
print(Vector3.Up);//010
print(Vector3.down);//0-10

//常用的一个方法
//计算两个点之间距离的方法
Vector3.Distance(v1,v2);//返回的是两点距离

位置

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
//相对世界坐标系
//Inspector面板显示的是相对于父对象的位置(有父对象的情况下,没有就是世界坐标)
this.transform.position;

//相对于父对象
print(this.transform.localPosition);

//位置的赋值不能直接改变x,y,z只能整体改变
//不能单独改某一个值
//this.transfotm.x=10;
this.transfotm.position=new Vector3(10,10,10);
this.transform.localPosition=Vector3.up*10;

//如果只想改一个值x y和z和原坐标一致
//直接赋值
//把那两个坐标点出来写进去

//先去出来在赋值
//一样的意思
//把坐标取出来 单独改一个
//再赋值回去

//对象目前的各朝向
//旋转后和世界朝向不同了
//对象当前的面朝向
print(this.transform.forward);//是一个相对世界的单位向量
//对象的左朝向
print(this.transform.left);

位移

路程=方向速度时间

计算位移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//方式一 自己计算
//想要变化的就是position
//用当前的位置+我要动多长的距离 得出最终所在的位置
this.transform.position=this.transform.position+this.transform.forward*1*Time.deltaTime;
//方式二 API
//参数一表示位移多少
//参数二表示相对坐标系
this.transform.Translate(Vector3.forward*1*Time.deltaTime);//向自己z轴方向动
this.transform.Translate(this.forward*1*Time.deltaTime,Space.World);//和上面一样
this.transform.Translate(Vector3.forward*1*Time.deltaTime,Space.World);//相对于世界坐标系z轴动

//相对于自己的坐标系 下的自己的面朝向向量移动(一定不会这样让物体移动)
//可以理解为先得到相对于世界的面朝向向量,再在自身参考系中用这个向量移动
this.transform.Translate(this.transform.forward*1*Time.deltaTime,Space.Self);
//相对于自己的坐标系,用相对于世界的z轴方向移动,那就是自己的面朝向喽
this.transform.Translate(Vector3.transform.forward*1*Time.deltaTime,Space.Self);
//一般使用API去移动物体

角度与旋转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//相对世界的角度
//欧拉角
//只能得到0至360度的角度
this.transform.eulerAngles//角度
//相对于父对象的角度
this.transform.localEulerAngles;
//自转
this.transform.Rotate(new Vector3(0,10,0)*Time.deltaTime);
//默认是以自身为参考系 与位移相反 位移默认以世界坐标为参考系
//后面的参数和位移一样

//公转
//第一个参数是中心点
//第二个参数是旋转轴
//第三个参数是旋转的角度
this.transform.RatateAround(Vector3.zero,new Vector(0,10,0),1*Time.deltaTime);

缩放和看向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//缩放
//相对世界坐标系
print(this.transform.lossyScale);
//相对于本地坐标系(父对象)
print(this.transform.localScale);

//注意
//同样缩放不能只改xyz,只能一起改(相对于世界坐标系的缩放大小只能得 不能改)
//所以我们一般要修改缩放大小 都是改的相对于父对象的 缩放大小 localScale
//this.transform.localScale=new Vector3(3,3,3);
//Unity 没有提供缩放的API
//之前的旋转位移都提供了API但是 缩放没有
//如果你想要让缩放发生变化 只能自己去改
this.transform.localScale+=Vector3.one*Time.deltaTime;

//看向
//让一个对象的面朝向 可以一直看向某一个点或者某一个对象
//看向一个点 相对于世界坐标系
this,transform.LookAt(Vector3.zero);
//看向一个对象
this.transform.LookAt(对象变量名);

transform父子关系

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
//获取和设置父对象
//获取父对象
print(this.transform.parent.name);
//设置父对象 断绝父子关系
this.transform.parent=null;
//设置父对象 认爸爸
this.transform.parent=otherparent.transform;

//通过API来进行父子关系的设置
this,transform.SetParent(null);//断绝父子关系
this,transform.SetParent(otherparent.transform);//认爸爸

//参数一 我的父亲
//参数二 是否保留世界坐标的位置 角度 缩放 信息
//true 会保留世界坐标下的状态 和父对象 进行计算 得到本地坐标系的信息
//flase 不会保留 会直接把世界坐标系下的为直角的缩放 直接赋值到本地坐标系下
this.transform.SetParent(GameObject.Find("Object").transform,true);
//抛妻弃子
//和自己的所有儿子断绝关系 没有父子关系了
this,tranform.DetachChildren();

//获取子对象
//报名字查找儿子
//找到儿子的transform信息
//用Find方法是能够找到失活的对象的 GameObject相关的查找是不能找到失活的对象的
//只能找到自己的儿子 不能找到自己的孙子
this.transform.Find("");
//虽然他的效率比GameObject.Find要高一点 前提是必须知道父亲是谁

//遍历儿子
//如何得到有多少个儿子
//失活的儿子也会算数量
//找不到孙子 所以孙子不会算数量
print(this.transform.childCount);
//通过索引号 得到自己对应的儿子
//如果这个编号 超出了儿子数量的范围 会直接报错的
//返回值是transform可以得到对应儿子的位置相关信息
this.transform.GetChild(0);

for(int i=0;i<this.transform.childCount;i++)
{
print(this.transform.GetChild(i).name);
}


//儿子的操作
//判断父亲是谁
this.transform.isChildOf(父对象的transform);//返回bool值
//得到自己作为儿子的编号
print(son.GetSiblingIndex());
//把自己设置为第一个儿子
son.SetAsFirstSibling();
//把自己设置为最后一个儿子
son.SetAsLastSibling();
//把自己设置为指定个儿子
son.SetSiblingIndex(编号);//越界不会报错 会直接设置成最后一个编号

transform坐标转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//世界坐标转本地坐标
//点的转换
//受到缩放影响(因为缩放会影响本地坐标的单位向量的大小)
//用伽利略变换的角度理解
//就是在这个世界的绝对位置减去参考系位置得到相对位置
this.transform.InverseTransformPoint(Vector3.forward)//括号里填世界坐标的点


//方向的转换
//不受到缩放影响
this.transform.InverseTransformDirection(Vector3.forward)
//受缩放影响
this.transform.InverseTransformVector(Vector3.forward);


//本地坐标转世界坐标
//点的转换
//受到缩放影响
this.transform.TransformPoint(Vector3.forward);
//方向的转换
//不受到缩放影响
this.transform.TransformDirection(Vector3.forward)
//受缩放影响
this.transform.TransformVector(Vector3.forward);

Input和screen相关(都是写在update中的)

input鼠标键盘输入

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//鼠标在屏幕中的位置
//屏幕坐标的原点 是在屏幕的左下角 网友是x轴正方向 往上是y轴正方向
//返回值是Vector3但是只有x和y的值,z一直是0 因为屏幕是2d的 没有z轴
Input.mousePosition;

//鼠标按下一瞬间 进入
//0左键 1右键 2中键
if(Input.GetMouseButtonDown(0))//括号里的返回的是bool值,如果按了会返回true
{
//处理逻辑
}
//鼠标抬起一瞬间进入
if(Input.GetMouseButtonUp(0))
{
//逻辑
}
//鼠标长按按下抬起都会进入
//当按住按键不放时 会一直进入 这个判断
if(Input.GetMouseButton(0))
{
//逻辑
}

//中间滚动
//返回值的 y -1往下下 0没有滚 1往上滚
//返回值是Vector2的值,鼠标中键滚动会改变其中的y值
print(Input.mouseScrollDelta);

//检测键盘输入
//键盘按下
Input.GetKeyDown(KeyCode.W)//按下w会返回true
//传入字符串的重载
//传入的字符串不能是大写的不然会报错
//只能传入小写字符串
Input.GetKeyDown("q");
//键盘抬起
Input.GetKeyUp(KeyCode.W);
//键盘长按
Input.GetKey(KeyCode.W);

//检测默认轴输入
//我们学习鼠标 键盘输入 主要是用来控制玩家 比如旋转 位移等等
//所以unity提供了更方便的方法来帮助我们控制对象的位移和旋转

//键盘AD按下时 返回-1到1的变换
//相当于 得到的这个值 就是我们的左右方向 我们可以通过它来控制 对象左右移动 或者左右旋转
Input.GetAxis("Horizontal");

//键盘SW按下时 返回-1到1的变换
//相当于 得到的这个值 就是我们的上下方向 我们可以通过它来控制 对象上下移动 或者上下旋转
Input.GetAxis("Vertical");
//鼠标横向移动时-1到1 左右
Input.GetAxis("Mouse X");
//鼠标竖向移动时-1到1 上下
Input.GetAxis("Mouse Y");

//GetAxisRaw方法 和GetAxis使用方式相同
//只不过它的返回值 只会是-1 0 1 不会有中间值

//其他
//是否有任意键或者鼠标长按
if(Input.anyKey)
{
//逻辑
print("有一个键按下");
}

//是否有任意键或鼠标按下
Input.anyKeyDown;

//这一帧的键盘输入
print(Input.inputString);

//手柄输入相关
//得到连接的手柄的所有按钮名字
string[] strs=Input.GetJoystickNames();

//某一个手柄键按下
if(Input.GetButtonDown("Jump"))
{

}
//某一个手柄键抬起
if(Input.GetButtonUp("Jump"))
{

}
//某一个手柄键长按
if(Input.GetButton("Jump"))
{

}


//以下仅作了解
//移动设备触摸相关
if(Input.touchCount>0)
{
Touch t=Input.touches[0];
}
//是否启用多点触控


screen相关

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
//静态属性
//常用
//获取当前屏幕的分辨率
Resolution r=Screen.CurrentResolution
print("当前屏幕分辨率的宽"+r.width+"高为"+r.height);

//屏幕窗口当前宽高
//得到的是当前窗口的宽高 不是设备分辨率的宽高
//一般写代码 要用窗口宽高 做计算时 就用他们
print(Screen.width);
print(Screen.height);

//屏幕休眠模式
Screen.sleepTimeout=SlepTimeout.NeverSleep;//休眠不停止
//不常用
//运行时是否全屏模式
Screen.fullScreen=true;
//窗口模式
//独占全屏 FullScreenMode.ExclusiveFullScreen
//全屏窗口 FullScreenMode.FullScreenWindow
//最大化窗口 FullScreenMode.MaximizedWindow
//窗口模式 FullScreenMode.Windowed

//移动设备屏幕转向相关
//之后找补


//静态方法
//设置分辨率 一般移动设备不使用
Screen.SetResolution(1920,1000,false);//true全屏

camera相关

Camera可编辑参数

Clear Flags 如何清除背景 1.skybox 天空盒(3d游戏) 2.Solid Color 颜色填充(2d游戏) 3.Depth only 只画该层 背景透明(多个摄像机叠加使用) 4.Don’t Clear 不移除 覆盖渲染(基本上不会用,因为不会擦除上一帧的内容)

Cullling Mask 选择性渲染部分层级 可以指定只渲染对应层级的对象

Projection 1.Persective 透视模式(一般3d游戏) 2. orthographic 正交摄像机(一般2d游戏)

Clipping Planes 裁剪平面距离

Depth 渲染顺序上的深度(层级高的后被渲染)

Target Texture 渲染纹理 1.可以把摄像机画面渲染到一张图上(用于制作小地图)2.在project右键创建Render Texture

Occlusion Culling 是否已启用剔除遮挡 (被挡住的物体不会被渲染)

其他的之后找补

camera代码相关

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
//重要静态成员
//获取摄像机
//如果用之前的知识 来获取摄像机
//获取主摄像机
//标签一定是MainCamera
Camera.main
//场景中摄像机的个数
Camera.allCamerasCount;
//得到所有摄像机
//用数组存储
Camera[] allCamera=Camera.allCameras;

//渲染相关委托
//摄像机剔除前处理的委托函数
Camera.onPreCull +=(c)=>
{

};
//摄像机渲染前处理的委托
Camera.onPreRender+=(c)=>
{

};
//摄像机渲染后处理的委托
Camera.onPostRender+=(c)=>
{

};

//重要成员
//界面上的参数 都可以在camera中获取
//比如下面这个代码 就是得到主摄像机的深度 进行设置
Camera.main.depth=10;
//世界坐标转屏幕坐标
//z轴坐标代表物体离这个摄像机有多远
//我们用这个功能做得最多的就是头顶血条相关的功能
Vector3 v=Camera.main.WorldToScreenPoint(this.transform.position);
print(v);

//屏幕坐标转世界坐标
print(Input.mousePosition);
//z轴代表的是摄像机到视口的距离(摄像机到视口的深度)
Camera.main.ScreenToWorldPoint(Input.mousePosition);

核心系统

光源系统基础

省略

物理系统之碰撞检测

刚体

碰撞产生的必要条件:两个物体都有碰撞器,至少一个物体有刚体。

Addcompont Rigidbody。

碰撞器(collider)

种类:1.盒状 2.球状 3.胶囊 4.网格 5.轮胎 6.地形

共同参数:1. Is Trigger 是否是触发器。如果启用此属性,则该碰撞体将用于触发事件,并被物理引擎忽略,主要用于进行没有物理效果的碰撞检测 2.Material 物理材质,可以确定碰撞体和其它对象碰撞时的交互方式 3.Center 碰撞体在局部空间的中心点位置

常用碰撞器:盒状碰撞器(BoxCollider) :Size 碰撞体在XYZ方向上的大小。球状碰撞器(Spehere Collider):Radius 球形碰撞体的半径大小。胶囊碰撞器(Capsule Collider):Radius 胶囊体的半径。 Height 胶囊体的高度。Direction 胶囊体在对象局部空间中的轴向

异形物体使用多种碰撞器的组合:刚体对象的子对象碰撞器信息参与碰撞检测

不常用碰撞器:1.网格碰撞器(Mesh Collider) 2.环状碰撞器(Wheel collider) 3.地形碰撞器(Terrain Collider)

物理材质

创建物理材质

参数:1.Dunamic Friction:已在移动时使用的摩擦力,通常为0到1的值,类似于动摩擦系数。2.Static Friction:当对象静止时在表面上时使用的摩擦力。类似于静摩擦系数。3.Bounciness 表面的弹性,值为0不会反弹,1不消耗任何能量,相当于完全弹性碰撞,类似于弹性系数。4.Friction Combine:两个对象的摩擦力的组合方式。5.unce Combince:两个碰撞对象的弹性的组合方式。其模式于Friction Combine模式相同

碰撞检测函数

碰撞和触发响应函数属于特殊的生命周期函数,也是通过反射调用。

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
//物理碰撞检测响应函数
//碰撞触发接触时会自动执行这个函数

private void OncollisionEnter(Collision collision)//参数时碰撞体
{
//Cillision类型的参数包括了碰到自己对象的相关信息
//关键参数
//碰撞到的对象碰撞器的信息
//collision.collider;

//碰撞对象的依附对象(GameObject)
//Collision.gameObject;

//碰撞对象的依附对象的位置信息
//Collision.transform;

//触碰点数相关
//collision.contactCount;

//接触点具体坐标
//ContactPoint[]pos=collision.contacts;

//只要得到碰撞到的对象的任意一个信息,就能得到他的所有信息

print(this.name+"被"+collision.gameObject.name+"撞到了");
}

//碰撞结束分离时会自动执行的函数
private void OncollisionExit(Collision collision);
{

}
//两个物体相互接触摩擦时 会不停的调用该函数
private void OnCollisionStay(Collision collision)
{

}

//触发器检测响应函数
//要打开触发器
//触发开始的函数 第一次接触时会自动调用
private void OnTriggerEnter(Collider other)//参数是碰撞器类型
{

}
//当重叠结束时会调用一次
private void OnTriggerExit(Collider other)
{

}
//当两个对象重叠时,会不停调用
private void OnTriggerStay(Collider other)
{

}

//明确什么时候会响应函数
//只要挂载的对象能和别的物体产生碰撞和触发,就能够被响应
//根据需求选择函数
//一般Enter用的比较多
//明确物理碰撞和触发器响应的区别


//碰撞和触发器函数都可以写成虚函数 在子类中重写逻辑
//一般把想要重写的碰撞和触发函数 写成保护类型的 没必要写成pubilc 因为不会自己手动调用 都是unity通过反射自动调用的

刚体加力

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
//刚体自带添加力
//给刚体加力的目的就是让其有一个速度 朝向某一个方向移动
//首先应该获取刚体组件
Rigidbody rigidBody;
rigidBody=this.GetComponent<Rigidbody>();
//添加力
//相对世界坐标
//世界坐标系 z轴正方向加了一个力
rigidBody.AddForce(Vector3.forward*10);
//相对本地坐标
rigidBody.AddRelativeForce(Vector3.forward*10);
//添加扭矩力 使其旋转
//相对世界坐标
rigidBody.AddTorque(Vector3.up*10);
//相对本地坐标
rigidBody.AddRelativeTorque(Vector3.up*10);

//直接改变速度
//速度方向是相对于世界坐标系
//如果要改变速度来让其移动 一定要注意
rigidBody.velocity=Vector3.forward*5;

//模拟爆炸效果
//模拟爆炸的力 所有产生效果的对象都要得到他们的刚体才有效果
rigidBody.AddExplosionForce(10,Vector3.zero,10);

//力的几种模式
rigidBody.AddForce(Vector3.forward*10,ForceMode.Acceleration);
//第二个参数 力的模式 主要的作用就是 计算方式不同而已
//由于四种计算方式的不同 最终的移动速度不同

//Acceleration
//给物体增加一个持续的加速度 忽略其质量
//就是不同质量的物体能获得相同的加速度

//Force
//给物体增加一个持续的加速度 与物体质量有关

//Inpulse
//给物体添加一个瞬间的力 与物体的质量有关 忽略时间 默认为1

//VelocityChange
//给物体添加一个瞬时速度 忽略质量 忽略时间

//力场脚本
//Constant Force

//补充 刚体的休眠
//unity为了节约性能 在特定情况下会将刚体休眠
rigidBody.IsSleeping//检测是否休眠状态

音效系统

主要是面板操作

常用的音效格式 wav mp3 ogg aiff

音频文件属性设置

音频源和音频监听脚本

Component: Audio source(源脚本 出声用的)、Audio Listener(监听脚本 收声用的 关了听不到声音)

代码控制音频源

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
//代码控制播放停止
AudioSource audioSource;

audioSource=this.GetComponent<AudioSource>();
if(Input.GetKeyDown(KeyCode.P))
{
//播放音效
audioSource.Play();
}

if(Input.GetKeyDown(KeyCode.S))
{
//停止播放
audioSource.Stop();
}
if(Input.GetKeyDown(KeyCode.Space))
{
//暂停播放
audioSource.Pause();
}
if(Input.GetKeyDown(KeyCode.X))
{
//停止暂停 从暂停中恢复
audioSource.UnPause();
}
if(Input.GetKeyDown(KeyCode.D))
{
//延迟5秒播放
audioSource.PlayDelayed(5);
}
//如何检测音效播放完毕
//如果你希望某一个音效播放完毕后 想要做什么事情
//可以在update生命周期函数中 不停的去检测它的该属性
//false播放完毕 true播放中
if(audioSource.isPlaying)
{
print("播放中");
}

else
{
print("播放结束");
}

//如何动态控制音效播放
//直接在要播放音效的对象上挂载脚本 控制播放
//实例化挂载音效源脚本的对象
//用一个AudioSource来控制播放不同的音效

麦克风输入相关

1
2
3
4
5
6
//获取设备麦克风信息
string[] strs= Microphone.devices;
for(int i=0;i<strs,Length;i++)
{
print(strs[i]);
}

剩下的之后找补

游戏场景切换和退出游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//场景切换
//要引用命名空间
if(Input.GetKeyDown(KeyCode.Space))
{
//alt加回车补全
//直接写代码切换场景可能会报错
//没有把场景加载到场景列表当中
//Build Settings
SceneManager.LoadScene("场景名");
}

//退出游戏
if(Input.GetKeyDown(KeyCode.Escape))
{
//执行这句代码就会退出游戏
//编辑模式下没有用
//发布游戏之后才有用
Application.Quit();
}

鼠标隐藏锁定相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//隐藏鼠标
Cursor.visible=false;
//锁定鼠标
//Node 不锁定
//Locked 锁定 鼠标被限制在屏幕中心点 还会被隐藏 通过esc键拜托编辑模式的锁定
//Confined 限制在窗口范围内
Cursor.LockState=CursorLockMode.Locked;
//设置鼠标图片
//图片要宽高一样
//图片非透明的话 将Texture Type设置成Cursor
//参数一 光标图片
//参数二 偏移位置 相对图片左上角
//参数三 平台支持的光标模式(硬件或软件)
Cursor.SetCursor(tex,Vector2.zero,CursorMode.Auto);

随机数和unity自带委托

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
//unity中的随机数
//Unity中的Random是在Unity引擎中的,不是C#中的
//同样是左闭右开
int randomNum=Random.Range(0,100);
//有一个float的重载
//左右都包含
float randomNumf==Random.Range(1.1f99.9f);

//c#中的随机数
//这里8讲了

//C#中的委托
//Action 无返回值
//Func有返回值
System.Action ac=()=>
{
print("");
};

System.Action<int,float>ac2=(i,f)=>
{

};

System.Func<int>fun1=()=>
{

};//泛型最后一个是返回值

//Unity自带的委托
UnityAction uac=()=>
{

};

UnityAction<String> uac1 = (s)=>
{

};

模型资源的导入

模型由什么构成:1.骨骼(骨骼)非必须 有动作的模型才需要 2.肉(网格面片)必需 决定了模型的轮廓 3.皮(贴图)必须 决定了模型的颜色效果

官方推荐使用FBX格式(.fbx)的模型文件 其他格式虽然支持 但是不推荐(dae 3ds dxf obj)

指导美术人员模型的导入,就是看Unity官方说明。导出的模型面朝向向朝模型坐标系的z轴

缩放大小单位注意

获取模型 AssetStore(推荐)淘宝(推荐)一些第三方的资源下载网站

获取资源一般有两种形式1.unity资源包(直接导入)2.原生的模型贴图资源(自己处理)