【从零开始的Unity魔法学堂】委托

# Unity魔法学堂
## 准备
### 准备工作
+ Unity Hub
+ Unity Editer
+ IDE(集成开发环境)
+ VScode
+ VS
### 基本架构
+ 场景(Scene)
+ 游戏对象(GameObject)
+ 组件(Component)
### Unity界面
+ 场景窗口
+ 快捷键QWERTY切换工具
+ WASDQE自由移动
+ Hierarchy(层级窗口)
+ 查看所有游戏对象
+ Inspector(检视面板)
+ 查看更改选中物体的信息
+ AddComponent可挂载Unity预制组件或自己写的脚本
+ 直接将脚本拖拽也可挂载
+ Game游戏窗口
+ 查看游戏实际运行画面
+ 游戏运行中性能参数也可在Game窗口监视
+ Project项目窗口
+ Unity项目工程文件夹
+ Console控制台窗口
+ 双击信息可快速跳转到代码对应位置
### 基本工作流程
+ 建立工程,导入资源
+ 搭建场景并配置游戏对象
+ 编写代码,控制台查错
+ 配置项目信息,打包导出
### Hello Word!
+ Unity支持语言-有且仅有C#
+ 配置编辑器
+ 顶部Edit-Preference
+ Preference界面-偏好设置页面
+ 生命周期函数
+ Start()
+ 开始前调用
+ Update()
+ 持续刷新
***
代码
```C#
print("Hello Word!"); //发送到控制台窗口,但不会在游戏中体现
```
## C#初级
### 变量与常量
#### 常见类型
|类型|描述|范围|默认值|
|:----:|:----:|:----:|:----:|
|bool |布尔值| True 或 False |False|
|byte |8位无符号整数 |0到255 |0|
|char |16位Unicode字符|U+0000 到 U+ffff|'\0'|
|decimal| 128 位精确的十进制值,28-29 有效位数 |$\frac{-7.9\times 10^{28} - 7.9\times 10^{28}}{10^{0-28}}$|0.0M|
|double| 64 位双精度浮点型 |$(+/-)5.0 \times 10^{-324} - (+/-)1.7 \times 10^{308}$| 0.0D|
|float| 32 位单精度浮点型 |-3.4 x 1038 到 + 3.4 x 1038 |0.0F|
|int |32 位有符号整数类型 |-2,147,483,648 到 2,147,483,647 |0|
|long |64 位有符号整数类型 |-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |0L|
|sbyte |8 位有符号整数类型 |-128 到 127 |0|
|short |16 位有符号整数类型 |-32,768 到 32,767 |0|
|uint| 32 位无符号整数类型 |0 到 4,294,967,295 |0|
|ulong |64 位无符号整数类型 |0 到 18,446,744,073,709,551,615 |0|
|ushort |16 位无符号整数类型 |0 到 65,535| 0|
+ 整数
+ sbyte
+ short
+ int
+ long
+ byte
+ ushort
+ uint
+ ulong
+ 小数
+ float
+ double
+ 文字
+ char
+ 逻辑
+ bool
#### 变量命名
#### 变量声明
如果要写float类型数字,需在后面加f
float a=0.1f;
### 二进制与类型
+ 机器码
+ Unicode
+ 不能向bool直接赋值0或1,必须使用true和false
+ 类型转换
### 运算符
+ 算术运算符
+ 关系运算符
+ 逻辑运算符
+ 赋值运算符
+ 位运算符
+ 每一位进行运算
+ &
+ |
+ ^
+ 异或
+ ~
+ 取反
+ <<
+ 左移
+ \>>
+ 右移
### 控制流
+ 执行语句的顺序
+ 顺序
+ 分支
+ 循环
### 方法
+ 函数
+ 重载
### 数组
#### 多维数组和交错数组
```C#
//规则数组(类似矩阵,每一行列数相同)
//一维数组
char[] allMagics = new char[9]={...};
char[] allMagics = new char[9];
//二维数组
int[,] squareCrystal = new int[2,3];
//三维数组
int[,,] cubeCrystal = new int[2,3,4];
//交错数组(每一行的列数不同)
float[][] crossCrystal = new float[3][]; //定义时可只填入第一个维度
crossCrystal[0] = new float[4];
crossCrystal[1] = new float[6];
crossCrystal[2] = new float[8];
```
#### 字符串
```C#
string name = "SB";
int age = 12;
//字符串的拼接
print(name+age+"岁") //int自动转化为字符串
```
**字符串支持空双引号"",但char不支持空单引号''**
### 枚举
+ enum枚举是一种特殊的基本类型
+ 可使用枚举来创建枚举类型
+ 因为限定了可包含的值,枚举类型可以防止无效输入
+ 枚举的成员直接使用英文名称来定义
+ 成员本质上也是数字
+ 隐含的值从0开始
+ 枚举默认值为第一个名字
```C#
修饰词(public、protected)enum 枚举类型名:整数类型
{
成员1,
成员2,
...
}
enum Gender
{
Male;
Famale;
Other;
}
Gender gender=Gender.Female;
```
### 结构体
struct
+ 结构体中不仅可以放变量,还可以放函数
```C#
struct Apprentice
{
public string name;
public Gender gender;
public int age;
public string[] allMagic;
}
Apprentice newbody;
```
+ 权限修饰符
+ public
+ 公开
+ private(默认)
+ 仅在当前结构中可访问
+ protected
+ 受保护的
### 总结
## C#高级
### 封装
#### 类
+ 面向对象的思想
```C#
class Kami
{
public string name;
//构造函数可重载
public Kami(string name)
{
this.name = name; //为实例赋值
print("神明"+name+"诞生了!");
}
public Kami(int tribute)
{
print("收获了"+tribute+"贡品");
}
//方法和静态方法
public void RequestWish()
{
print("许愿");
}
public static void RealizeWish()
{
print("神明帮你实现了愿望");
}
}
Kami new_kami = new Kami("Anki");
new_kami.RequestWish(); //普通方法,需要通过实体实现
Kami.RealizeWish(); //静态方法,归属于Kami类无需创建变量,直接使用
```
#### 值与引用
+ 内存可分为堆和栈两块区域
+ 堆:大而慢
+ 值类型数据在栈上
+ 栈:小而快
+ 引用类型数据在栈上存放一个堆地址,数据在堆上
+ 值类型
+ 数字
+ 文字
+ 逻辑
+ 枚举
+ 结构体
+ 引用类型
+ 数组
+ 字符串
+ 类
+ 实际上Unity中所有变量都声明在类中,存在堆里,只有局部变量会在栈上开辟空间
### 继承
#### 继承
+ 面向对象三大特征之一
+ 被继承者称为父类或基类
+ 继承者称为子类或派生类
+ 子类继承后可添加自己的成员
+ 一父多子
```C#
//父类
class Apprentice
{
public string name;
public Gender gender;
public int age;
}
//子类
class MagicApperentice:Apperentice //继承学徒类,并可继续附加成员
{
public void TellName()
{
print(name); //子类只能继承public的属性和方法,如果是private则不会被继承,protected可以被继承,不能被外部调用
}
}
class BlacksmithApperentice:Apperentice
{
}
class MusicianApperentice:Apperentice
{
}
```
#### MonoBehaviour
+ Unity的脚本都派生自该类,这些脚本可挂载在GameObject组件上
+ 被挂载的组件自动实例化
+ 除了Start、Update外,MonoBehaviour还提供了很多别的生命周期以及便捷的成员变量和方法
+ print()属于MonoBehaviour的方法,是对Debug.Log()方法的封装
### 多态
+ 面向对象三大特征之一
+ 当使用某个功能时,根据情况,功能会有不同的表现形态
+ 通过方法重载,在类内提供名称统一但表现不同的方法
+ 通过方法重写,为不同子类提供名称统一而表现不同的方法
+ 多态是一种思想,不是语法
+ 动态多态
+ 也叫子类型多态、包含多态
+ 父类通过定义虚方法为子类规定一种功能,子类可以放进父类型的变量中,直接通过父类型变量访问这个虚方法,此时不同子类在这个虚方法上表现出了多态
+ 面向对象编程所说的多态通常指这种多态
```C#
//父类
class Apprentice
{
public string name;
public Gender gender;
public int age;
public virtual void TellName() //虚方法,方便子类进行重写,必须对子类公开(public或protected)
{
print(name);
}
}
//子类
class MagicApperentice:Apperentice //继承学徒类,并可继续附加成员
{
public override void TellName() //重写父类虚方法,方法头与父类保持一致,如果不重写,则直接继承
{
print(name+"魔法学徒");
base.TellName(); //调用父类的虚方法
}
}
class BlacksmithApperentice:Apperentice
{
}
class MusicianApperentice:Apperentice
{
}
//父类变量的数组可以指向子类变量
Apprentice[] many = new Apprentice[3];
//构造方法后可以使用{成员变量1=值,成员变量2=值}的形式进行对象初始化
many[0]=new MagicApperentice(){name = "marlin"};
many[1]=new BlacksmithApperentice(){name = "Yaki"};
many[2]=new MusicianApperentice(){name = "Anna"};
```
### 抽象与接口
#### abstract抽象
+ 在class前加关键词abstract
+ 作为一个抽象的概念,不应具有实体,因此抽象类只能被继承,自身无法实例化
+ 抽象类的子类依然可以是抽象类
+ 在方法前面加关键词abstract,抽象方法
+ 抽象方法只能定义在抽象类中
+ 与虚方法类似,抽象方法不能私有
+ 一定会被重写,所以在父类中无需实现
+ 除非同为抽象类,否则子类必须重写所有抽象方法
+ 抽象方法规定了子类必须实现的功能
```C#
//定义抽象类
abstract class Animal
{
//定义抽象方法
public abstract void Run();
}
```
#### interface接口
+ 指一个功能模块提供给使用者的媒介
+ 类通过类似继承的方式加入接口,这个过程叫实现接口
+ 接口通过抽象方法规定了类中必须存在的公开方法,相当于规定了类提供给调用者的接口
```C#
interface IMagic
{
//接口里面创造共同具有的抽象方法,权限修饰符和abstract可省略
//public abstract void Invoke();
void Invoke();
}
class Dragon:IMagic,IFly,IRun //类通过继承的格式接收接口,继承的接口不限数量,添加别的接口以及父类使用逗号隔开
{
public void Invoke(){}; //接口中的方法必须是public,不用写override
}
class Magician:Human,IMagic
{}
Class Elf:IMagic
{}
//当一组类拥有相同的接口,就能使用相同的数组来管理具备相同特征的实例
IMagic[] magicCreatures = new IMagic[3];
magicCreatures[0] = new Dragon();
magicCreatures[1] = new Magician();
magicCreatures[2] = new Elf();
```
### 属性与异常
#### 属性
+ 对类中成员的一层包装,也叫类的命名成员
+ 属性通过访问器get、set来读写包装内的数据
+ get和set内除了return语句外,还能添加其他语句,因此可以用于数据二次处理、关联其他逻辑等
+ 如果省略get或set,可实现只读或只写
```C#
//设置类的属性
int target;
public int targetinfo
{
get
{
return target;
}
set
{
target = value;
}
}
targetinfo = 100; //将传入set的value中
```
#### 异常
+ 一些复合基本语法,但是运行过程中会出现的错误
+ 当问题发生时,程序就会抛出异常,若不处理,就会导致一系列问题
+ 用户可以使用throw关键字+异常实例来主动抛出异常,也可通过继承来自定义异常类型
|异常符号|异常内容|
|:----:|:----:|
|SystemException | 其他用户可处理的异常的基本类 |
|ArgumentException |方法的参数是非法的 |
|ArgumentNullException | 一个空参数传递给方法,该方法不能接受该参数 |
|ArgumentOutOfRangeException | 参数值超出范围 |
|ArithmeticException |出现算术上溢或者下溢 |
|ArrayTypeMismatchException |试图在数组中存储错误类型的对象 |
|BadImageFormatException | 图形的格式错误 |
|DivideByZeroException | 除零异常 |
|DllNotFoundException | 找不到引用的DLL |
|FormatException | 参数格式错误 |
|IndexOutOfRangeException | 数组索引超出范围 |
|InvalidCastException |使用无效的类 |
|InvalidOperationException | 方法的调用时间错误 |
|MethodAccessException | 试图访问思友或者受保护的方法 |
|MissingMemberException | 访问一个无效版本的DLL |
|NotFiniteNumberException | 对象不是一个有效的成员 |
|NotSupportedException | 调用的方法在类中没有实现 |
|NullReferenceException |试图使用一个未分配的引用 |
|OutOfMemoryException | 内存空间不够 |
|PlatformNotSupportedException | 平台不支持某个特定属性时抛出该错误 |
|StackOverflowException |堆栈溢出|
|SystemException | 运行时产生的所有错误的基类。|
|IndexOutOfRangeException | 当一个数组的下标超出范围时运行时引发。|
|NullReferenceException | 当一个空对象被引用时运行时引发。|
|InvalidOperationException | 当对方法的调用对对象的当前状态无效时,由某些方法引发。|
|ArgumentException | 所有参数异常的基类。|
|ArgumentNullException |在参数为空(不允许)的情况下,由方法引发。|
|ArgumentOutOfRangeException |当参数不在一个给定范围之内时,由方法引发。|
|InteropException | 目标在或发生在CLR外面环境中的异常的基类。|
|ComException | 包含COM类的HRESULT信息的异常。|
|SEHException | 封装Win32结构异常处理信息的异常。|
|SqlException | 封装了SQL操作异常。|
#### 异常处理
+ 异常捕获:try-catch-finally
+ try中为可能抛出异常的代码,之后可跟多个catch块,每个catch块后可跟异常类型,并在花括号中处理异常
+ 如果catch后没有说明异常类型,则捕获所有类型异常
+ 无论是否捕获异常,finally都会执行,但finally可省略
```C#
try
{
print(allMagics[1]);
}
catch(System.IndexOutOfRangeException)
{
//如果try中语句引起此类型异常,则执行此处代码
//如果省略小括号,则发生任何类型异常,都会执行此catch中内容
}
...
finally
{
//无论是否捕获异常,finally都会执行
}
```
### 运算符重载与索引器
#### 运算符重载
+ 可以对C#内置的部分运算符进行重新定义
+ 可重载运算符
+ 算术运算符
+ 条件运算符
+ 参数和返回类型符合运算符自身要求
+ 参数有两个且不同的类时,参数左右顺序决定使用时对象的左右顺序
+ 部分运算符要求成对重载
+ \>和<
+ == 和 !=
```C#
class Apprentice
{
public static Apprentice operator +(Apprentice a,Apprentice b);
//运算符重载函数的参数与运算符操作数数量相同
//返回类型不能是void
}
```
#### 封装的作用
+ 将高频使用的功能单独提取出来,可以重复使用以提高效率
+ 单独提取部分作为独立模块,内部逻辑和外部分离,若修改独立模块内部逻辑,不会影响其他模块
+ 若更换关联模块,只要满足同样的对接方式,即可直接替换
+ 规定系统中各模块的对接方式,是接口存在的意义
#### 索引器
+ 允许一个对象可以像数组一样使用下标来访问,适合具有成组特性的类
+ 与属性类似,通过get、set访问器来定义索引方式
+ 方括号中的参数即为访问时的下标,可以定义为各种类型
+ 利用索引器和类中的数组,可以组合出功能更高级的数据结构
+ 对于数组,包含着length等相关成员可供访问
```C#
class MagicSet
{
public int this[int index]
{
// get 访问器
get
{
// 返回 index 指定的值
}
// set 访问器
set
{
// 设置 index 指定的值
}
}
}
```
#### 举例
运算符重载
```C#
public class Main:MonoBehaviour
{
Apprentice apprentice = new Apprentice();
Provision p1=new Provision(10),p2=new Provision(50);
void Start()
{
Apprentice newApprentice = apprentice + (p1 + p2);
print(newApprentice.hp+" "+newApprentice.mp);
}
void Update()
{
}
}
class Apprentice //学徒
{
public int hp,mp
}
class Provision //补给包
{
public int hp,mp;
public Provision(){};
public Provision(int number){bp=mp=number;}
public static Provision operator +(Provision a,Provision b)
{
return new Provision(){hp=a.hp+b.hp,mp=a.mp+b.mp};
}
public static Apprentice operator + (Apprentice a,Provision b)
{
return new Apprentice(){hp=a.hp+b.hp,mp=a.mp+b.mp};
}
}
```
索引器的使用
```C#
public class Main:MonoBehaviour
{
MagicSet magicset=new MagicSet(1);
void Start()
{
magicset[0]="Truth";
print(magicset[1]);
}
void Update()
{
}
}
class MagicSet
{
string[] magics; //数组
public MagicSet(int length) //构造函数
{
magics = new string[length]; //为数组开辟空间
}
//定义索引器
public string this[int index]
{
get
{
if(index>magics.Length-1||index<0)
{
return "Index out of range";
}
return magics[index];
}
set
{
magics[index]=value;
}
}
}
```
### 泛型与集合
#### 泛型
+ 一种延迟编写类型的规范
+ 先拟定一个类型,在调用时再填入具体类型
+ 可以看作将类型作为一种可变参数,进一步提高代码的复用性
+ 泛型除了作用在方法,还能作用于类、接口等
+ 作用于类时,在类名后加尖括号<>,在其中为泛型起名即可
+ class GClass<T>{...}
+ 一次可定义多个泛型,在尖括号中用逗号隔开
+ <T1,T2>
```C#
public T GenericMethod<T>(T t);
//为各种数组添加元素的方法
Type[] AddElement<Type>(ref Type[] array,Type newElement) //ref类似于取地址,变量以引用的方式进入函数修改值等效于修改外部变量
{
Type[] newArray=new Type[array.Length+1];
for(int i=0;i<array.Length;i++)
{
newArray[i]=array[i];
}
newArray[newArray.Length-1]=newElement;
return newArray;
}
```
#### ref和out
+ 用来修改参数方法的两个关键词
+ 若将一个参数定义为ref和out,调用时必须填入可赋值的变量,且要加上对应的ref或out词缀
+ 修改方法内的ref或out变量,将会直接改变调用者外部填入的变量
+ 区别在于
+ ref可以不修改,作为普通参数使用
+ out必须修改,且在修改后才能在方法内使用
+ 可以通过定义多个ref或out参数,来实现一个方法带出多个返回值
#### List类
+ List <T>是强类型对象的集合,可以通过索引对其进行访问,并具有用于排序,搜索和修改列表的方法
+ 它是System.Collection.Generic命名空间下的ArrayList的泛型版本。
List的属性和方法
|属性| 用法|
|:----:|:----:|
|Items| 获取或设置指定索引处的元素|
|Count| 返回List <T>中存在的元素总数|
|方法| 用法|
|:----:|:----:|
|Add| 在List <T>的末尾添加元素。|
|AddRange| 将指定集合的元素添加到List <T>的末尾。|
|BinarySearch| 搜索元素并返回该元素的索引。|
|Clear |从List <T>中删除所有元素。|
|Contains| 检查指定元素在List <T>中是否存在。|
|Find |根据指定的谓词函数查找第一个元素。|
|Foreach| 遍历 List <T>。|
|Insert| 在List <T>中的指定索引处插入元素。|
|InsertRange| 在指定的索引处插入另一个集合的元素。|
|Remove |删除指定元素的第一次出现。|
|RemoveAt| 删除指定索引处的元素。|
|RemoveRange| 删除所有与提供的谓词功能匹配的元素。|
|Sort |对所有元素进行排序。|
|TrimExcess| 将容量设置为实际的元素数。|
|TrueForAll |确定List <T>中的每个元素是否与指定谓词定义的条件匹配。|
```C#
List<string> magics = new List<string>(); //不必确定空间大小
magics.Add(); //添加元素等
```
#### Dictionary字典
+ Dictionary < TKey,TValue > 是一个泛型集合,它以不特定的顺序存储键值对。
+ 属于System.Collection.Generic命名空间。
+ 实现 IDictionary <TKey,TValue>接口。
+ 键必须是唯一的,不能为null。
+ 值可以为null或重复。
+ 可以通过在索引器中传递相关键来访问值,例如 myDictionary[key]
+ 元素存储为 KeyValuePair <TKey,TValue> 对象。
|方法| 用法|
|:----:|:----:|
|Comparer| 获取用于确定字典中的键是否相等的 IEqualityComparer|
|Count |获取包含在 Dictionary中的键/值对的数目|
|Item| 获取或设置与指定的键相关联的值|
|Keys |获取包含 Dictionary中的键的集合|
|Values |获取包含 Dictionary中的值的集合|
|Add | 将指定的键和值添加到字典中|
|Clear |从 Dictionary中移除所有的键和值|
|ContainsKey |确定 Dictionary是否包含指定的键 |
|ContainsValue |确定 Dictionary是否包含特定值 |
|GetEnumerator | 返回循环访问 Dictionary的枚举数 |
|GetType |获取当前实例的 Type (从 Object 继承)|
|Remove | 从 Dictionary中移除所指定的键的值 |
|ToString |返回表示当前 Object的 String (从 Object 继承)|
|TryGetValue | 获取与指定的键相关联的值 |
```C#
IDictionary<int, string> numberNames = new Dictionary<int, string>();
numberNames.Add(1,"One"); //使用Add()方法添加键/值
```
#### 泛型集合
+ 微软在.Net架构中,封装好的、支持泛型的各种数据存储结构
+ 这些结构以类的形式提供支持,并且可以动态扩展大小,其中包含一系列数据处理相关的方法
+ 常用的有
+ List<T>
+ Dictionary<TKey,TValue>
+ Stack<T>
+ Queue<T>
+ 此外还有非泛型集合
+ 非泛型集合中字典叫做Hashtable哈希表
### 委托
#### 事件驱动
+ 一种程序设计思路,可以很好的解除各个模块间的强关联,将各种用户操作、游戏事件进行抽象提取,集中管理起来
+ 作为事件的发布方,在合适的时机发布事件即可,无需关心谁订阅了事件
+ 作为事件订阅方,在事件被发布时执行相关操作即可,无需关心是谁发布了事件
+ 以跨平台游戏为例,游戏核心层只需监听操作事件,无需考虑触发设备是屏幕、键盘还是手柄
#### delegate委托
+ 使用关键词delegate声明委托
+ 委托声明决定了该委托可以绑定什么格式的方法
+ 使用委托声明来创建委托变量,之后可以通过实例化来绑定方法
+ 委托变量本质上存储着一个方法的引用,类似于C++中函数指针
```C#
public class Main:MonoBehaviour
{
public delegate void Event(string s); //定义委托
Event weather,notice;
void Start()
{
//发布委托
weather("晴");
notice("考试");
//订阅委托
weather = new Event(GoToLibrary);//同weather += GoToLibrary;
//如果为=,则只能绑定一个方法,如果要添加委托,则使用+=
weather += Business;
//解绑委托
weather -= Business;
weather += (s)=>{print("今天"+s+"去玩");}; //匿名函数
}
void Update()
{
}
void GoToLibrary(string s)
{
print("今天"+s+"去图书馆");
}
void Business(string s)
{
print("今天"+s+"去工作");
}
}
```
#### 匿名方法
+ 没有名称只有主体的方法,专用于委托
+ 简化格式也称为Lambda表达式
+ delegate (string s){···}
+ (s)=>{···}
+ 头部只有一个参数或主体只有一行非return语句时,对用括号可以省略
+ s=>print("")
### 总结